@@ -5,7 +5,7 @@ There are two problems that will lead you here, both of them pretty tricky:
6 1. [**No Mutation**](#no-mutation) — Defining values in Elm is slightly different than defining values in languages like JavaScript.
6 1. [**No Mutation**](#no-mutation) — Defining values in Elm is slightly different than defining values in languages like JavaScript.
8− 2. [**Tricky Recursion**](#tricky-recursion) — Sometimes you need to define recursive values. A common case is JSON decoders for things like discussion forums, where a comment may have replies, which may have replies, etc.
8+ 2. [**Tricky Recursion**](#tricky-recursion) — Sometimes you need to define recursive values when creating generators, decoders, and parsers. A common case is a JSON decoder a discussion forums where a comment may have replies, which may have replies, which may have replies, etc.
11## No Mutation
11## No Mutation
@@ -51,10 +51,11 @@ type alias Comment =
51 , responses : Responses
51 , responses : Responses
54−type Responses = Responses (List Comment)
55+ Responses (List Comment)
57−You may have run into this definition in the [hints for recursive aliases](recursive-alias)! Anyway, once you have comments, you may want to turn them into JSON to send back to your server or to store in your database or whatever. So you will probably write some code like this:
58+You may have run into this definition in the [hints for recursive aliases](recursive-alias.md)! Anyway, once you have comments, you may want to turn them into JSON to send back to your server or to store in your database or whatever. So you will probably write some code like this:
60import Json.Decode as Decode exposing (Decoder)
61import Json.Decode as Decode exposing (Decoder)
@@ -91,46 +92,42 @@ decodeComment =
91-- SOLUTION
92-- SOLUTION
92decodeResponses : Decoder Responses
93decodeResponses : Decoder Responses
93decodeResponses =
94decodeResponses =
94− Decode.map Responses (Decode.list (Decode.lazy (\\_ -> decodeComment)))
95+ Decode.map Responses (Decode.list (Decode.lazy (\_ -> decodeComment)))
97−Notice that in `decodeResponses`, we hide `decodeComment` behind an anonymous function. Elm cannot evaluate an anonymous function until it is given arguments. So it allows us to delay evaluation until it is needed. If there are no comments, we will not need to expand it!
98+Notice that in `decodeResponses`, we hide `decodeComment` behind an anonymous function. Elm cannot evaluate an anonymous function until it is given arguments, so it allows us to delay evaluation until it is needed. If there are no comments, we will not need to expand it!
100+This saves us from expanding the value infinitely. Instead we only expand the value if we need to.
100−### Summary
102+> **Note:** The same kind of logic can be applied to tasks, random value generators, and parsers. Use `lazy` or `andThen` to make sure a recursive value is only expanded if needed.
102−**Situation:** You want a recursive JSON decoder, random value generator, or task. These are all cases where a *value* represents some sort of computation.
104−**Solution:** put the recursion behind an anonymous function.
105+## Understanding “Bad Recursion”
106−You can always do this by defining `lazy` in terms of `andThen`. In the case of random value generators, it would look like this:
107+The compiler tries to detect bad recursion, but how does it know the difference between good and bad situations? Writing `factorial` is fine, but writing `x = x + 1` is not. One version of `decodeComment` was bad, but the other was fine. What is the rule?
108−```elm
109+**Elm will allow recursive definitions as long as there is at least one lambda before you get back to yourself.** So if we write `factorial` without any pretty syntax, it looks like this:
109−import Random exposing (Generator, andThen)
110−import Random.Extra exposing (constant)
112−lazy : (() -> Generator a) -> Generator a
111+```elm
113−lazy delayedGenerator =
112+factorial =
114− constant ()
113+ \n -> if n <= 0 then 1 else n * factorial (n - 1)
115− |> andThen delayedGenerator
118−If you are in some situation where this will not work, I am curious to hear about it. I would recommend asking around in [the Elm slack](http://elmlang.herokuapp.com/) first, and opening an issue here if it seems to be the real deal!
116+There is technically a lambda between the definition and the use, so it is okay! The same is true with the good version of `decodeComment`. There is a lambda between the definition and the use. As long as there is a lambda before you get back to yourself, the compiler will let it through.
121−## Defining “Bad” Recursion
123−The compiler will yell at you for *bad* recursion, but how does it know the difference between good and bad situations? Writing `factorial` is fine, but writing `x = x + 1` is not. One version of `decodeComment` was bad, but the other was fine. What is the rule?
118+**This rule is nice, but it does not catch everything.** It is pretty easy to write a definition where the recursion is hidden behind a lambda, but it still immediately expands forever:
125−**Elm will allow recursive definitions as long as there is at least one lambda before you get back to yourself.** So if we write `factorial` without any pretty syntax, it looks like this:
129− \\n -> if n <= 0 then 1 else n * factorial (n - 1)
122+ (\_ -> x) () + 1
132−There is technically a lambda between the definition and the use! The same is true with the good version of `decodeComment`. As long as there is a lambda before you get back to yourself, the compiler will let it through.
125+This follows the rules, but it immediately expands until our program runs out of stack space. It leads to a runtime error as soon as you start your program. It is nice to fail fast, but why not have the compiler detect this as well? It turns out this is much harder than it sounds!
127+This is called [the halting problem](https://en.wikipedia.org/wiki/Halting_problem) in computer science. Computational theorists were asking:
129+> Can we determine if a program will finish running (i.e. halt) or if it will continue to run forever?
134−This rule is nice, but it does not catch *everything*. Pathological cases like `x = (\\_ -> x) () + 1` can make it through. Why not rule these out as well though?! Well, because it is impossible! And not the normal kind of impossible where your cable bill is messed up and the fifth time you call it turns out it *can* be corrected. This is the “we proved it with math, it’s called [the halting problem](https://en.wikipedia.org/wiki/Halting_problem)” kind of impossible. Impossible [like this](https://www.youtube.com/watch?v=nlD9JYP8u5E).
131+It turns out that Alan Turing wrote a proof in 1936 showing that (1) in some cases you just have to check by running the program and (2) this check will take forever for programs that do not halt!
136−Anyway, I just thought it was cool that we cannot solve the halting problem *in general*, but a simple rule about lambdas can detect the majority of bad cases *in practice*.
133+**So we cannot solve the halting problem *in general*, but our simple rule about lambdas can detect the majority of bad cases *in practice*.**