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…
A case against currying
111–120 of 135 posts
Re: A case against currying
#112Re: A case against currying
#113I 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
#114Earlier 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…
Re: A case against currying
#115Earlier 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.
Re: A case against currying
#116Earlier 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."
Re: A case against currying
#117Currying 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…
(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
#118Earlier 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.
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
#119What's the steelman argument though? Why do languages like Haskell have currying? I feel like that is not set out clearly in the argument.
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!