(defn double-+
[a b]
(* 2 (+ a b)))
在函数式编程中,函数是一等公民。这意味着函数可以像值一样对待。它们可以作为值赋值,传递给函数,并从函数中返回。
在 Clojure 中,使用 defn
定义函数很常见,例如 (defn foo …)
。但是,这只是 (def foo (fn …))
的语法糖。fn
返回一个函数对象。defn
返回一个指向函数对象的 var。
高阶函数是一个函数,它:
将一个或多个函数作为参数
返回一个函数作为其结果
这是任何语言中函数式编程的一个重要概念。
高阶函数允许我们组合函数。这意味着我们可以编写小的函数并将它们组合起来创建更大的函数。就像把一堆小的乐高积木拼在一起建造房子一样。
让我们稍微离开理论,来看一个例子。
让我们看一下两个函数
(defn double-+
[a b]
(* 2 (+ a b)))
(defn double-*
[a b]
(* 2 (* a b)))
这些函数共享一个共同的模式。它们仅在名称和用于计算 a
和 b
的函数方面有所不同。一般来说,模式如下所示。
(defn double-<f>
[a b]
(* 2 (f a b)))
我们可以通过将 f
作为参数传递进来,来泛化我们的 double-
函数。
(defn double-op
[f a b]
(* 2 (f a b)))
我们可以使用它来表达对操作结果进行加倍的概念,而不是必须分别编写执行特定加倍操作的函数。
匿名函数是没有名称的函数。在 Clojure 中,可以通过两种方式定义它们,fn
和字面量 #(…)
。使用 defn
创建函数会立即将其绑定到一个名称,fn
只创建函数。
让我们用几个乐队举个例子
(def bands [
{:name "Brown Beaters" :genre :rock}
{:name "Sunday Sunshine" :genre :blues}
{:name "Foolish Beaters" :genre :rock}
{:name "Monday Blues" :genre :blues}
{:name "Friday Fewer" :genre :blues}
{:name "Saturday Stars" :genre :jazz}
{:name "Sunday Brunch" :genre :jazz}
])
我们只想检索摇滚乐队。这是一个一次性操作,我们不会在代码的其他任何地方使用它。我们可以使用匿名函数来节省一些按键。
(def rock-bands
(filter
(fn [band] (= :rock (:genre band)))
bands))
更简洁地,使用函数字面量,我们可以这样定义 rock-bands
。
(def rock-bands (filter #(= :rock (:genre %)) bands))
函数字面量通过 %
、%n
和 %&
支持多个参数。
#(println %1 %2 %3)
如果你正在编写匿名函数,字面量语法很好,因为它非常紧凑。但是,超过几个参数后,语法可能变得难以阅读。在这种情况下,使用 fn
可能更合适。
我们的第一个函数将称为 adder
。它将接收一个数字 x
作为其唯一参数并返回一个函数。由 adder
返回的函数也将接收一个数字 a
作为其参数并返回 x + a
。
(defn adder [x]
(fn [a] (+ x a)))
(def add-five (adder 5))
(add-five 100)
;; => 105
从 adder
返回的函数是一个闭包。这意味着,它可以访问创建函数时作用域内的所有变量。add-five
可以访问 x
,即使它在 adder
函数定义之外。
过滤是计算机编程中常见的一种操作。以这组动物为例
(def pets [
{:name "Fluffykins" :type :cat}
{:name "Sparky" :type :dog}
{:name "Tibby" :type :dog}
{:name "Al" :type :fish}
{:name "Victor" :type :bear}
])
我们想过滤掉非狗类动物,因为我们正在编写企业级软件。首先,让我们看一下普通的 for 循环。
(defn loop-dogs [pets]
(loop [pets pets
dogs []]
(if (first pets)
(recur (rest pets)
(if (= :dog (:type (first pets)))
(conj dogs (first pets))
dogs))
dogs)))
这段代码工作正常,但它笨拙且令人困惑。我们可以使用高阶函数 filter
来简化它。
(defn filter-dogs [pets]
(filter #(= :dog (:type %)) pets))
使用 filter
的解决方案更加清晰,并且允许我们展示意图,而不仅仅是给出命令。我们可以通过将过滤函数分解到一个单独的 var
中,将其分解成更小的部分。
(defn dog? [pet] (= :dog (:type pet)))
(defn filter-dogs [pets] (filter dog? pets))
原文作者:Michael Zavarella