├── .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 |
--------------------------------------------------------------------------------