Help us understand the problem. What is going on with this article?

Elixir episode:2 構造体

環境

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

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

またねーノシ

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした