Live data from Hacker News

A case against currying

emi-h.com

71–80 of 135 posts

Re: A case against currying

#71
post #56

I couldn't agree more. Having spent a lot of time with a language with currying like this recently, it seems very obviously a misfeature. 1. Looking at a function call, you can't tell if it's returning data, or a function from some unknown number of arguments to data, without carefully examining both its declaration and its call site 2. Writing a function call, you can accidentally get a function rather than data if…

> in what sense is make_string_comparator_for_locale "really" a function which takes a locale and a string and returns a function from string to ordering?

In the sense that "make_string_comparator" is not a useful concept. Being able to make a "string comparator" is inherently a function of being able to compare strings, and carving out a bespoke concept for some variation of this universal idea adds complexity that is neither necessary nor particularly useful. At the extreme, that's how you end up with Enterprise-style OO codebases full of useless nouns like "FooAdapter" and "BarFactory".

The alternative is to have a consistent, systematic way to turn verbs into nouns. In English we have gerunds. I don't have to say "the sport where you ski" and "the activity where you write", I can just say "skiing" and "writing". In functional programming we have lambdas. On top of that, curried functions are just a sort of convenient contraction to make the common case smoother. And hey, maybe the contraction isn't worth the learning curve or usability edge-cases, but the function it's serving is still important!

> Because of point 3, our codebase has a trivial wrapper to put round functions when your function actually returns a function

That seems either completely self-inflicted, or a limitation of whatever language you're using. I've worked on a number of codebases in Haskell, OCaml and a couple of Lisps, and I have never seen or wanted anything remotely like this.

Re: A case against currying

#72
post #65

I completely agree with the points in this article and have come to the same conclusion after using languages that default to unary curried functions. > I'd also love to hear if you know any (dis)advantages of curried functions other than the ones mentioned. I think it fundamentally boils down to the curried style being _implicit_ partial application, whereas a syntax for partial application is _explicit_. And as if…

The functional programming take is that “the result of foobinade-ing an and b” IS “foobinade applied to two of its arguments”. The application is not some syntactic pun or homonym that can refer to two different meanings—those are the same meaning.

Let us postulate two functions. One is named foobinade, and it takes three arguments. The other is named foobinadd, and it only takes two arguments. (Yes, I know, shoot anybody who actually names things that way.)

When someone writes

  f = foobinade a b
  g = foobinadd c d
there is no confusion to the compiler. The problem is the reader. Unless you have the signatures of foobinade and foobinadd memorized, you have no way to tell that f is a curried function and g is an actual result.

Whereas with explicit syntax, the parentheses say what the author thinks they're doing, and the compiler will yell at them if they get it wrong.

Re: A case against currying

#73
post #23

I agree with this article. Tuples nicely unified multiple return values and multiple parameters. FWIW Scala and Virgil both support the _ syntax for the placeholder in a partial application. def add(x: int, y: int) -> int { return x + y; } def add3 = add(_, 3); Or more simply, reusing some built-in functions: def add3 = int.+(_, 3);

As noted in the article:

> This feature does have some limitations, for instance when we have multiple nested function calls, but in those cases an explicit lambda expression is always still possible.

I've also complained about that a while ago https://news.ycombinator.com/item?id=35707689

---

The solution is to delimit the level of expression the underscore (or dollar sign suggested in the article) belongs to. In Kotlin they use braces and `it`.

    { add(it, 3) } // Kotiln
    add(_, 3) // Scala
Then modifying the "hole in the expression" is easy. Suppose we want to subtract the first argument by 2 before passing that to `add`:

    { add(subtract(it, 2), 3) } // Kotlin
    // add(subtract(_, 2), 3) // no, this means adding 3 to the function `add(subtract(_, 2)`
    x => { add(subtract(x, 2), 3) } // Scala

Re: A case against currying

#74
post #52
post #41

One "feature of currying" in Haskell that isn't mentioned in the fine article is that parts of the function may not be dependent on the last argument(s) and only needs to be evaluated once over many application of the last argument(s) which can be very useful when partially applied functions are passed to higher-order functions. Functions can be done explicitly written to do this or it can be achieved through compile…

That's a very good point, I never thought really about how this relates to the execution model & graph reduction and such. Do you have an example of a function where this can make a difference? I might add something to the article about it. It's also a question of whether this is exclusive to a curried definition or if such an optimization may also apply to partial application with a special operator like in the arti…

An example where this is useful is to help inline otherwise recursive functions, by writing the function to take some useful parameters first, then return a recursive function which takes the remaining parameters. This allows the function to be partially in-lined, resulting in better performance due to the specialization on the first parameters. For example, foldr:

foldr f z = go

  where

    go [] = z

    go (x : xs) = f x (go xs)
when called with (+) and 0 can be inlined to

go xs = case xs of

    [] -> 0

    (x : xs) = x + go xs
which doesn't have to create a closure to pass around the function and zero value, and can subsequently inline (+), etc.

Re: A case against currying

#75
post #35

Earlier quoted context omitted.

But it is about code syntax. Languages like Haskell make it part of the language by only supporting single-argument functions. So currying is the default behaviour for programmers. I think you are focusing on the theoretical aspect of partial application and missing the actual argument of the article which having it be the default, implicit way of defining and calling functions isn't a good programming interface.

Similar to how lambda calculus "just is" (and it's very elegant and useful for math proofs), but nobody writes non-trivial programs in it...

Make that almost nobody.

I wrote a non-trivial lambda program [1] which enumerates proofs in the Calculus of Constructions to demonstrate [2] that BBλ(1850) > Loader's Number.

[1] https://github.com/tromp/AIT/blob/master/fast_growing_and_co...

[2] https://codegolf.stackexchange.com/questions/176966/golf-a-n...

Re: A case against currying

#76
post #21

Prior to this article, I didn't think of currying as being something a person could be "for" or "against." It just is. The fact that a function of multiple inputs can be equivalently thought of as a function of a tuple can be equivalently thought of as a composite of single-input functions that return functions is about cognition, and understanding structure, not code syntax.

I'm a programmer, not a computer scientist. The equivalence is a computer science thing. They are logically equivalent in theoretical computer science. Fine.

They are not equally easy for me to use when I'm writing a program. So from a software engineering perspective, they are very much not the same.

Re: A case against currying

#77
post #12
post #8

Earlier quoted context omitted.

They are isomorphic in the strong sense that their logical interpretations are identical. Applying Curry-Howard, a function type is an implication, so a curried function with type A -> B -> C is equivalent to an implication that says "If A, then if B, then C." Likewise, a tuple is a conjunction, so a non-curried function with type (A, B) -> C is equivalent to the logic statement (A /\ B) -> C, i.e., "If A and B then…

Please see my other comment below, and maybe re-read the article. I'm not asking what the difference is between curried and non-curried. The article draws a three way distinction, while I'm asking why two of them should be considered distinct, and not the pair you're referring to.

Apologies, I was focused on the usual pairing in this space and not the more subtle one you're talking about. As others have pointed out, there isn't really semantic a difference between the two. Both approaches to function parameters produce the same effect. The differences are purely in "implementation," either theoretically or in terms of systems-building.

From a theoretical perspective, a tuple expresses the idea of "many things" and a multi-argument parameter list expresses the idea of both "many things" and "function arguments." Thus, from a cleanliness perspective for your definitions, you may want to separate the two, i.e., require function have exactly one argument and then pass a tuple when multiple arguments are required. This theoretical cleanliness does result in concrete gains: writing down a formalism for single-argument functions is decidedly cleaner (in my opinion) than multi-argument functions and implementing a basic interpreter off of this formalism is, subsequently, easier.

From a systems perspective, there is a clear downside in this space. If tuples exist on the heap (as they do for most functional languages), you induce a heap allocation when you want to pass multiple arguments! This pitfall is evident with the semi-common beginner's mistake with OCaml algebraic datatype definitions where the programmer inadvertently wraps the constructor type with parentheses, thereby specifying a constructor of one-argument that is a tuple instead of a multi-argument constructor (see https://stackoverflow.com/questions/67079629/is-a-multiple-a... for more details).

Re: A case against currying

#78
post #23

I agree with this article. Tuples nicely unified multiple return values and multiple parameters. FWIW Scala and Virgil both support the _ syntax for the placeholder in a partial application. def add(x: int, y: int) -> int { return x + y; } def add3 = add(_, 3); Or more simply, reusing some built-in functions: def add3 = int.+(_, 3);

As noted in the article: > This feature does have some limitations, for instance when we have multiple nested function calls, but in those cases an explicit lambda expression is always still possible. I've also complained about that a while ago https://news.ycombinator.com/item?id=35707689 --- The solution is to delimit the level of expression the underscore (or dollar sign suggested in the article) belongs to. In Ko…

I think I like the explicit lambda better; I prefer to be judicious with syntactic sugar and special variable names.

    fun x => add(subtract(x, 2), 3) // Virgil

Re: A case against currying

#79
post #59
post #55

Earlier quoted context omitted.

One slightly contrived example would be if you had a function that returned the point of a set closest to another given point. getClosest :: Set Point -> Point -> Point You could imagine getClosest build a quadtree internally and that tree wouldn't depend on the second argument. I say slightly contrived because I would probably prefer to make the tree explicit if this was important. Another example would be if you we…

I think we need to see a few non-contrived examples, because i think in every case where you might take advantage of currying like this, you actually want to make it explicit, as you say. The flip side of your example is that people see a function signature like getClosest, and think it's fine to call it many times with a set and a point, and now you're building a fresh quadtree on each call. Making the staging expli…

> and now you're building a fresh quadtree on each call [...] Making the staging explicit steers them away from this.

Irrespective of currying, this is a really interesting point - that the structure of an API should reflect its runtime resource requirements.

Re: A case against currying

#80
post #65

Earlier quoted context omitted.

The functional programming take is that “the result of foobinade-ing an and b” IS “foobinade applied to two of its arguments”. The application is not some syntactic pun or homonym that can refer to two different meanings—those are the same meaning.

Let us postulate two functions. One is named foobinade, and it takes three arguments. The other is named foobinadd, and it only takes two arguments. (Yes, I know, shoot anybody who actually names things that way.) When someone writes f = foobinade a b g = foobinadd c d there is no confusion to the compiler. The problem is the reader . Unless you have the signatures of foobinade and foobinadd memorized, you have no wa…

> Unless you have the signatures of foobinade and foobinadd memorized, you have no way to tell that f is a curried function and g is an actual result.

Yes, but the exact FP idea here is that this distinction is meaningless; that curried functions are "actual results". Or rather, you never have a result that isn't a function; `0` and `lambda: 0` (in Python syntax) are the same thing.

It does, of course, turn out that for many people this isn't a natural way of thinking about things.

Post reply on HN