Clojerl は Erlang VM (BEAM) で動く Clojure である。
Erlang/Elixir では pattern match を頻繁に使ふ。一つは條件分岐する爲。慣れてゐる Elixir で書こう。
defmodule Example do def example1(:a, v), do: v + 1 def example1(_, v), do: v def example2(v) do case v do {:a, v} -> v + 1 {_, v} -> v end end end
example1
函數では第一引數が :a
であれば加算しさうでなければ何もしない。example2
函數の樣に data の内部構造にも match 出來る。pattern match で條件分岐するのは主に tuple に對して效果が大きい。tuple は位置と意味を混淆 (complect) すると云ふ事で Clojure には無く、全くその通りなのだが Erlang では頻用するから pattern match をしたい。
これなら迂遠ではあっても Clojure でも書ける。しかし次のは書けない。
defmodule Example do def example({:x, v, 42}, [%{x: v, y: y} | rest]), do: {y, rest} end
一つ目の tuple の中の v と、二つ目の list の中の map の中の v とが同じ値でなければこれには match しない。この樣に Erlang の pattern match は非線形である。また變數だけでなく値も書ける。既に束縛 (a.k.a. 代入) 済みの變數を patter の中に書いた場合には再束縛はされず、その値の data にしか match しない。これを Clojure で書くのは不可能ではないものの、pattern match library を一通り書かなければならない。
pattern match は他にも返り値を檢査するのに使ふ。
{:ok, pid} = GenServer.start_link(Example, nil)
この場合 pid
變數に代入するのだが、:ignore
や {:error, reason}
が返って來たら error として crush させたいのだ。これも Clojure で書くのは面倒だ。
pattern match は擴張出來ず再利用も出來ないと云ふ事で Clojure には無く、multi method や分配束縛を使ふのだが、Erlang/OTP には behaviour や recieve 等 pattern match を前提とした機能が頻發するので pattern match を書きたい。また先の缺點には Scala の unapply や Egison 等の例外が在る。
Why no pattern matching? clojure.org clojure.org
さて Clojerl で pattern match する方法だが document には無い。この document には何が有るんだ? 果たして GitHub repository の examples に在った。
fn*
と let*
特殊形式を使ふ。返り値の檢査は let*
で行ふ。
(let* [#erl[:ok pid] (gen_server/start_link example nil)] pid)
束縛せず檢査だけするなら以下で好い。
(let* [#erl[:ok _] (gen_server/start_link example nil)])
fn*
は無名函數を作るもので、callback として渡す事も在るが、主に def
と組み合はせて使ふ。
(def example (fn* ([:a v] (+ v 1)) ([_ v] v)))
case や recieve もそれぞれ case*
、recieve*
特殊形式として書ける。
(case* v #erl[:a v] (+ v 1) #erl[_ v] v) (recieve* #erl[:ex1 v 42 #erl{:x v :y y}] v #erl[:ex2 message] message _ #erl[:error "Unknown message"] (after 1000 #erl[:error "Timeout"]))
函數定義で使ふ pattern match と似た機能に Erlang には guard が在る。引數が條件に當て嵌まる時だけ本體を實行させる。これも Clojerl の document には無く examples に在った。
fn*
特殊形式の機能である。
(fn* ([v] {:when (erlang/is_pid v)} :pid) ([v] {:when (erlang/is_integer v)} :integer))