Hi. I'm currently at https://www.braveclojure.com/core-functions-in-depth/#Infinite_Sequences_ and having a bit of a hard time understanding that "mind bending" function. Does anybody know of a complementary article/video or whatever that could help me?
Tried reading the sources for take
, lazy-seq
, repeat
, broke the function removing the call to lazy-seq
, tried searching http://clojure.org for lazyness and lazy but so far I still don't get it. I accept it works and I can use it, sure, but I would like to have the workings forcibly "realized" by my brain 🙂
For instance, in the example given:
(take 8 (even-numbers))
Why does it return the 0
in the output? Where/why does it loop? Where is the state stored? Conceptually I know its inside a closure but the border is blurry in my mind.Are you familiar with linked lists?
More or less, this chapter kind of introduces the concept when explaining the implementation of the sequence abstraction.
But yes, I think I get it.
Ok, that likely good enough. So if you want to traverse a linked list, you want to “get” each item in the list. Each item will be a pair of values: the “head” and the “rest”, where the “rest” is another linked list.
OK
then you do whatever you want with the head, and get the next item from “rest”
So imagine we have a function called GET that returns “head” and “rest”,
We use “GET” to get the head and the rest, do something with head, and then loop back to call “GET” on the “rest”
Oh, I see, it returns the 0
because in the source it calls (cons (first s) (take (dec n) (rest s))))...
in the source for take
, that is
Yeah
oh, so in that second call to take
using the rest, it decreases the number passed, so that is where the loop happens... I guess?
Well, let’s step through it one at a time
OK
First value: take
calls (even-numbers 0)
. That’s going to give it a list with two items in it, the value 0
, and a function. (It’s a lazy function , but just think of it as a function for now)
so far so good
So take
has a “head” and a “rest”, and it knows that the “head” is the first item it’s going to return, so it cranks out 0
as its first item.
Now, the “rest” should normally be a list, but in this case it’s a function — a lazy function.
Lazy functions are a special case, so take
knows that to get the actual list, it needs to call the function.
In this case, the function is (even-numbers (+ n 2))
which is a closure, like you said.
OK
So it closed over the 0
, and that makes it (even-numbers (+ 0 2))
or (even-numbers 2)
OK
So take
calls (even-numbers 2)
and gets back another list: (2, (even-numbers (+ 2 2)))
So the “head” is 2
, and take
peels that off and cranks that out as its second item.
Perfect. I get this now. 🙂
That leaves the function as the “rest”, and again, it’s a lazy function, so it just keeps doing that over and over, for as many values as it needs.
Cool 🙂
> Lazy functions are a special case, so take
knows that to get the actual list, it needs to call the function.
Heh, I’m kind of hand-waving there — not 100% sure what the internals are doing to make that happen.
Magic ✨
Thank you so much for taking the time to help me. At least how that particular instance works is clear now 🙂
The basic idea is that instead of what you might call a physical list like (1 2 3 4)
, you get a pair with 1 value + a factory for making the next “value + factory” pair, and every time you take the value, you run the factory.
Yes, excellent.
Or rather, the factory runs automatically, but you get the idea.
It's clear now. 🙂 It's funny how things just snap into place.
Yeah, I struggled with it for quite a while before it finally clicked.
I guess a source of confusion with how it is explained in the book is that he defines even-numbers
as a multi-arity function and ends up not using it.. so I was looking at that cons
and the call to lazy-seq
and lost it..
I guess he does use it.. haha
Yeah, the 0-arity is just to seed the 1-arity version.
I'm trying to write it with 1-arity and I think I'm missing something on the need for a multi-arity function in this case..
Hmm, maybe not. It just would be the case that you would not be able to call it without any arguments. OK.
Exactly.
Awesome 😄
Thanks again!
:thumbsup: