Live data from Hacker News

A case against currying

emi-h.com

91–100 of 135 posts

Re: A case against currying

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

> Yes, but the exact FP idea here is that this distinction is meaningless; that curried functions are "actual results". Everyone knows that. At least everyone who would click a post titled "A case against currying." The article's author clearly knows that too. That's not the point. The point is that this distinction is very meaningful in practice, as many functions are only meant to be used in one way. It's extremely…

> It's extremely rare that

Not when a language embraces currying fully and then you find that it’s used all the fucking time.

It’s really simple as that: a language makes the currying syntax easy, and programmers use it all the time; a language disallows currying or makes the currying syntax unwieldy, and programmers avoid it.

Re: A case against currying

#92

> curried functions often don't compose nicely Same for imperative languages with "parameter list" style. In python, with def f(a, b): return c, d def g(k, l): return m, n you can't do f(g(1,2)) but have to use f(*g(1,2)) what is analogical to uncurry, but operate on value rather than function. TBH I can't name a language where such f(g(1,2)) would work.

perl, though that uses lists rather than multiple value or fixed-size tuples:

  #!/usr/bin/env perl
  use v5.36;
  
  sub f($a, $b) {
    return ($a+1, $b+1);
  }
  
  sub g($k, $l) {
    return ($k+1, $l+1);
  }
  
  say for f(g(1,2));
prints out

  3
  4

Re: A case against currying

#93
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.

Another way to make the point: when you write 0, which do you mean?

In a pure language like Haskell, 0-ary functions constants

Re: A case against currying

#95

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

That's so cool. I already liked Coalton, and after this change I think it's definitely going to be even better. Can't wait to try it.

Re: A case against currying

#96
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.

Mathematically it's quite pretty, and it gives you elegant partial application for free (at least if you want to partially apply the first N arguments).

Re: A case against currying

#97
post #88

Earlier quoted context omitted.

Fine, it's a regular type. It's still not the type I think it is . If it's an Int -> Int when I think it's an Int, that's still a problem, no matter how much Int -> Int is an "actual result".

Come on, just write let f :: Int = foobinade a b And the compiler immediately tells you that you are wrong: your type annotation does not unify with compiler’s inferred type. And if you think this is verbose, well many traditional imperative languages like C have no type deduction and you will need to provide a type for every variable anyways.

I spent the last three years on the receiving end of mass quantities of code written by people who knew what they were writing but didn't do an adequate job of communicate it to readers who didn't already know everything.

What you say is true. And it works, if you're the author and are having trouble keeping it all straight. It doesn't work if the author didn't do it and you are the reader, though.

And that's the more common case, for two reasons. First, code is read more often than it's written. Second, when you're the author, you probably already have it in your head how many parameters foobinade takes when you call it, but when you're the reader, you have to go consult the definition to find out.

But if I was willing to do it, I could go through and annotate the variables like that, and have the compiler tell me everything I got wrong. It would be tedious, but I could do it.

Re: A case against currying

#98
1. Such bad examples :( Tuples are data types you have to destruct, in every language. Somebody please show me a language where this doesn't require a tuple-to-function-argument translation:

  sayHi name age = "Hi I'm " ++ name ++ " and I'm " ++ show age
  people = [("Alice", 70), ("Bob", 30), ("Charlotte", 40)]
  -- ERROR: sayHi is String -> Int -> String, a person is (String, Int)
  conversation = intercalate "\n" (map sayHi people)

In python you have `*people` to destruct the tuple into separate arguments, or pattern matching. In C-languages you have structs you have to destruct.

2. And performance, you'd think a slow-down affecting every single function call would be high-up on the optimization wish list, right? That's why it's implemented in basically every compiler, including non-fp compilers. Here's GHC authors in 2004 declaring that obviously the optimization is in "any decent compiler". https://simonmar.github.io/bib/papers/eval-apply.pdf

3. Type errors, the only place where currying is actually bad, is not even mentioned directly. Accidentally passing a different number of arguments compared to what you expected will result in a compiler error.

Some very powerful and generic languages will happily support lots of weird code you throw at them instead of erroring out. Others will errors out on things you'd expect them to handle just fine.

Here's Haskell supporting something most people would never want to use, giving it a proper type, and causing a confusing type error in any surrounding code when you leave out a parentheis around `+`:

  foldl (+) 0 [1,2,3] :: Num a => a
  foldl + 0 [1,2,3]
    :: (Foldable t, Num a1, Num ((b -> a2 -> b) -> b -> t a2 -> b),
        Num ([a1] -> (b -> a2 -> b) -> b -> t a2 -> b)) =>
       (b -> a2 -> b) -> b -> t a2 -> b
Is it bad that it has figured out that you (apparently) wanted to add things of type `(b -> a2 -> b) -> b -> t a2 -> b` as if they were numbers, and done what you told it to do? Drop it into any gpt of choice and it'll find the mistake for you right away.

Re: A case against currying

#99
post #30

I like currying because it's fun and cool, but found myself nodding along throughout the whole article. I've taken for granted that declaring and using curried functions with nice associativity (i.e., avoiding lots of parentheses) is as ergonomic as partial application syntax gets, but I'm glad to have that assumption challenged. The "hole" syntax for partial application with dollar signs is a really creative alterna…

Clojure CL as well have macros that let you thread results from call to call, but you could argue that's cheating because of how flexible Lisp syntax is.

Clojure also has the anonymous function syntax with #(foo a b %) where you essentially get exactly this hole functionality (but with % instead of $). Additionally there’s partial that does partial application, so you could also do (partial foo a b).

Re: A case against currying

#100

1. Such bad examples :( Tuples are data types you have to destruct, in every language. Somebody please show me a language where this doesn't require a tuple-to-function-argument translation: sayHi name age = "Hi I'm " ++ name ++ " and I'm " ++ show age people = [("Alice", 70), ("Bob", 30), ("Charlotte", 40)] -- ERROR: sayHi is String -> Int -> String, a person is (String, Int) conversation = intercalate "\n" (map say…

In SML I believe. I never used SML but from how I understand it in ML all functions technically take one argument, which may be a tuple. In Haskell and Ocaml, all functions technically take one argument and just return a function that takes one argument again.

I never understood why the latter was so popular. Just for automatic implitic partial application which honestly should just have explicit syntax. In Scheme one simply uses the `(cut f x y)` operator which does a partial application and returns a function that consumes the remaining arguments which is far more explicit. But since Scheme is dynamically typed implicit partial application would be a disaster but it's not like in OCaml and Haskell the error messages at times can't be confusing.

I don't get simulating it with tuples either to be honest. Nothing wrong with just letting functions take multiple arguments and that's it. In Rust they oddly take multiple arguments as expect, but they can return tuples to simulate returning multiple arguments whereas in Scheme they just return multiple arguments. There's a difference between returning one argument which is a tuple of multiple arguments, and actually returning multiple arguments.

I think automatic implicit partial application, like almost anything “implicit” is bad. But in Haskell or Ocaml or even Rust it has to be a syntactic macro, it can't just be a normal function because no easy variadic functions which to be fair is incredibly difficult without dynamic typing and in practice just passing some kind of sequence is what you really want.

Post reply on HN