1. 9

  2. 5

    Currying makes sense in Haskell/ML, but with very few exceptions, it just doesn’t fit in to Clojure. The obvious argument is that Clojure, like many other languages, chooses a syntax that prefers variadics to the exclusion of currying by default.

    More importantly, currying encourages patterns that should be avoided. For example, functional combinators and monadic interfaces thread a context argument in the rightmost position. For one thing, these patterns produce unprintable/opaque closures, which deeply hinders debugging. That’s a shame in a language that ships with a pretty printer for - and encourages use of - a common, closed, syntactic data structure (EDN). For another thing, Clojure is impure, so you can just have effects, rather than introduce the extra complexity of monads which make it much easier to accidentally produce lazy or duplicated effects (since the monadic values are reified, the implicit linearity of imperative constructs is lost). Instead of combinators or monads, just write a direct-style, effectful interpreter for some EDN-encoded value, pass a context map as the first argument, if you need to.

    Never mind the fact that currying introduces lots of anonymous values, which can easily lead to the sort of confusion that you’d want a type checker to alleviate!

    1. 1

      This version of curry supports variadic arguments. Other issues you’ve mentioned may be relevant or not, but are the same issues that Clojure’s partial has.

      As for the monads and effects, monads are not only about effects, but more generally about threading context, so although they are less needed in Clojure than in Haskell, they can be useful.

      1. 3

        This version of curry supports variadic arguments.

        It only “supports” defaulting to an arity of 2 and allowing you to declare a higher fixed arity. Currying and variadic function application are fundamentally incompatible.

        the same issues that Clojure’s partial has.

        I think that partial and comp are overused in Clojure as well. For top-level declarations, the syntactic overhead of using named parameters serves as useful documentation and you get the right code-reloading behavior implicitly through var resolution. Otherwise, you have to remember to (comp #'some-named-var #'some-other-named-var) to avoid some confusion later. For inline functions, the #(....%....) shorthand syntax is still quite short and much clearer and more flexible than either comp or partial.

        monads are not only about effects, but more generally about threading context

        I think my comment covered this, but I’ll restate more concretely: You should pass “pure” context via a map as the first argument; and you should pass “impure” context as either thread-local global vars or by putting a reference type in your pure map.

        The interplay of currying and context threading is that you can define methods on initial parameters to delay computation and then provide the context later to force evaluation. In a strict, impure language, it’s much preferable to just do a thing rather than to build up an opaque structure that does a thing. If you actually need delayed and context-varying execution, it’s much better to design a transparent structure to be interpreted. Then the context can be threaded through the interpreter directly.

    2. 1

      Often, only some arguments are known at some point in time. Later they are repeated. Instead of keeping track of them all the time, we would like to somehow fill them in, and be able to supply the rest at an appropriate time in future.

      Very far from the actual point of currying. Generally it’s more for cases where there are a few arguments that tend to stay constant but others that vary a lot, e.g., when mapping on an array. In Haskell you can simply write map (* 2) [1..] and get a list of all the even numbers. This also requires laziness, but it’s a somewhat better example of where currying makes a difference compared to lambdas.