4 ms·
Currying can obfuscate what is applied to what. Consider in any ML language "a b c d" – we can see that "a" is a function, but we have no idea of its arity. Unc
by latk 13y ago
Currying can obfuscate what is applied to what. Consider in any ML language "a b c d" – we can see that "a" is a function, but we have no idea of its arity. Uncurried, it could be: "a(b, c, d)", "a(b, c)(d)", "a(b)(c, d)", "a(b)(c)(d)" (oh, that's the curried form again). Especially when function definitions are implied through pattern matching, it is hard to understand the contract of a function at a glance.
As a reader of that code cannot easily understand whether the number and type of arguments is correct, one has to rely on the type checker that everything will work out.
However, this is more of a criticism of ML syntax than of currying – all things are good in moderation.
- jhaywood 13y agoThat's not true. At least in SML every function only takes one argument. If a function has an arity higher than 1 it is because it take a single tuple as an argument. But you can't use the sugar for currying and tuple arguments interchangeably.
- thinkpad20 13y agoIt's actually simpler in some ways, because we know that "a" must have arity 1. What we know is that "a" should be a function which takes a "b", that "a b" should be a function which takes a "c", and "a b c" should take a "d". As a practical consideration, this rarely if ever becomes an issue, and if it does, the type checker will tell you straight away. Type annotations can make clear what isn't intuitively clear with a function's signature, and since the correctness of the type checker is rigorously proven, I don't see anything particularly wrong with "relying" on the type checker.
- latk 13y agoThe argument that every function has arity 1 is technically true (this is the whole point of currying) but is not useful when definitions like "let a b c = ..." suggest other semantics. It's possible you've had a difference experience with this, but I tend to get confused when the semantic argument list isn't delimited. There is nothing wrong with relying on the type checker, except that it tends to add cognitive overhead.
- Peaker 13y ago"let a b c = ..." doesn't suggest other semantics. It's saying: "a applied to b, and then applied to c, equals ...". Note Haskell makes an effort to have the LHS of definitions imitate the exact syntax of function application. Patterns use the same syntax as data constructor applications.
- thinkpad20 13y agoIn my experience, the more you use currying, the more intuitive it becomes (surprise, surprise). In any case, you very quickly develop an understanding that `let foo bar baz = qux` is just syntactic sugar for `let foo = \bar -> \baz -> qux`. Of course, if you want to simulate higher-arity functions, you could just use tuples. It's perfectly acceptable to write `let foo(bar, baz) = qux`.
- mafribe 13y agoEvery function having arity 1 is reducing complexity. It's extremely uniform, and let a b c = ... is merely syntactic sugar for let a = (lambda b. (lambda c. ...)). It's very natural, and as Lisp/Scheme/Racket shows, it's perfectly fine in a dynamically typed context as well.
- chongli 13y agoThis is not a problem in Haskell, as it makes a distinction between the types of all these different functions. The uncurried forms take tuples (a distinct type) as arguments whereas the curried form does not.
- catnaroek 13y agoIn ML, they are all different functions as well.
- bunderbunder 13y agoIt's a problem with ML syntax, but one that can easily be overcome with parentheses. Sort of like how a circumspect C programmer uses parentheses in complex mathematical expressions rather than relying on everyone being able to correctly remember complex order of operation rules. OTOH, pipeline operators make a good case for currying. There really is something nice about being able to write sliceOfBread |> smearWith peanut-butter |> smearWith jelly |> topWith sliceOfBread |> cutInHalf |> eat instead of eat(cutInHalf(topWith(sliceOfBread, smearWith(jelly, smearWith(peanut-butter, sliceOfBread)))))
- groovy2shoes 13y agoI just want to point out that the pipeline operator (or, more accurately, the forward application operator), is not provided by many ML implementations, but it's trivial to define it yourself. Here it is in Standard ML: infix |>; fun x |> f = f x; The definition is also similar in Haskell: x |> f = f x And in OCaml: let (|>) x f = f x;; F# and Elm provide this operator out of the box.
- namelezz 13y agoThank you for your explanation.
- munificent 13y ago> more of a criticism of ML syntax than of currying – all things are good in moderation. I don't follow this. My understanding is that currying is pure syntactic sugar: it's a cheap way to expression partial application. What am I missing?
- catnaroek 13y agoIt is not syntactic sugar. It is one of two demonstrably equivalent ways to emulate functions of two arguments. The demonstrable equivalence comes from the equational theory of cartesian closed categories. The need to emulate functions of more than one variable comes from the fact that only functions of one variable are a native concept.
- Peaker 13y agoThe word is encode, not emulate. Native support isn't any more concise, in fact it is more verbose when partial application is involved. So why complicate the language with native support for a feature whose encoding on top of one argument functions is concise, elegant, and works well?
- catnaroek 13y ago> Native support isn't any more concise, in fact it is more verbose when partial application is involved. I only know too well. Everytime I write stuff like using std::placeholders; std::bind(foo, bar, _1, _2, baz, _3, _4); I wish I were using an applicative language instead.
- mafribe 13y agoI'm not following you here. In ML-like languages a b c d is clearcut: it's means (((a b) c) d). No ambiguity whatsoever. Bracha's critique, as usual, is missing the point.
- alipang 13y agoAgreed. a b c d is a function applied to three arguments that returns a value. Functions are values, that's the whole point. For some reason it seems parent post would like to specifically indicate the case where a function application results in a value that is specifically not a function? Seem quite strange to me.
- codygman 13y ago> all things are good in moderation What about poison? Rabies? Rabies in moderation actually sounds quite appealing.
- latk 13y agoTwo words: medicines and vaccines. Of everything there is a “too little” and a “too much”, which is especially true with programming paradigms. Some people write procedural code where OOP should be used, others abuse OOP for something that should have been done in a functional manner, and sometimes functional programs should rather be expressed with procedural code.