7 ms·
Closures and Objects Are Equivalent (2013)
- Jtsummers 4y agoApparently only one past submission with comments: https://news.ycombinator.com/item?id=9363635 https://news.ycombinator.com/item?id=9363635 - April 12, 2015 (45 comments)
- dang 4y agoThanks! we can throw in a couple other related ones I suppose: Closures and Objects Are Equivalent - https://news.ycombinator.com/item?id=9363635 https://news.ycombinator.com/item?id=9363635 - April 2015 (45 comments) Objects vs closures - https://news.ycombinator.com/item?id=1982080 https://news.ycombinator.com/item?id=1982080 - Dec 2010 (17 comments) Objects are a poor man's closures (2003) - https://news.ycombinator.com/item?id=1241717 https://news.ycombinator.com/item?id=1241717 - April 2010 (38 comments) -- Edit: I put 2013 above since the page says "last edited 2013" - but most of this content must be much older...
- nanomonkey 4y agoClosure Oriented programming is described in Let Over Lambda[0], objects in Lisp. [0][https://letoverlambda.com/index.cl/guest/chap2.html https://letoverlambda.com/index.cl/guest/chap2.html]
- smitty1e 4y agoC2.com is the ur-wiki.
- User23 4y agoThe WikiWikiWeb indeed.
- juancn 4y agoIf you squint enough, all Turing complete concepts are equivalent to one another.
- Animats 4y agoOnly for LISP people. To C++ programmers, an object is a struct with associated functions. Then there's the confusion between lambdas and closures. They are not the same thing. Lambdas are simply anonymous functions, usually with funny syntax to make them easier to write in line. Closures are functions with data imported from an outer scope. Some languages separate the two. Python and Javascript have nested functions which support closures, using standard function syntax. Pascal and GCC C have nested functions which are not closures and cannot see outer dynamic scopes.
- saghm 4y ago> Then there's the confusion between lambdas and closures. They are not the same thing. Lambdas are simply anonymous functions, usually with funny syntax to make them easier to write in line. Closures are functions with data imported from an outer scope. Some languages separate the two. I remember a few years ago saying that I found the naming of anonymous functions in Rust "closures" rather than "lambdas" weird since most of the time they're used they don't close over anything. Someone responded by saying that all of them closed over something, it just sometimes was unit (the empty tuple type in Rust, essentially the equivalent of "void" when used as a return type). I didn't find it super compelling at the time, although over the years learning more about how much the ownership and borrowing system influenced the way that closures needed to be used in Rust (e.g. having separate traits for functions that close over shared borrows, mutable borrows, and owned values) has made me at least understand why the fact that they close over things is worth emphasizing.
- fulafel 4y agoAlso anonymous functions in programming languages didn't historically always or even usually have lexical scope (= be closures). Eg Lisp had lambda expressions for a long time (20 years? 30 years?) before lexical scoping made it in. Python also had its lambda and nested functions for a long time before lexical scopes came to the language. The namesake, lambda calculus, doesn't have it (I think?).
- tempodox 4y ago
- galaxyLogic 4y agoI would state it slightly differently: Closures are for FP what objects are for OOP. When you create a closure you are in fact creating an instance of a Function. In FP functions are "first class citizens", therefore it must be easy to create new (instances of) Functions. In FP you can do that by calling some code that creates a closure, not only by providing new source-code for a new Function-definition. Note Function Definition is not a function. What can you do with a closure? You can call it. What can you do with an object? You can not call it. You can only call one of its methods.
- Kamq 4y agoI believe the equivalent here means formally equivalent. But, really, you can use each of them to emulate the other. If you want to emulate objects with functions, you just have your function return a bag of closures over the state. Your first function is effectively a constructor. To emulate functions with objects, you just create a single method class. Or do something real fancy with Scala's apply and/or C++'s ClassName::operator()() so it looks like a normal call, if you are so inclined.
- galaxyLogic 4y ago> To emulate functions with objects, you just create a single method class. You still can not "call the object" you can only call its single method. But I see what you are saying I think. You can accomplish similar things with closures and objects. For them to be "equivalent" I think should mean there is nothing you can do with closures that you can not do with objects, and vise versa. I don't know if that is the case, is it?
- Kamq 4y agoThat's what the term "formally equivalent" means here. You can do whatever you can do with one of them, with the other one. Actors as well.
- galaxyLogic 4y ago
- DonHopkins 4y agoTuring equivalence is a hell of a drug.
- Labo333 4y agoReminds me of a university project I did 6 years ago: we had to compute a bunch of shortest paths in a large graph, during the computation of reach [^1]. There were a small number of sources but a large number of queries, that were not easily predictible in advance. The assignment was quite computation intensive and advised to use C++ or Java. I had a tradeoff to make on each source between computing a full Dijkstra's (distance to all other nodes) or multiple "lazy" Dijkstra's (stopping upon reaching the target node). Instead, I had a nice idea: what if I could continue computations at the last known Dijkstra's state? To implement it, I could either: - create an object, list all variables of my Dijkstra's and put them in a dict state - use an iterator that looks very much like the textbook Dijkstras's and use the `next()` Python method to pass queries, while the state variables AND the instruction pointer are stored in the closure This is a really good illustration that `next` makes closures "mutable" and "callable" as the link states. The resulting code of an "AWESOME ONLINE MEMOISED DIJKSTRA" as I wrote in the docstring back then is stupidly small and simple to read [^2]. It is also easy to call: `dijkstra_with_target(graph, source).send(target)`. In the end, my Python code (executed with Pypy) outperformed all C++ and Java implementations by an order of magnitude. I should write a blog post about this (and almost did here)! [^1]: https://www.irif.fr/~kosowski/INF421-2016/problem.html https://www.irif.fr/~kosowski/INF421-2016/problem.html [^2]: https://github.com/louisabraham/INF421-project/blob/master/src/Dijkstra.py#L53 https://github.com/louisabraham/INF421-project/blob/master/s...
- Labo333 4y agoI wrote the blog post: https://louisabraham.github.io/articles/generating-closures https://louisabraham.github.io/articles/generating-closures
- fulafel 4y agoThis smart working Python vs hard working low level languages anecdote becomes even better when you consider that it's using the pure-python heap queue implementation.
- kaba0 4y agoIt seems similar to the old-time joke of a monk-senior all-knower engineer and a junior, where the latter tries to find “The Truth” and comes back with The answer three times, OOP, FP and actors, and it turns out that they are all the same.
- rosebay 4y agoClosures don’t have inheritance or any of the other machinery that object oriented programming typically comes with. Perhaps saying they are light weight objects (where objects has its meaning as instances of classes) is a good way of saying it. The realisation that closures and objects are very similar is one of the main turning points in becoming proficient in both OOP and Functional Programming, it was for me at least.
- jasonwatkinspdx 4y agoDelegation can model inheritance or other OOP semantics. If you squint your eyes delegation and lexical scope nesting kinda resemble each other.
- _old_dude_ 4y agoThis is especially true in JavaScript. Inheritance between classes is implemented using delegation and the global scope is dynamic.
- zozbot234 4y agoNot quite. Inheritance needs a "tying the knot" construction, because a base-class method can call a method that may have been overridden in a derived class. So you need an indirection step for any call to an overridable method, which cannot be modeled by simple delegation. Of course this is really a misfeature (outside of pure abstract base classes that, like interfaces or traits, don't implement their defined methods), which is at the root of the well-known "fragile base class" problem. It means implementation-inheritance is inherently non-modular, even though literally everything else in OOP was specifically intended to enable and promote modularity.
- jcelerier 4y agoDepends on which language, in c++ you can inherit from the type of a closure as it's just another object type
- int_19h 4y agoNot all OO languages even have inheritance. I would argue that the only two features that are mandatory to call something "object-oriented" is 1) the notion of object identity, and 2) some kind of dynamic method dispatch.
- chadlavi 4y agoNice content but what in the heck is up with all the camel case links?
- layer8 4y agoThat’s how the first wikis worked. You didn’t explicitly mark links to other pages. Instead, camel-case words were links by definition, and page titles accordingly were camel-case words. See https://wiki.c2.com/?JoinCapitalizedWords https://wiki.c2.com/?JoinCapitalizedWords.
- chadlavi 4y agoHuh neat, thank you for the explanation
- carapace 4y agoJust like hammers and mallets are equivalent, yeah? - - - - Recursion and loops are equivalent. They both just compile to branches with negative offsets, eh? - - - - If you take enough LSD everything is equivalent to everything else.