環境
$ 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.2 (compiled with Erlang/OTP 20)
はじめに
Elixir episode:3 Enumerable までの fg
プロジェクトを使う。
Inspect
プロトコル
Fg.List
の中身を簡単に確認したいなぁ。
毎回 Enum.to_list/1
は嫌だなぁ。
iex(1)> list = Fg.List.new(0..99)
%Fg.List{top: %Fg.Cell{fun: #Function<2.85842498/1 in Fg.Cell.new/2>}}
iex(2)> nested = Fg.List.cons(list, list)
%Fg.List{top: %Fg.Cell{fun: #Function<2.85842498/1 in Fg.Cell.new/2>}}
iex(3)> inspect(nested)
"%Fg.List{top: %Fg.Cell{fun: #Function<2.85842498/1 in Fg.Cell.new/2>}}"
iex(4)> Enum.to_list(nested)
[
%Fg.List{top: %Fg.Cell{fun: #Function<2.85842498/1 in Fg.Cell.new/2>}},
0,
1,
2,
# 略
...
]
そんなときに Inspect.inspect/2
を実装する。
# 追加
defimpl Inspect, for: Fg.List do
alias Fg.List, as: Me
@left "#Fg.List<"
@right ">"
def inspect(me = %Me{}, _opts) do
inspected = me |> Enum.to_list() |> Kernel.inspect()
@left <> inspected <> @right
end
end
iex(1)> list = Fg.List.new(0..99)
#Fg.List<[0, 1, 2, #略 ...]> # デフォで要素が50ヶ列挙される
iex(2)> Fg.List.cons(list, list)
#Fg.List<[#Fg.List<[0, 1, 2, #略 ...]>
なんかダラつくね。
以下の記事に丁寧な解説がありました、感謝。
Inspect.Algebra.surround_many/6
は非推奨で消えたらしいので、
Inspect.Algebra.container_doc/6
で読み替えましょう。
# 変更
defimpl Inspect, for: Fg.List do
import Inspect.Algebra
alias Fg.List, as: Me
@left "#Fg.List<["
@right "]>"
def inspect(me = %Me{}, opts) do
native = Enum.to_list(me)
container_doc(@left, native, @right, opts, &Inspect.inspect/2)
end
end
iex(1)> list = Fg.List.new(0..99)
#Fg.List<[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19,
20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39,
40, 41, 42, 43, 44, 45, 46, 47, 48, 49, ...]>
iex(2)> nested = Fg.List.cons(list, list)
#Fg.List<[
#Fg.List<[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37,
38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, ...]>,
0,
1,
2,
# 略
...
]>
iex(3)> inspect(nested, limit: 3)
"#Fg.List<[#Fg.List<[0, 1, ...]>, 0, 1, ...]>"
なるほどぉ。オプションも効くようになったぞ、いいね。
String.Chars
プロトコル
String.Chars.to_string/1
を実装し文字列/バイナリを返す。
リストの文字列化って何だ?
iex(1)> list = [1,2,3]
[1, 2, 3]
iex(2)> nested = [list | list]
[[1, 2, 3], 1, 2, 3]
iex(3)> to_string(nested)
<<1, 2, 3, 1, 2, 3>>
へぇ、平坦化しちゃうんだ。
じゃー flatten/1
がいるねぇ。
defmodule Fg.List do
alias __MODULE__, as: Me
alias Fg.Cell
...
# 追加
def flatten(me = %Me{}), do: _flatten(me)
defp _flatten(%Me{top: %Cell{fun: nil}}), do: new()
defp _flatten(me = %Me{}) do
flat_h = head(me) |> _flatten()
flat_t = tail(me) |> _flatten()
append(flat_h, flat_t)
end
defp _flatten(x), do: new([x])
end
...
# 追加
defimpl String.Chars, for: Fg.List do
alias Fg.List, as: Me
def to_string(me = %Me{}) do
me
|> Me.flatten()
|> Enum.to_list()
|> Kernel.to_string()
end
end
iex(1)> list = Fg.List.new(1..3)
#Fg.List<[1, 2, 3]>
iex(2)> nested = Fg.List.cons(list, list)
#Fg.List<[#Fg.List<[1, 2, 3]>, 1, 2, 3]>
iex(3)> to_string(nested)
<<1, 2, 3, 1, 2, 3>>
List.Chars
プロトコル
List.Chars.to_charlist/1
で文字コード・リストを返す。
Erlang との遣り取りに使えるね。
iex(1)> list = [1,2,3]
[1, 2, 3]
iex(2)> nested = [list | list]
[[1, 2, 3], 1, 2, 3]
iex(3)> to_charlist(nested)
[[1, 2, 3], 1, 2, 3]
こっちは構造いじらないのね。その方がめんどい・・・。
...
# 追加
defimpl List.Chars, for: Fg.List do
alias Fg.List, as: Me
def to_charlist(me = %Me{}) do
me
|> Me.foldl([], &to_chars/2)
|> Enum.reverse()
end
defp to_chars(me = %Me{}, acc) do
chars =
me
|> Enum.to_list()
|> Kernel.to_charlist()
[chars | acc]
end
defp to_chars(list, acc) when is_list(list) do
[Kernel.to_charlist(list) | acc]
end
defp to_chars(x, acc), do: [x | acc]
end
この定義でいいのかどうかわからん。
(入れ子巡回できてないし、そもそも巡回する必要なかった)
List.Chars
を実装しているものが他にあったら駄目だね。
iex(1)> list = Fg.List.new([:a,:b,:c])
#Fg.List<[:a, :b, :c]>
iex(2)> nested = Fg.List.new([1,2,3, list, 4,5,6])
#Fg.List<[1, 2, 3, #Fg.List<[:a, :b, :c]>, 4, 5, 6]>
iex(3)> to_charlist(nested)
[1, 2, 3, [:a, :b, :c], 4, 5, 6]
モジュールがプロトコルを実装しているかどうかって
Protocol.assert_impl!/2
で例外を拾うのかな?
ちがうちがう、考えすぎたし、勘違い。
本筋は文字列からの変換だ。
iex(1)> to_charlist "あいうえお"
[12354, 12356, 12358, 12360, 12362]
だからこれだけでいいはず。
...
# 修正
defimpl List.Chars, for: Fg.List do
alias Fg.List, as: Me
def to_charlist(me = %Me{}) do
me
|> to_string()
|> Kernel.to_charlist()
end
end
失礼しました。
ビヘイビアは継承だけど、プロトコルは疎結合なのがいいね。ノシ