LoginSignup
1
0

More than 3 years have passed since last update.

Elixir episode:2 構造体

Last updated at Posted at 2019-05-13

環境

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

空リストのヘッド、テイルは何か?
考えすぎると眠れなくなるのでほどほどにしましょう。

またねーノシ

1
0
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
1
0