Hacker News new | past | comments | ask | show | jobs | submit login

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.




Closures 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.


> 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


Other 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.


If 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)


Slightly 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"


Who is this Qc Na master? Is that a story from a book or a legend from a forum board?


There'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/m...


there is another one, similar to this, but I believe the pupil is called grasshopper. ring any bells? ive been looking for it ...


Indeed 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 ;-)


That'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).


The 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.


> I still don't understand your point about eager execution. The function is supposed to be executed when enough parameters have been passed.

Which makes perfect sense for languages which are mostly side-effect free and in which functions with no parameters do not make sense.

That's not the case of Javascript, partially applying all the parameters but not wanting to actually run the function is a useful thing to desire, and not doing that makes partial application incoherent, especially as e.g. default parameters get involved.

That is why currying is a bad fit for imperative languages, but partial application is a useful tool still.


You're not making any sense to me. Neither currying nor partial application delay running of the function once all parameters have been passed. There is no eager execution happening in the implementation I provided. I believe you misunderstand when the original function is supposed to be executed.

sum = curry((a,b,c) => a+b+c)

sum(1)(2)(3) equals 6 under currying.

sum(1,2)(3) equals 6 under partial application.

Perhaps you are thinking of calling the function with no arguments as a signal to execute the final calculation. That is NOT how currying normally works.

I believe you think currying works like this:

sum(1)(2)(3)() = 6

This is NOT standard currying. There is never a need to give an empty call to signal execution in standard currying as CS and Mathematics define it.

The implementation I provided is defacto currying by the book. Perhaps you can provide an example if we are misunderstanding eachother.


> Neither currying nor partial application delay running of the function once all parameters have been passed.

Currying does not, partial application can (and should).

> sum = curry((a,b,c) => a+b+c)

> sum(1)(2)(3) equals 6 under currying.

> sum(1,2)(3) equals 6 under partial application.

Sure. Now what happens to `sum(1, 2, 3)`? Or if the function is `(a, b, c=1) => a + b + c`?

> Perhaps you are thinking of calling the function with no arguments as a signal to execute the final calculation. That is NOT how currying normally works.

Which is the point. It's not how currying works. It is, however, how a "regular" version of partial application works e.g. Javascript's Function#bind or Python's `functools.partial`.

> I believe you are confused.

Nope.

> I believe you think currying works like this:

No.

> The implementation I provided is defacto currying by the book.

"Currying by the book" does is defined for strict arities and can not generate variable-arity functions.


Thank you for helping me understand the issue with default parameter values in a variadic language like JS, which probably makes a nonstandard currying with an execution signal more applicable.

I've created a curry which will only execute when called with no arguments, ie. sum(1)(2)(3)() = 6

This is like you mentioned, a much better suited technique of currying for a language like JS:

  const curry = fn => {
    const curried = (a,...x) => x.length === 0 ? fn(...a) : curried.bind(undefined, [...a, x[0]])
    return curried.bind(undefined, [])
  }

  // equals 15 because default c=10
  curry((a,b,c=10)=>a+b+c)(2)(3)()

We can also apply the same idea of an empty call as a signal to the multi-argument currying variation. I'd love to know what you think. And please keep reading. Because I apologize and stand corrected. Apparently partial application is single use, unlike currying. Essentially a fixed point. I have learned something today. I apologize for being so hard headed. Thank you for putting up with me and correcting me. The wikipedia article on currying confused me with regard to partial application but then I read the partial application wiki article and what you are saying clicked.

It seems like there are 3 techniques here.

Multi argument currying. Single argument currying. And partial application (aka binding)

And too many references called the former the latter, hence my confusion.


It's really no worries. At the end of the day, the issue is really that mathematical / functional / "traditional" currying is not really suited to imperative languages, which is most of them, and notions of "advanced" currying are — I think — throwing good money after bad.

Partial application is very useful (and you're correct that it's "one shot", though you can obviously layer partial applications by partially applying the result), so can the odd curried version of a library function (ideally provided by the library) be, but general-purpose currying… not so much I would say: the languages don't care for it (unlike curried language), it interacts badly with complicated parameterisation (varargs, default, keyword, …), and the almost entirely of the use cases is covered by straight partial application. What little is left can simply be curried by hand.




Consider applying for YC's W25 batch! Applications are open till Nov 12.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: