Clojure

高階関数

第一級関数

関数型プログラミングでは、関数は第一級オブジェクトです。これは、関数を値として扱うことができることを意味します。関数は値として代入したり、関数に渡したり、関数から返したりすることができます。

Clojureでは、`defn` を使用して `(defn foo …​)` のように関数定義を行うのが一般的です。しかし、これは `(def foo (fn …​))` の糖衣構文に過ぎません。 `fn` は関数オブジェクトを返します。 `defn` は関数オブジェクトを指すvarを返します。

高階関数

高階関数とは、以下のいずれかの条件を満たす関数です。

  1. 1つ以上の関数を引数として取る

  2. 結果として関数を返す

これは、あらゆる言語の関数型プログラミングにおける重要な概念です。

高階関数を使用すると、関数を*合成*することができます。これは、小さな関数を記述してそれらを組み合わせて、より大きな関数を作成できることを意味します。小さな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