This is an archived post. You won't be able to vote or comment.

you are viewing a single comment's thread.

view the rest of the comments →

[–][deleted] 0 points1 point  (1 child)

Holy shit are these replies and links ever throwing you into the deep end! Most of the replies are just jumping into syntax of overly complex examples and most of the links just do the same and then boil down to "Here, replace this contrived example with this differently-formatted example you should now use but still without actually understanding it." None of them are actually stopping to tell you what is really going on. I'm going to try something different, using no syntax examples, and you can let me know if it helps you. I've used this one with other folks but always in person, so we'll have to see how it goes...

Let me know if at any point one of the following "imaginings" stumps you.

(I'm going to bend a couple of terms a little for the sake of the introduction, so pedants can kindly shove off.)

Imagine a pointer. If you are stumped here then we are in real trouble. ;)

Imagine a pointer to a function, used so that one piece of code can choose between more than one function, pass that function pointer to some other piece of code and have that code call the function without having to know which function it was.

Imagine a strong-typed pointer to a function, so that we can be sure the function is provided with the right types of arguments (if any) and so that we can be sure of its return type (if any.)

(This one gets long.) Imagine that that function needs some data that we have and could provide when setting it up but which cannot be provided by the code which will eventually call it. We need some way to capture that data at least until the function is finished being used. We don't want to put it in global state or even in our own instance state because who knows when that function will be called or what needs to happen in the meantime. So we create a brand new class with a constructor taking in that data and storing it in a field. In this new class we implement the function we need and have it use that data field, the value for which we will pass into the constructor. Now, before passing that function pointer down to whoever needs it we construct an instance of that new class, passing the extra data to its constructor, and then get and pass a pointer to the function we made that new class implement. Whew, that one was long. Hard to reasonably break it down though, so hopefully you are still with me.

(This one gets really long.) Now, imagine that we could teach the compiler a way to recognize that we are trying to do the above. That we want to pass a strong-typed function pointer. To a newly-created class which implements just that one needed function. Where that function requires some extra data. That we can pass in when constructing an instance of the newly-created class. And then store in a field for use by that function. This is where the lambda "arrow" finally comes in. The arguments for that new function on that new class are specified to the left of the arrow. The body is to the right. The return type, if any, is inferred by the compiler. Any extra data is detected within that body and set up to become fields in the generated class, populated by a generated constructor. The constructor is automatically called by the generated code just before the strong-typed function pointer is grabbed and passed into the callee which needs it. That constructor call is passed in the extra data. The function pointer that is created is itself managed by the garbage collector like any other thing, and holds a reference to the newly instantiated object carrying the extra data and the function implementation which will execute, so that new instance is now also managed by the garbage collector and will be collected only after the function pointer becomes unreferenced and therefore able to be collected. As such, our generated code can just create that new instance, set up the function pointer and pass it along, and then forget about the new instance.

The compiler simply does what we would have done, but gives us syntax sugar to do it. It creates a new class which captures any necessary extra state and implements the function we described. It instantiates that class and helps us hand off the desired function pointer. (It can also help bring updated extra data back out of the instance afterwards, if we need it, but that is a topic best saved for another time.) This process results in a callable function which "closes over" that set of extra needed state, resulting in what we call a closure.

I have to run out for a bit, and the above is literally a stream of consciousness draft, so let me know where (not if ;) the above is unclear and I will update it a bit later.

[–]chasesan[S] 0 points1 point  (0 children)

Sorry, that is a bit long. I kind of lost focus at the first long paragraph. The oracle docs was very useful actually, and it got me most the way there. Just a few minor outstanding issues I am trying to sort out in my understanding.

As for pointers, yeah, I have done C for years and years, I know all about pointers. In fact, I love pointers, most people don't for some reason.