LoginSignup
1
0

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-05-15

環境

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
    |> to_string()
    |> Kernel.to_charlist()
  end
end

失礼しました。

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

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