Live data from Hacker News

A case against currying

emi-h.com

111–120 of 135 posts

Re: A case against currying

#111
post #109

I also think there’s an interesting effect when cool functional language features like currying and closures are adopted by imperative languages. They make it way too easy to create state in a way that makes you FEEL like you’re writing beautiful pure functions. Of course, in a functional language everything IS pure and this is just how things work. But in an imperative language you can trick yourself into thinking y…

I'm actually fascinated by what you wrote. Why was it a dark time?

Re: A case against currying

#113
post #111
post #109

I also think there’s an interesting effect when cool functional language features like currying and closures are adopted by imperative languages. They make it way too easy to create state in a way that makes you FEEL like you’re writing beautiful pure functions. Of course, in a functional language everything IS pure and this is just how things work. But in an imperative language you can trick yourself into thinking y…

I'm actually fascinated by what you wrote. Why was it a dark time?

No encapsulation… huge functions with tons of local variables shared between closures… essentially global state in practice. I think ant the time, objects with member variables felt “heavy” and local variables felt “light”. But the fact that they were so lightweight just gave me more opportunities to squirrel away state into random places with no structure around it. It really wasn’t all that horrific, and it helped me ship something quickly, but it wasn’t maintainable. These days I think the “heavy boilerplate” of grouping stuff into structs and objects forces me to slow down and think a bit harder about whether I really want to enshrine a new piece of state into the app’s data model. Most of the time I don’t.

Re: A case against currying

#114
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…

[dead]

Re: A case against currying

#115
post #78

Earlier quoted context omitted.

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

Coming from Scala to Kotlin, this is what I thought as well. Seeing `it` felt very wrong, then I got used to it.

I don't mind adopting features from popular languages. (After all, Virgil using _ for partial application turned out to be a happy accident that aligned with Scala.) Adopting features that are popping up in other languages helps to reduce the explanation burden, but I'm not sure on this one. It took me about 10 years to finally settle on having `fun` as a keyword to introduce lambdas instead of the parser back-tracking madness that JS parsers do.

Re: A case against currying

#116

Earlier quoted context omitted.

> Naming the variables can help readability It can, or it can't; depending on the situation. Sometimes it just adds weight to the mental model (because now there's another variable in scope).

Sure, I like chained method calls too, for simple things. But it gets ridiculous sometimes where people write a ten-stage pipeline in a single expression and then call that "readable."

I'm with you 100%. The main thing is that sometimes a "break point" (using a variable rather than _more_ chain) can help readability. And sometimes it makes things worse. It's really a case-by-case type of thing.

Re: A case against currying

#117

Currying was recently removed from Coalton: https://coalton-lang.github.io/20260312-coalton0p2/#fixed-ar...

> Simplicity: Every function takes exactly one input and produces exactly one output. No exceptions. If you didn’t care about the input or output, you used Unit, and we made special syntax for that. Seems like a disaster to use s-expressions for a language like that. I love s-expressions but they only make sense for variadic languages. The entire point of them is to quickly delimit how many arguments are passed. In s…

S-expressions are more like the tupled argument form, but better.

    (f x y z)
Is equivalent to:

    (f . (x . (y . (z . ())))
Every function takes one argument - a list.

Lists make partial application simpler than with tuples (at least Haskell style tuples), because we don't need to define a new form for each N-sized tuple. Eg, in Haskell you'd need:

    partial2 : (((a, b) -> z), a) -> (b -> z)
    partial3 : (((a, b, c) -> z), a) -> ((b, c) -> z)
    partial4 : (((a, b, c, d) -> z), a) -> ((b, c, d) -> z)
    ...
With S-expressions, we can just define a partial application which takes the first argument (the car of the original parameter list) and returns a new function taking a variadic number of arguments (the cdr of the original parameter list). Eg, using a Kernel operative:

    ($define! $partial
        ($vau (f first) env
            ($lambda rest
                (eval (list* f first rest) env))))

    ($define! f ($lambda (x y z) (+ x y z)))
    (f 3 2 1)
    => 6
    
    ($define! g ($partial f 3))
    (g 2 1)
    => 6
    
    ($define! h ($partial g 2))
    (h 1)
    => 6

    ($define! i ($partial h 1))
    (i)
    => 6
We could perhaps achieve the equivalent in Haskell explicitly with a multi-parameter typeclass and a functional dependency. Something like:

    class Partial full first rest | full first -> rest where
        partial :: (full -> z, first) -> (rest -> z)
        
    instance Partial ((a,b)) a b where
        partial (f, a) = \b -> f (a, b)
        
    instance Partial ((a, b, c)) a ((b, c)) where
        partial (f, a) = \(b, c) -> f (a, b, c)
        
    instance Partial ((a, b, c, d)) a ((b, c, d)) where
        partial (f, a) = \(b, c, d) -> f (a, b, c, d)

    ...

Re: A case against currying

#118
post #80

Earlier quoted context omitted.

> 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…

If 0 and a function that always returns 0 are the same thing, does that make `lambda: lambda: 0` also the same? I suppose it must do, otherwise `0` and `lambda: 0` were not truly the same.

In a non-strict language without side-effects, having a function with no arguments does not make sense. Haskell doesn't even let you do that.

You can write a function that takes a single throw-away argument (eg 0 vs \ () -> 0) and, while the two have some slight differences at runtime, they're so close in practice that you almost never write functions taking a () argument in Haskell. (Which is very different from OCaml!)

Re: A case against currying

#119
post #86

What's the steelman argument though? Why do languages like Haskell have currying? I feel like that is not set out clearly in the argument.

The practical upside is that it makes using higher-order functions much smoother, with less distracting visual noise.

In Haskell this comes up all over the place. It's somewhat nice for "basic" cases (`map (encode UTF8) lines` vs `map (\ line -> encode UTF8 line) lines`) and, especially, for more involved examples with operators: `encode readEncoding env readStdin` vs, well, I don't even know what...)

You could replace the latter uses with some alternative or special syntax that covered the most common cases, like replacing monads with an effect system that used direct syntax, but that would be a lot less flexible and extensible. Libraries would not be able to define their own higher-order operations that did not fit into the effect system without incurring a lot of syntactic overhead, which would make higher-order abstractions and embedded DSLs much harder to use. The only way I can think of for recovering a similar level of expressiveness would be to have a good macro system. That might actually be a better alternative, but it has its own costs and downsides!

Post reply on HN