はじめに
Elixirを習得するのに小品を書いています。ラムダ計算インタプリタの実装を通じてcatch/throwのことを学びました。
catch/throwの必要性
ラムダインタープリタは対話式です。REPLをぐるぐると回してユーザーと対話します。入力されたものにシンタックスエラーがある場合などは解釈できないため実行を中断してREPLの頭からやり直す必要があります。Lispではこういう時にcatch/throwを使うのが通例です。たぶんElixirもそうなのだろうと調べました。
Elixirスクールより
まずはいつもお世話になっているElixirスクールでcatchのことをざっくり読んでみました。下記のコードが例として示されています。
iex> try do
...> Enum.each -50..50, fn(x) ->
...> if rem(x, 13) == 0, do: throw(x)
...> end
...> "Got nothing"
...> catch
...> x -> "Got #{x}"
...> end
"Got -39"
try do
<実行すること>
catch
<throwが投げられたときの処理>
end
このような構造のようです。疑問だったのはthrowは他の関数呼び出しでも使えるのか?ということでした。ものは試しです。実際にやってみました。
Lispと同じだった
OKでした。try do でread、parseを起動します。parseの途中でシンタックスエラーを検出したときにはthrowを投げます。タグはありません。おそらく直前のtry doに対応するcatchで捕捉されるのだろうと思います。catchでエラーメッセージを出力をしてからREPLを再実行します。
ついでにendによりラムダ計算インタプリタを終了する部分もcatchで置き換えました。すっきりしました。
実行例
iex(3)> L.repl
>^x.x
^x.x
>^xx
syntax error
>end
endnil
iex(4)>
全コード
defmodule L do
def repl() do
try do
print(reduce(read()))
repl()
catch
x -> IO.write(x)
if x != "end" do
repl()
end
end
end
def read() do
IO.gets("\n>") |> String.to_charlist |> parse([])
end
# terminal
def parse('\n',res) do res end
def parse('end\n',_) do :end end
# ^ arg .
def parse([94,arg,46|ls],res) do
arg1 = String.to_atom(<<arg>>)
{body,ls1} = parse1(ls,[])
parse(ls1,res++[{:^,arg1,body}])
end
def parse([94|_],_) do
throw("syntax error")
end
# ( )
def parse([40|ls],res) do
{exp,ls1} = parse1(ls,[])
parse(ls1,res++[exp])
end
def parse([41|ls],res) do
parse(ls,res)
end
def parse([l|ls],res) do
parse(ls,res++[String.to_atom(<<l>>)])
end
def parse1([94,arg,46|ls],res) do
arg1 = String.to_atom(<<arg>>)
{body,ls1} = parse1(ls,res)
{{:^,arg1,body},ls1}
end
def parse1([94|_],_) do
throw("syntax error")
end
def parse1([40|ls],_) do
{exp,[41|ls1]} = parse1(ls,[])
{exp,ls1}
end
def parse1([l|ls],_) do
{String.to_atom(<<l>>),ls}
end
def print([]) do end
def print([{:^,x,y}]) do
IO.write("^")
IO.write(x)
IO.write(".")
print1(y)
end
def print([{:^,x,y},z]) do
print1({:^,x,y})
print1(z)
end
def print([x]) do
IO.write(x)
end
def print1({:^,x,y}) do
IO.write("(^")
IO.write(x)
IO.write(".")
print1(y)
IO.write(")")
end
def print1(e) do
IO.write(e)
end
def reduce(:end) do
throw "end"
end
def reduce([x]) do [x] end
def reduce([x,y]) do
beta(x,y)
end
def beta({:^,arg,body},y) do
[replace(arg,body,y)]
end
def replace(x,x,z) do
z
end
end