環境
sh
$ lsb_release -d
Description: Ubuntu 18.04.2 LTS
$ elixir -v
Erlang/OTP 21 [erts-10.3.5] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [hipe]
Elixir 1.8.1 (compiled with Erlang/OTP 20)
はじめに
Elixir episode:1 リスト の Fg.List
モジュールを使う。
Fg.Cell
前回は cons/2
の返り値をリストということにしたが、
Lisp にならってコンスされたものは「セル」と表現することにする。
前回のセルの実態は「1引数の関数」だったので型が不明瞭だった。
構造体にしておけばパターンマッチにて型判定ができるようになる。
$ touch lib/fg/cell.ex
fg/lib/fg/cell.ex
defmodule Fg.Cell do
alias __MODULE__, as: Me
defstruct fun: nil
def new, do: %Me{} # リストの終わりを示す番兵となる
# Lisp でいうところのドット対は認めない。 ex) [1 | 2]
def new(x, %Me{} = me) do
fun = fn selector when is_function(selector, 2) ->
selector.(x, me)
end
%Me{fun: fun}
end
def head(%Me{fun: fun}) when is_function(fun, 1) do
fun.(fn x, _y -> x end)
end
def tail(%Me{fun: fun}) when is_function(fun, 1) do
fun.(fn _x, y -> y end)
end
end
これは Lisp でいうところの真性リスト(proper list) の実態である。
Elixir にも反意述語 List.improper?/1
がある。
List.improper? [1 | 2]
は true
を返す。
Fg.List
に構造体を定義する
Fg.Cell
を内部に持つものは真性リストである。
Fg.List
が真性リストになるように再定義する。
sh
$ mkdir test/fg
$ touch test/fg/list_test.exs
fg/test/fg/list_test.exs
defmodule Fg.ListTest do
use ExUnit.Case
doctest Fg.List
alias Fg.List, as: Me
test "空リストは真性リストである" do
empty = Me.new()
assert Me.list?(empty)
end
test "最終セルのテイルが空リストならば真性リスト" do
list = Me.new([1, 2, 3])
one = list |> Me.head()
two = list |> Me.tail() |> Me.head()
three = list |> Me.tail() |> Me.tail() |> Me.head()
empty = list |> Me.tail() |> Me.tail() |> Me.tail()
assert {one, two, three, empty} == {1, 2, 3, Me.new()}
assert Me.list?(list)
end
test "真性リストのテイルは必ず真性リスト" do
assert_raise FunctionClauseError, fn -> Me.cons(1, 2) end
length1 = Me.cons(1, Me.new())
length3 = Me.cons(1, Me.new([2,3]))
assert Me.list?(length1)
assert Me.list?(length3)
end
end
fg/lib/fg/list.ex
defmodule Fg.List do
alias __MODULE__, as: Me
alias Fg.Cell
defstruct top: Cell.new()
def new, do: %Me{} # 空リスト
def new(%Me{} = me), do: me
def new(enumerable) do
top =
enumerable
|> Enum.reverse()
|> Enum.reduce(Cell.new(), &Cell.new/2)
%Me{top: top}
end
def list?(%Me{top: %Cell{}}), do: true
def list?(_), do: false
def cons(x, %Me{top: %Cell{} = top}), do: %Me{top: Cell.new(x, top)}
# 空リストのヘッドは取れない(Common Lisp: 空リストのヘッドは空リスト)
def head(%Me{top: %Cell{fun: nil}}), do: raise(ArgumentError)
def head(%Me{top: top}), do: Cell.head(top)
# 空リストのテイルは取れない(Common Lisp: 空リストのテイルは空リスト)
def tail(%Me{top: %Cell{fun: nil}}), do: raise(ArgumentError)
def tail(%Me{top: top}), do: %Me{top: Cell.tail(top)}
end
$ mix test
空リストのヘッド、テイルは何か?
考えすぎると眠れなくなるのでほどほどにしましょう。
またねーノシ