关于currying:Clojure中的高阶函数

Higher-order functions in Clojure

Clojure很棒,我们都知道,但这不是重点。 我想知道以类似Haskell的方式创建和管理高阶函数的惯用方式是什么。 在Clojure中,我可以执行以下操作:

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

但是(sum 1)不会返回函数:它将导致错误。 当然,您可以执行以下操作:

1
2
3
(defn sum
  ([a] (partial + a))
  ([a b] (+ a b)))

在这种情况下:

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

但这似乎不是正确的方法。 有任何想法吗?
我不是在谈论实现sum函数,而是在更高级别的抽象上。 有什么惯用的模式可以遵循吗? 一些宏? 是定义宏的最佳方法还是有替代解决方案?


已经有人在Clojure小组中实现了这一点。您可以指定一个函数有多少个args,它将自动为您处理直到获得那么多。

我想,在Clojure中默认情况下不会发生这种情况的原因是,我们认为可变参数函数比自动咖喱函数更喜欢。


我已经使用了alalloy建议的功能。我不喜欢明确说明要讨论的参数数量。因此,我创建了自定义宏。这是特定于高阶函数的旧方法:

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

这是我的新宏:

1
2
3
4
(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)))

这是新的隐式方式:

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

如您所见,没有(curry)和其他东西的痕迹,只需像以前一样定义您的currified函数即可。

伙计们,您怎么看?有想法吗?有什么建议吗?
再见!

阿尔费多

编辑:我已经根据有关docstring的amalloy问题修改了宏。这是更新的版本:

1
2
3
4
5
6
7
(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)))

我不喜欢第二个绑定内的if语句。关于使其更简洁有什么想法?


这将使您可以执行所需的操作:

1
2
3
4
5
6
7
8
(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))))))

使用方法如下:

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

[nil]的条件是为了确保每个应用程序都确保一定程度的向前进展到已固化状态。它后面有很长的解释,但我发现它很有用。如果您不喜欢这一点,可以将args设置为:

1
[args (concat applied more)]

与JavaScript不同,我们无法知道所传递函数的有效性,因此必须指定所需的长度。这在Clojure [Script]中很有意义,其中一个函数可能具有多个arities。