Reputation: 273
Can someone please explain why the first function generates the stream "a"
, "aa"
, "aaa"
.. ?
Or why the second generates a stream of prefixes for example:
prefixes [1,2,3..]
-> [[],[1],[1,2], [1,2,3]..]
strings = "" : ( map ('a' :) strings )
prefixes (x : xs) = [] : ( map (x :) (prefixes xs) )
Upvotes: 1
Views: 135
Reputation: 54058
They're both pretty similar, so I'll just show you strings
and let you figure out prefixes
on your own as an exercise.
Haskell likes being both recursive and lazy. Laziness means that values are represented by thunks, or promises of future computations. You can actually see them in GHCi when you evaluate something:
> let strings = "" : map ('a' :) strings
> :print strings
strings = (_t1 :: [[Char]])
> strings !! 0
""
> :print strings
strings = "" : (_t2 :: [[Char]])
> strings !! 1
"a"
> :print strings
strings = "" : "a" :: (_t3 :: [[Char]])
> strings !! 2
"aa"
:print strings
strings = "" : "a" : "aa" : (_t4 :: [[Char]])
Each _tN
represents a pointer to the rest of the stream that hasn't been evaluated yet. You can also visualize it like this, where the _
represents the pointer to the rest of strings
strings
= "" : map ('a':) strings
^--------<------^
= "" : map ('a':) ("" : _)
^--------<-------^
= "" : ('a':"") : map ('a':) (_)
^--------<-------------^
= "" : "a" : map ('a':) ("a" : _)
^-------<---------^
= "" : "a" : ('a':"a") : map ('a':) (_)
^---------<-------------^
= "" : "a" : "aa" : map ('a':) ("aa" : _)
^--------<---------^
= "" : "a" : "aa" : ('a':"aa") : map ('a':) (_)
^----------<-------------^
= "" : "a" : "aa" : "aaa" : map ('a':) ("aaa" : _)
^---------<---------^
= "" : "a" : "aa" : "aaa" : ('a':"aaa") : map ('a':) (_)
^------------<------------^
= "" : "a" : "aa" : "aaa" : "aaaa" : map ('a':) ("aaaa" : _)
^---------<----------^
= ...
Hopefully this makes sense, each ^-<-^
shows what the _
is pointing at in each iteration (roughly)
Upvotes: 5
Reputation: 74354
These are both some really interesting "tying the knot" examples. You can understand them operationally by carefully following the evaluation, being sure to not evaluate too much ever. You can also understand them equationally. Sometimes the latter is easier
For instance, if we consider a slight variation on strings
strungs = map ('a':) strungs
we can think of the answer as being whatever value strungs
goes unchanged if you map ('a':)
over it. If we imagine strungs
is a list of strings (and it must be by the types) then performing map ('a':)
to that list adds a new 'a' to the front of each element.
Thus the only choice of strungs
that goes unchanged after such a map is if it's a list of any length where each element is the infinite string "aaaaa...".
Now strings
is a lot like strungs
, but it has another condition. Whatever strings
is, it has to be the same as "" : map ('a':) strings
. It's easy to see that we're playing a similar kind of game here as we did with strungs
, except that we keep adding new empty elements. Thus, strings
must look like
"", "a", "aa", "aaa", "aaaa", ...
because map ('a':) strings
looks like
"a", "aa", "aaa", "aaaa", "aaaaa", ...
and then prepending ""
makes it the same as what we started with
"", "a", "aa", "aaa", "aaaa", "aaaaa", ...
Upvotes: 7