├── .gitignore ├── LICENSE ├── README.md ├── elm-package.json └── src ├── Lazy.elm └── Native └── Lazy.js /.gitignore: -------------------------------------------------------------------------------- 1 | elm-stuff/ 2 | *~ 3 | *.html 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, Max New 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are 6 | met: 7 | 8 | 1. Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | 2. Redistributions in binary form must reproduce the above copyright 12 | notice, this list of conditions and the following disclaimer in the 13 | documentation and/or other materials provided with the 14 | distribution. 15 | 16 | 3. Neither the name of the author nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DEPRECATED 2 | 3 | It turned out that many folks were unclear on the difference between laziness 4 | and delayed computation: 5 | 6 | - **Delayed Computation** is when you have a value like `answer : () -> Int` 7 | that you can evaluate later. You only do all the computation when you say 8 | `answer ()` at some later time. If you call `answer ()` four times, you do 9 | the computation four times. 10 | 11 | - **Laziness** is an optimization on top of delayed computation. It is just 12 | like having `answer : () -> Int` but when this function is evaluated for the 13 | first time, the results are saved. So if you call `answer ()` four times, you 14 | do the computation *one* time. 15 | 16 | In all the cases in Elm that I have heard of that use this library, folks only 17 | really needed *delayed computation* and ended up with simpler code when they 18 | went that way. 19 | 20 | So in the end, there are two major reasons to stop supporting laziness: 21 | 22 | 1. It is overkill for all the scenarios I have seen in Elm. 23 | 2. It allows the creation of cyclic data, significantly complicating GC. 24 | 25 | With laziness you can create a list like `ones = 1 :: ones` that refers to 26 | itself. Without laziness, there is no way to create cyclic data in Elm. That 27 | means we can use a naive reference counting approach to collect garbage if we 28 | wanted. So although people have dreamed up data structures that use laziness 29 | in interesting ways, I do not feel these cases are compelling enough to commit 30 | to the collateral complications. 31 | 32 |
33 | 34 | * * * 35 | 36 | What follows is some of content from the old README. 37 | 38 | * * * 39 | 40 |
41 | 42 | 43 | # Pitfalls 44 | 45 | **Laziness + Time** — 46 | Over time, laziness can become a bad strategy. As a very simple example, think 47 | of a timer that counts down from 10 minutes, decrementing every second. Each 48 | step is very cheap to compute. You subtract one from the current time and store 49 | the new time in memory, so each step has a constant cost and memory usage is 50 | constant. Great! If you are lazy, you say “here is how you would subtract 51 | one” and store that *entire computation* in memory. This means our memory 52 | usage grows linearly as each second passes. When we finally need the result, we 53 | might have 10 minutes of computation to run all at once. In the best case, this 54 | introduces a delay that no one *really* notices. In the worst case, this 55 | computation is actually too big to run all at once and crashes. Just like with 56 | dishes or homework, being lazy over time can be quite destructive. 57 | 58 | **Laziness + Concurrency** — 59 | When you add concurrency into the mix, you need to be even more careful with 60 | laziness. As an example, say we are running expensive computations on three 61 | worker threads, and the results are sent to a fourth thread just for rendering. 62 | If our three worker threads are doing their work lazily, they 63 | “finish” super quick and pass the entire workload onto the render 64 | thread. All the work we put into designing this concurrent system is wasted, 65 | everything is run sequentially on the render thread! It is just like working on 66 | a team with lazy people. You have to pay the cost of coordinating with them, 67 | but you end up doing all the work anyway. You are better off making things 68 | single threaded! 69 | 70 | 71 | ## Learn More 72 | 73 | One of the most delightful uses of laziness is to create infinite streams of 74 | values. Hopefully we can get a set of interesting challenges together so 75 | you can run through them and get comfortable. 76 | 77 | For a deeper dive, Chris Okasaki's book *Purely Functional Data Structures* 78 | and [thesis](http://www.cs.cmu.edu/~rwh/theses/okasaki.pdf) 79 | have interesting examples of data structures that get great 80 | benefits from laziness, and hopefully it will provide some inspiration for the 81 | problems you face in practice. 82 | 83 | -------------------------------------------------------------------------------- /elm-package.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "summary": "Basic primitives for working with laziness", 4 | "repository": "http://github.com/elm-lang/lazy.git", 5 | "license": "BSD3", 6 | "source-directories": [ 7 | "src" 8 | ], 9 | "exposed-modules": [ 10 | "Lazy" 11 | ], 12 | "native-modules": true, 13 | "dependencies": { 14 | "elm-lang/core": "5.0.0 <= v < 6.0.0" 15 | }, 16 | "elm-version": "0.18.0 <= v < 0.19.0" 17 | } 18 | -------------------------------------------------------------------------------- /src/Lazy.elm: -------------------------------------------------------------------------------- 1 | module Lazy exposing 2 | ( Lazy 3 | , force, lazy 4 | , map, map2, map3, map4, map5 5 | , apply, andThen 6 | ) 7 | 8 | {-| This library lets you delay a computation until later. 9 | 10 | # Basics 11 | @docs Lazy, lazy, force 12 | 13 | # Mapping 14 | @docs map, map2, map3, map4, map5 15 | 16 | # Chaining 17 | @docs apply, andThen 18 | -} 19 | 20 | import Native.Lazy 21 | 22 | 23 | 24 | -- PRIMITIVES 25 | 26 | 27 | {-| A wrapper around a value that will be lazily evaluated. -} 28 | type Lazy a = 29 | Lazy (() -> a) 30 | 31 | 32 | {-| Delay the evaluation of a value until later. For example, maybe we will 33 | need to generate a very long list and find its sum, but we do not want to do 34 | it unless it is absolutely necessary. 35 | 36 | lazySum : Lazy Int 37 | lazySum = 38 | lazy (\() -> sum [1..1000000]) 39 | 40 | Now we only pay for `lazySum` if we actually need it. 41 | -} 42 | lazy : (() -> a) -> Lazy a 43 | lazy thunk = 44 | Lazy (Native.Lazy.memoize thunk) 45 | 46 | 47 | {-| Force the evaluation of a lazy value. This means we only pay for the 48 | computation when we need it. Here is a rather contrived example. 49 | 50 | lazySum : Lazy Int 51 | lazySum = 52 | lazy (\() -> List.sum [1..1000000]) 53 | 54 | sums : (Int, Int, Int) 55 | sums = 56 | (force lazySum, force lazySum, force lazySum) 57 | 58 | We are forcing this computation three times. The cool thing is that the first 59 | time you `force` a value, the result is stored. This means you pay the cost on 60 | the first one, but all the rest are very cheap, basically just looking up a 61 | value in memory. 62 | -} 63 | force : Lazy a -> a 64 | force (Lazy thunk) = 65 | thunk () 66 | 67 | 68 | 69 | -- COMPOSING LAZINESS 70 | 71 | 72 | {-| Lazily apply a function to a lazy value. 73 | 74 | lazySum : Lazy Int 75 | lazySum = 76 | map List.sum (lazy (\() -> [1..1000000])) 77 | 78 | The resulting lazy value will create a big list and sum it up when it is 79 | finally forced. 80 | -} 81 | map : (a -> b) -> Lazy a -> Lazy b 82 | map f a = 83 | lazy (\() -> f (force a)) 84 | 85 | 86 | {-| Lazily apply a function to two lazy values. 87 | 88 | lazySum : Lazy Int 89 | lazySum = 90 | lazy (\() -> List.sum [1..1000000]) 91 | 92 | lazySumPair : Lazy (Int, Int) 93 | lazySumPair = 94 | map2 (,) lazySum lazySum 95 | 96 | -} 97 | map2 : (a -> b -> result) -> Lazy a -> Lazy b -> Lazy result 98 | map2 f a b = 99 | lazy (\() -> f (force a) (force b)) 100 | 101 | 102 | {-|-} 103 | map3 : (a -> b -> c -> result) -> Lazy a -> Lazy b -> Lazy c -> Lazy result 104 | map3 f a b c = 105 | lazy (\() -> f (force a) (force b) (force c)) 106 | 107 | 108 | {-|-} 109 | map4 : (a -> b -> c -> d -> result) -> Lazy a -> Lazy b -> Lazy c -> Lazy d -> Lazy result 110 | map4 f a b c d = 111 | lazy (\() -> f (force a) (force b) (force c) (force d)) 112 | 113 | 114 | {-|-} 115 | map5 : (a -> b -> c -> d -> e -> result) -> Lazy a -> Lazy b -> Lazy c -> Lazy d -> Lazy e -> Lazy result 116 | map5 f a b c d e = 117 | lazy (\() -> f (force a) (force b) (force c) (force d) (force e)) 118 | 119 | 120 | {-| Lazily apply a lazy function to a lazy value. This is pretty rare on its 121 | own, but it lets you map as high as you want. 122 | 123 | map3 f a b == f `map` a `apply` b `apply` c 124 | 125 | It is not the most beautiful, but it is equivalent and will let you create 126 | `map9` quite easily if you really need it. 127 | -} 128 | apply : Lazy (a -> b) -> Lazy a -> Lazy b 129 | apply f x = 130 | lazy (\() -> (force f) (force x)) 131 | 132 | 133 | {-| Lazily chain together lazy computations, for when you have a series of 134 | steps that all need to be performed lazily. This can be nice when you need to 135 | pattern match on a value, for example, when appending lazy lists: 136 | 137 | type List a = Empty | Node a (Lazy (List a)) 138 | 139 | cons : a -> Lazy (List a) -> Lazy (List a) 140 | cons first rest = 141 | Lazy.map (Node first) rest 142 | 143 | append : Lazy (List a) -> Lazy (List a) -> Lazy (List a) 144 | append lazyList1 lazyList2 = 145 | let 146 | appendHelp list1 = 147 | case list1 of 148 | Empty -> 149 | lazyList2 150 | 151 | Node first rest -> 152 | cons first (append rest list2)) 153 | in 154 | lazyList1 155 | |> Lazy.andThen appendHelp 156 | 157 | 158 | By using `andThen` we ensure that neither `lazyList1` or `lazyList2` are forced 159 | before they are needed. So as written, the `append` function delays the pattern 160 | matching until later. 161 | -} 162 | andThen : (a -> Lazy b) -> Lazy a -> Lazy b 163 | andThen callback a = 164 | lazy (\() -> force (callback (force a))) 165 | -------------------------------------------------------------------------------- /src/Native/Lazy.js: -------------------------------------------------------------------------------- 1 | var _elm_lang$lazy$Native_Lazy = function() { 2 | 3 | function memoize(thunk) 4 | { 5 | var value; 6 | var isForced = false; 7 | return function(tuple0) { 8 | if (!isForced) { 9 | value = thunk(tuple0); 10 | isForced = true; 11 | } 12 | return value; 13 | }; 14 | } 15 | 16 | return { 17 | memoize: memoize 18 | }; 19 | 20 | }(); 21 | --------------------------------------------------------------------------------