LoginSignup
3
1

More than 5 years have passed since last update.

Elixr:ラムダ計算インタプリタ(2)ーCatch/Throwの使い方ー

Last updated at Posted at 2019-01-31

はじめに

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


3
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
1