36

Clojure is awesome, we all know this, but that's not the point. I'm wondering what the idiomatic way of creating and managing higher-order functions in a Haskell-like way is. In Clojure I can do the following:

(defn sum [a b] (+ a b))

But (sum 1) doesn't return a function: it causes an error. Of course, you can do something like this:

(defn sum
  ([a] (partial + a)) 
  ([a b] (+ a b)))

In this case:

user=> (sum 1)
#<core$partial$fn__3678 clojure.core$partial$fn__3678@1acaf0ed>
user=> ((sum 1) 2)
3

But it doesn't seem like the right way to proceed. Any ideas?
I'm not talking about implementing the sum function, I'm talking at a higher level of abstraction. Are there any idiomatic patterns to follow? Some macro? Is the best way defining a macro or are there alternative solutions?

Alfredo Di Napoli
  • 2,281
  • 3
  • 22
  • 28

3 Answers3

32

Someone has already implememented this on the Clojure group. You can specify how many args a function has, and it will curry itself for you until it gets that many.

The reason this doesn't happen by default in Clojure is that we prefer variadic functions to auto-curried functions, I suppose.

amalloy
  • 89,153
  • 8
  • 140
  • 205
  • 1
    Thanks for the quick reply. I've already looked at that post, and I'm wondering if the proposed solutions are adopted in some clojure.contrib sub-packages or are only a brunch of possibile ideas :) – Alfredo Di Napoli Mar 16 '11 at 17:31
  • Can I infer from your answer that you think variadic functions and auto-curried functions are not compatible to each other? If so, I'd like to hear more about the reason. Thanks. – day Sep 11 '12 at 14:29
  • Given `(defn f ([x] 1) ([x y] 2))`, what does `(f true)` yield? It must be 1, meaning it cannot be auto-curried as a partial application of the two-arity version. – amalloy Sep 11 '12 at 17:48
8

I've played a bit with the functions suggested by amalloy. I don't like the explicit specification of the number of argument to curry on. So I've created my custom macro. This is the old way to specific an high order function:

(defn-decorated old-sum
  [(curry* 3)]
  [a b c]
  (+ a b c))

This is my new macro:

(defmacro defn-ho
  [fn-name & defn-stuff]
  (let [number-of-args (count (first defn-stuff))]
    `(defn-decorated ~fn-name [(curry* ~number-of-args)] ~@defn-stuff)))

And this is the new implicit way:

(defn-ho new-sum [a b c] (+ a b c))

As you can see there is no trace of (curry) and other stuff, just define your currified function as before.

Guys, what do you think? Ideas? Suggestions? Bye!

Alfedo

Edit: I've modified the macro according the amalloy issue about docstring. This is the updated version:

(defmacro defhigh
  "Like the original defn-decorated, but the number of argument to curry on
  is implicit."
  [fn-name & defn-stuff]
  (let [[fst snd] (take 2 defn-stuff)
         num-of-args (if (string? fst) (count snd) (count fst))]
    `(defn-decorated ~fn-name [(curry* ~num-of-args)] ~@defn-stuff)))

I don't like the if statement inside the second binding. Any ideas about making it more succint?

Alfredo Di Napoli
  • 2,281
  • 3
  • 22
  • 28
  • 3
    I like it. Though I would suggest a different name for your macro. Perhaps "defcurry" or maybe "defcurried". – A. Levy Mar 16 '11 at 18:45
  • `(defn-ho myfn "does awesome stuff" [a b c] ...)`. Now your args-counting won't work that well. It certainly can be done, but handling the forms that defn accepts is not trivial, which I suspect is the reason it wasn't implemented that way to begin with. – amalloy Mar 16 '11 at 20:29
  • Yes, you're right, but mine was only a stub to work on. Furthermore, as you said, it won't be difficult to fix the typo in order to handle the docstring. For the rest, it's all matter of how you think to use the macro. Under certain circumstances, everything will work fine :) – Alfredo Di Napoli Mar 16 '11 at 20:39
  • The code mentioned in the Google groups seems to allow `(def-curry-fn f [a b c d] (+ a b c d))`. Is that essentially the same as your `defn-ho` or am I missing something? – wrongusername Jan 06 '12 at 04:45
0

This will allow you to do what you want:

(defn curry
  ([f len] (curry f len []))
  ([f len applied]
    (fn [& more]
      (let [args (concat applied (if (= 0 (count more)) [nil] more))]
        (if (< (count args) len)
          (curry f len args)
          (apply f args))))))

Here's how to use it:

(def add (curry + 2)) ; read: curry plus to 2 positions
((add 10) 1) ; => 11

The conditional with the [nil] is meant to ensure that every application ensures some forward progress to the curried state. There's a long explanation behind it but I have found it useful. If you don't like this bit, you could set args as:

[args (concat applied more)]

Unlike JavaScript we have no way of knowing the arity of the passed function and so you must specify the length you expect. This makes a lot of sense in Clojure[Script] where a function may have multiple arities.

Mario
  • 6,572
  • 3
  • 42
  • 74