6 ms·
Any discussion on the utility of currying (i.e. closures) vs constructing an object with methods always reminds me of this parable: ``` The venerable master Q
by SPBS 5y ago
Any discussion on the utility of currying (i.e. closures) vs constructing an object with methods always reminds me of this parable:
```
The venerable master Qc Na was walking with his student, Anton. Hoping to prompt the master into a discussion, Anton said "Master, I have heard that objects are a very good thing - is this true?" Qc Na looked pityingly at his student and replied, "Foolish pupil - objects are merely a poor man's closures."
Chastised, Anton took his leave from his master and returned to his cell, intent on studying closures. He carefully read the entire "Lambda: The Ultimate..." series of papers and its cousins, and implemented a small Scheme interpreter with a closure-based object system. He learned much, and looked forward to informing his master of his progress.
On his next walk with Qc Na, Anton attempted to impress his master by saying "Master, I have diligently studied the matter, and now understand that objects are truly a poor man's closures." Qc Na responded by hitting Anton with his stick, saying "When will you learn? Closures are a poor man's object." At that moment, Anton became enlightened.
```
Currying (and closures) are great when you don't want to go through the overhead of constructing an entire object just to hold state. They're lightweight pseudo-objects. But the moment the number of variables you are closing over starts getting too big, it is better to formalize it into an object instead. That's how I interpret this story.
- isaacimagine 5y agoClosures and objects are both ways to pair code and data — I don't think the lesson is entirely about switching to objects when a closure becomes to large; personally, I find it to be an invitation to discover how and why code and data are paired together in the first place. Why do closures and objects arise in such a natural fashion? I'm not entirely sure, and I don't think there's one right answer. All I can think is that I can't imagine the depth of Alan Kay's knowledge on the subject, given he pioneered scheme and smalltalk, two languages that define the core of the closure/object paradigm.
- fendy3002 5y ago> Why do closures and objects arise in such a natural fashion My reasoning is we need a partitioned stateful function which is impossible to achieve and the alternative are worse: - partition global state with your own object id - passing all state as arguments
- isaacimagine 5y agoOther ways to manage state as well: - Managing state through a monad or effect system - Managing state through coroutines / continuations - Managing state through dynamic scoping (poor man's effect system) - Associating implicitly mutating functions with types without having full objects (Rust) Some of these are pretty powerful, especially effects and continuations. I think the reason we don't see more of them is because we haven't figured out a wholly ergonomic way to fit them into a language yet.
- goldenkey 5y agoIndeed but we can get around closures by using first class operators like bind. This implementation I wrote is much more elegant and performant than the O(n) stack frame crud in that article: const curry = fn => { const curried = (...a) => a.length >= fn.length ? fn(...a) : curried.bind(undefined, ...a) return curried } In fact, I can't think of any implementation in JS that would be more performant, using any paradigm, functional, object oriented, or imperative. Try to best it if you can. I'm curious to see what HN can come up with ;-)
- masklinn 5y agoThat's not currying, that's partial application (except bad because it forces the evaluation of the function if all parameters are filled, which is often undesirable as javascript is an eagerly evaluated language with side-effects).
- goldenkey 5y agoThe implementation in the article is technically partial application. I was just reproducing it. But you are correct. Currying is a subset of partial application. You can do currying through partial application. The function is eventually called when the correct number of parameters has finally been supplied. Currying lets you call a function, splitting it in multiple calls, providing one argument per-call. Partial Application lets you call a function, splitting it in multiple calls, providing multiple arguments per-call. The only difference is that currying would ignore any additional parameters in a flexible language like JS. It's easy to make this implementation only consider the first parameter. See below. const curry = fn => { const curried = (a,x) => a.length + 1 >= fn.length ? fn(...a, x) : curried.bind(undefined, [...a, x]) return curried.bind(undefined, []) } I still don't understand your point about eager execution. The function is supposed to be executed when enough parameters have been passed. I suppose you mean that JS does not check if the call arguments length equals the function signature, so strict currying is safer to avoid errors? If so, that makes sense. I tend to consider that caveat of JS a feature though, which is why partial application is what I'd use to accomplish currying. It subsumes it and provides more flexibility by allowing multiple arguments to be curried in a single call.
- discreteevent 5y agoSlightly off topic, but later on in that Qc Na thread: ---------- This brought to mind my introduction to OOP by a talented but notoriously inarticulate programmer: NIP: Do you know what OOP is? Me: No. NIP: Ah. Well, you have objects and methods. Objects: objects aren't anything. And methods are how you talk to objects. Okay? Me: Sure. I knew there was no point in asking questions -- this was the man at his most expository. I took him to mean, "Forget what objects are, just work with them." ------------- Which I take to mean "Objects are black boxes. How they behave is what they are"
- srcreigh 5y agoIf you don't mind using mutation for private state in a functional language, objects and closures are kinda the exact same thing. (define (make-queue) (define data empty) (lambda (cmd [arg #f]) (match cmd ['enqueue (set! data (append data (list arg)))] ['dequeue (define value (first data) (set! data (rest data)) value]))) Don't see the connection yet? Let me try again. (define (make-queue) (define data empty) (define (instance cmd [arg #f]) (match cmd ['enqueue (set! data (append data (list arg)))] ['dequeue (define value (first data) (set! data (rest data)) value])) instance)
- zwarag 5y agoWho is this Qc Na master? Is that a story from a book or a legend from a forum board?
- detaro 5y agoThere's a bit of a tradition of writing such koans for software topics. This one is from here: http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/msg03277.html http://people.csail.mit.edu/gregs/ll1-discuss-archive-html/m...
- aliswe 5y agothere is another one, similar to this, but I believe the pupil is called grasshopper. ring any bells? ive been looking for it ...