Elixir episode:4 モジュールの文字列化

# 環境

```shell-session: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.2 (compiled with Erlang/OTP 20)
```

# はじめに

[Elixir episode:3 Enumerable](https://qiita.com/feelinguy/items/39be885fffab14bc611f) までの `fg` プロジェクトを使う。

# `Inspect` プロトコル

`Fg.List` の中身を簡単に確認したいなぁ。
毎回 `Enum.to_list/1` は嫌だなぁ。

```elixir:iex
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` を実装する。

```elixir:fg/lib/fg/list.ex
# 追加
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
```
```elixir:iex
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 について調べてみた](https://qiita.com/melpon/items/dede48ad3bbc8c4ff443)

`Inspect.Algebra.surround_many/6` は非推奨で消えたらしいので、
`Inspect.Algebra.container_doc/6` で読み替えましょう。


```elixir:fg/lib/fg/list.ex
# 変更
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
```
```elixir:iex
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` を実装し文字列/バイナリを返す。
リストの文字列化って何だ？

```elixir:iex
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` がいるねぇ。

[flatten 関数が理解できなかった その１](https://qiita.com/feelinguy/items/43bfeebec63b63a51847)

```elixir:fg/lib/fg/list.ex
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
```
```elixir:iex
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 との遣り取りに使えるね。

```elixir:iex
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]
```

こっちは構造いじらないのね。その方がめんどい・・・。

```elixir:fg/lib/fg/list.ex
...
# 追加
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` を実装しているものが他にあったら駄目だね。

```elixir:iex
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` で例外を拾うのかな？

ちがうちがう、考えすぎたし、勘違い。
本筋は文字列からの変換だ。

```elixir:iex
iex(1)> to_charlist "あいうえお"
[12354, 12356, 12358, 12360, 12362]
```

だからこれだけでいいはず。

```elixir:fg/lib/fg/list.ex
...
# 修正
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
```

失礼しました。

ビヘイビアは継承だけど、プロトコルは疎結合なのがいいね。ノシ