(defn double-+
[a b]
(* 2 (+ a b)))
関数型プログラミングでは、関数は第一級オブジェクトです。これは、関数を値として扱うことができることを意味します。関数は値として代入したり、関数に渡したり、関数から返したりすることができます。
Clojureでは、`defn` を使用して `(defn foo …)` のように関数定義を行うのが一般的です。しかし、これは `(def foo (fn …))` の糖衣構文に過ぎません。 `fn` は関数オブジェクトを返します。 `defn` は関数オブジェクトを指すvarを返します。
高階関数とは、以下のいずれかの条件を満たす関数です。
1つ以上の関数を引数として取る
結果として関数を返す
これは、あらゆる言語の関数型プログラミングにおける重要な概念です。
高階関数を使用すると、関数を*合成*することができます。これは、小さな関数を記述してそれらを組み合わせて、より大きな関数を作成できることを意味します。小さなLEGOブロックをたくさん組み合わせて家を建てるようなものです。
少し理論から離れて、例を見てみましょう。
2つの関数を見てみましょう。
(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)))
これを使用して、特定の2倍の操作を実行する関数を個別に記述するのではなく、操作の結果を2倍にするという概念を表すことができます。
匿名関数とは、名前のない関数です。Clojureでは、`fn` とリテラル `#(…)` の2つの方法で定義できます。 `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}
])
ロックバンドだけを取得したいとします。これは1回限りの操作であり、コードの他の場所では使用しません。匿名関数を使用することで、キーストロークを節約できます。
(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` は、 `adder` 関数定義の外部にある場合でも、 `x` にアクセスできます。
フィルタリングは、コンピュータプログラミングでは一般的な操作です。この動物のセットを考えてみましょう。
(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