📝Clojure Functional Programming

📝Clojure Functional Programming

いわゆる関数型プログラミングのパラダイムで登場する用語のClojure実現方法.

Clojure - Clojureを学ぼう - 関数

ref: 📂Clojure Core 🏷Functional Programming

Clojure: Function #

defn で定義.

ref: 🔗Clojure - Learn Clojure - Functions 🏷Function

Clojure: Multi-arity Functions #

Multi-arity functionsをサポート. arityはアリティと発音する, 関数の取りうる個数. オーパーロードと同義.

(defn messenger
  ([]     (messenger "Hello world!"))
  ([msg]  (println msg)))

ref: アリティ オーバーロード

Clojure: 可変長引数関数 | Variadic Functions #

可変長引数 をサポートする関数, variadic = 可変長, aka. オプション引数.

Clojureでは & を用いて指定する.

(defn hello [greeting & who]
  (println greeting who))

オプション引数が関数呼び出してで指定されない場合はnilがbindされる.

defun: Clojureでのパターンマッチマクロ #

関数にパターンマッチの機能を追加するためのマクロ. もしxxならばyyするのような条件の羅列をキレイに記述.

core.matchの形式で条件を Multi-arity Functions の形式で処理を記述することができる.

killme2008/defun

ref. Clojureにおけるパターンマッチング~defunマクロ~ - Qiita


erlangや 📝Elixer からのインスパイヤとか.

ref. 🔗なぜ僕の中でElixirが一番であり続けるのか | さんぽしの散歩記

この記事では Exixerのパイプライン演算子とパターンマッチを組み合わせて流れるようにコードを書く魅力が語られている.

同じことを Clojureでやろうとするならば cond-> 条件つきスレッドマクロ の機能がそれに該当するかもしれないが,

長いパイプライン演算子は別関数として切り出すべき

なるほどたしかに小さな関数に切り出してパターンマッチしたほうが見た目がいい場合がある.

Clojure: 無名関数 #

Clojureでは無名関数は fn で定義する. リーダマクロ #() でも表現可能.


🏷無名関数 🏷クロージャ

Clojure: constantly #

constantlyは 引数を受け取って無名関数を返す.

APIなど3rd party のライブラリを使おうと思ったとき引数に関数を指定しないと使えないときに引数を渡すためのテクニック.

Clojure: compliment #

関数を受取り, 関数を評価した結果の反対の真偽を返す無名関数を返す.

notのわかりやすい名前をつけるときに使える. (つまり defによって良い名前で束縛されることを期待).

(def not-empty? (complement empty?))

(not-empty? [])    ;;=> false
(not-empty? [1 2]) ;;=> true

ref: complement - ClojureDocs

Clojure: 関数適用 #

  • apply: 関数適用
  • partial: 部分適用.
  • comp: 関数合成.

Clojure: apply | 関数適用 #

無名関数fnを引数リストargsに適用する.

以下の2つが同じことをしている.

  • (apply str [“str1” “str2” “str3”])
  • (str “str1” “str2” “str3”)

ref: Clojure - Clojureを学ぼう - 関数

Clojure: partial | 部分適用 #

Clojureではpartialの表記を利用して部分適用する.

複数の引数を取る関数の場合, partialで適用できるのははじめの引数のみであることに注意(left-apply).

部分適用は一部の引数を固定した無名関数を返すことにすぎないため, 2つ目移行の任意の引数を固定するには自分で無名関数を書くことが必要.

(defn foo [x y z]
    (+ x y z))

;; partialだとxしか固定できない.
(def foo1 (partial foo 1))

(def foo2
  (fn [x z] (foo x 2 z)))

(def foo3
  (fn [x y] (foo x y 3)))

ref: 部分適用

Clojure: comp | 関数合成 #

複数の部分関数を組み合わせるのはcompをつかう.

Threading Macros は フォームを評価した結果であり, compは評価するための関数をまとめたものである.

(def proc-comp (comp proc1 proc2 proc3))
(def proc-next (fn [x] (proc3 (proc2 (proc1 x))))
(proc-comp x)
(proc-nest x)
(-> x
    (proc1)
    (proc2)
    (proc3))

Clojure: juxt #

複数の関数を受け取って新しく関数を返し, その関数は受け取った引数をそれぞれの関数に適用した結果をvectorで返す.

ex.) ((juxt a b c) x) => [(a x) (b x) (c x)]

ref: filter と remove のふたつの結果を簡単に受け取る方法 - Qiita


multimethodでの応用も便利. あるMapを受け取ってkeyやある処理を元に得られた結果によって異なる関数をdispatchしたいときは以下のように書ける.

;; Define the multimethod
(defmulti service-charge (juxt account-level :tag))

;; Handlers for resulting dispatch values
(defmethod service-charge [::acc/Basic   ::acc/Checking] [_] 25)
(defmethod service-charge [::acc/Basic   ::acc/Savings]  [_] 10)
(defmethod service-charge [::acc/Premium ::acc/Account]  [_] 0)

Clojureではカリー化をpartialで実現する #

Clojureではカリー化をサポートしておらず, 部分適用関数partialでうまく書き直せる.

表現を正確にするならばHaskellのようなautomatic curryingをサポートしていないので無名関数を駆使して表現を書き換えるが, partial関数をつかうとその表現方法が簡単に書けるという意.

💡カリー化と部分適用の違い については別ページでまとめたので要確認.

カリー化というのは表現方法に過ぎないので書き方ではpartial を使おうが使わなかろうが書ける. partialが便利, という話.

Clojure Style Guide には カリー化には無名関数よりpartialが望ましい との記載あり.


Clojure: 分配束縛(Destructuring) #

Clojureでは分配束縛をサポートしている.

  • vectorの中にvectorやmapを書いて表現する.
    • [[][]]
    • [{}{}]
  • 分配束縛したくない変数はvectorなら:as, mapなら:or で表現する.
  • :keysをつかうと,mapをうけとったらその値をシンボルにバインドできる.

JavaScriptだと分割代入ともいう.

一時変数(let)を分解するために分配束縛をつかう #

通常は let の中で使われる.

(def my-vector [:a :b :c :d])

(let [[a b c d] my-vector]
  (println a b c d))
;; => :a :b :c :d

関数の引数で分配束縛をつかう #

通常は [[][]]や[{}{}]のように書いて右から左にparseするが, 関数の引数として[[][]]を省略して[[]]とかける.

通常のbinding.

(defn foo [a b]
    (println a b))

(defn foo [a b & {:keys [x y]}]
  (println a b x y))
(foo "A" "B" :x "X" :y "Y")  ;; => A B X Y

余分なものはひとつにまとめるbinding.

(defn foo [a b & args]
    (println a b args))
(foo :a :b :x :y :z) ;; => :a :b (:x :y :z)

(defn foo [& {:as m}]
  (println m))
(foo :x "X" :y "Y") ;; => {:y Y, :x X}

keysで必要なものだけ取りつつ残りも取るよくばりパターン.

(defn foo [a b & {:keys [x y] :as m}]
  (println a b x y m))
(foo "A" "B" :x "X" :y "Y")
;; => A B X Y {:y Y, :x X}

この記法(keyword引数にmapを指定)はClojure 1.11からのサポートなのかな?

Clojure - Keyword argument functions now also accept maps

この書き方はつかえそう.

(defn some-handler [{:keys [db,,,,] :as req}]
,,)

奥が深い…

see also: 関数の引数にデフォルト値を指定するには?

Clojure: 遅延評価/遅延シーケンス(Laziness Evaluation/Laziness Sequence) #

遅延評価とは, 値が必要になるまで式の評価を遅らせること.

  • rangeで, 遅延シーケンスの数列を作成できる.
  • repeatで, 遅延シーケンスのシンボルの繰り返しが作成できる.
  • repeatedlyで, 指定回数だけ無名関数を適用したシーケンスを作成できる.
  • iterateで, 関数適用のシーケンスを作成できる. 数学の漸化式.

遅延シーケンスの実現には doall, dorunを利用する.

💡そもそもなぜClojureは設計思想として遅延評価なのか? #

Clojure: 再帰(Recursion) #

  • Clojureではloop/recurを利用することで再帰を実装する.
  • recurを利用すれば末尾再帰がかける.

Clojure: データ操作 #

  • mapはシーケンスに関数を適用してシーケンスを返す.
  • filterはシーケンスの要素のそれぞれにpredicateを適用してtrueの要素のみを取り出す.
  • reduce はシーケンスをaccuumurateして単一の戻り値を返す.
  • forはシーケンスを順番通りに通りに取り出す.
    • 手続き的に処理したい場合に使う.
    • pythonのforeach, zip的な.
  • flattenは入れ子構造の配列を単一シーケンスに変換する.

いろいろあるが手を動かして覚えたほうがいい. 基本的な機能は他の言語でもあるのでシンタックスを覚えるのみ.

シーケンスにはユーティリティ関数もいろいろあるのでその都度覚えよう.

ref. シーケンスの分離と合流テクニック

Clojure map vs pmap #

mapは 遅延シーケンス を構築し, dorun/doall などで強制評価してはじめてリストが順次実行される.

一方pmapは遅延シーケンスを構築するものの, doallで評価するとリストがパラレルに並列実行されて結果がリストで返される(Java Futureを内部で利用).

ref. pmap - clojure.core | ClojureDocs

以下の2つでは実行時間に4倍の差が生まれる.

(defn long-running-job [n]
    (Thread/sleep 3000)
    (+ n 10))

(doall (map long-running-job (range 4)))
(doall (pmap long-running-job (range 4)))

🔖map

clojure: filter #

与えられたコレクションから条件に合うもの抜き出す.

(filter pred coll)

filter系は派生関数がいろいろある.

  • remove は filterの逆で条件がtrueになるものを取り除く.
  • keep は fで評価した結果がnilでないものを残す.

tag: 🏷filter

Active Recalls #

Clojure部分適用のシンタックスはなんですか? 部分適用の限定的な性質はなんですか? #

partial.

Clojureの部分適用では第一引数のみにしか適用することができない.

第2引数以降を固定したい場合は無名関数で代用する.

ex.) (partial grow “Alice”)

Clojureで2つの関数から新たな関数を返すためのシンタックスはなんですか? スレッディングマクロとどう違いますか? #

comp

スレッディングマクロはフォームを返すがcompは関数を返すため引数をもらわない限りそれ自体では評価できない.

ex.) (defn surprise [direction] ((comp oh-my toggle-grow) direction))

配列やマップのデータ構造の要素をそのままbindingする方法はなんですか? #

分配束縛 - destructuring

Clojureのrange, repeat, repeatedlyが共通してもつ性質はなんですか? #

遅延評価 - Lazy Evaluation

Clojureにおいて再帰を実現するためのシンタックスはなんですか? #

loop/recur. 末尾再帰ならrecur.

Clojureで漸化式のような遅延シーケンスを構築する関数はなんですか? #

iterate

Clojureのconstantly, complementはそれぞれどんな役割ですか? #

constantlyは引数を受取り無名関数を返す.

complementは関数を受取りその関数の評価結果の真偽をひっくり返した無名関数を返す.

Clojure: juxtはどんな役割がありますか? #

複数の関数を受け取って新しく関数を返し、その関数は受け取った引数をそれぞれの関数に適用した結果をvectorで返す.

ex.) ((juxt a b c) x) => [(a x) (b x) (c x)]


Tags