クロージャは、Clojureにおけるプログラミングのパターンのひとつで、外側のレキシカルスコープにある変数を関数の内部で使えるようにするものです。Clojureはクロージャを意識させずにサポートしているので、それが何なのかを知らないまま使われることもよくあります。
;; Top-level definitions are global-scope
(def dozen 12)
;; Functions create a new scope.
;; Referencing the outer variable here is a closure.
(fn [n] (* dozen n))
アトムを使うと、ある程度の状態を保持できます。
;; This function closure increments the counter's state
;; in the outer lexical context.
;; This way the counter can be shared between many calling contexts.
(def increment
(let [counter (atom 0)]
(fn [] (swap! counter inc))))
incrementを呼び出すたびに、そのカウンターが1つずつ増えていきます。
(increment)
;;=> 1
(increment)
;;=> 2
あなたのデザイン会社では、これまで主にCSSの変換を使ってWebページを作ってきました。いくつか話し合った結果、動的な計算にClojureを使い始めることになりました。チームメイトの中にはClojureの経験が浅い人もいるので、関数クロージャーを使って、{x, y}の座標ペアを繰り返し変換できる仕組みを作ることにします。
translate2d関数を実装しましょう。この関数は、クロージャーを利用して、座標ペアの2次元平行移動を繰り返し行える関数を返します。
(def move-coordinates-right-2px (translate2d 2 0))
(def result (move-coordinates-right-2px 4 8))
;; result => [6 8]
scale2d関数を実装しましょう。この関数は、クロージャーを利用して、座標ペアの2次元の拡大縮小を繰り返し行える関数を返します。
この演習では、拡大縮小の値は正の値のみを想定します。
(def double-scale (scale2d 2 2))
(def result (double-scale 6 -3))
;; result => [12 -6]
2つの変換関数を組み合わせて、繰り返し行える変換を作りましょう。これは_関数合成_と呼ばれることが多く、1つ目の関数_'f(x)'の結果を、2つ目の関数'g(x)'_への入力として使います。
(def move-coordinates-right-2px (translate2d 2 0))
(def double-coordinates (scale2d 2 2))
(def composed-transformations
(compose-transform move-coordinates-right-2px
double-coordinates))
(def result (composed-transformations 0 1))
;; result => [4 2]
memoize-transform関数を実装しましょう。この関数は、_メモ化_したい関数を受け取り、その関数への入力を覚えている新しい関数を返します。こうすることで、同じ引数でもう一度呼び出されたときには、最後の戻り値を「覚えている」ので、計算は一度だけで済みます。
メモ化は_動的計画法_と呼ばれることもあり、結果が覚えられているので、コストの高い処理を一度だけ実行できるようにします。
(def triple-scale (scale2d 3 3))
(def memoized-scale (memoize-transform triple-scale))
(memoized-scale 4 3)
;; => [12, 9], this is computed since it hasn't been computed before for the arguments
(memoized-scale 4 3)
;; => [12, 9], this is remembered, since it was computed already