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

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

環境

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 までの fg プロジェクトを使う。

Inspect プロトコル

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

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 を実装する。

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
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 について調べてみた

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

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
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 を実装し文字列/バイナリを返す。
リストの文字列化って何だ?

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 関数が理解できなかった その1

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
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 との遣り取りに使えるね。

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]

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

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 を実装しているものが他にあったら駄目だね。

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 で例外を拾うのかな?

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

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

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

fg/lib/fg/list.ex
...
# 修正
defimpl List.Chars, for: Fg.List do
  alias Fg.List, as: Me

  def to_charlist(%Me{} = me) do
    me
    |> Enum.to_list()
    |> Kernel.to_charlist()
  end
end

失礼しました。

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

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
ユーザーは見つかりませんでした