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?

3 Answers 3

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.

Sign up to request clarification or add additional context in comments.

3 Comments

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 :)
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.
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.
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?

4 Comments

I like it. Though I would suggest a different name for your macro. Perhaps "defcurry" or maybe "defcurried".
(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.
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 :)
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?
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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.