プログラミングElixir第8章~コレクション

マップとキーワードリストの使いわけ

  1. パターンマッチを行いたいか→ マップを使う
  2. キーが複数エントリ存在するか→ Keywordモジュール
  3. 要素の順番を保証しなければならないか→ Keywordモジュール
  4. そのほか→ マップを使う

キーワードリスト

defmodule Canvas do
  @defaults [ fg: "black", bg: "white", font: "Arial" ]

  def draw_text( text, options \\ [] ) do
    options = Keyword.merge(@defaults, options)
    IO.puts "Drawing test #{inspect(text)}"
    IO.puts "Foreground:  #{options[:fg]}"
    IO.puts "Background:  #{Keyword.get(options, :bg)}"
  end
end
iex(3)> Canvas.draw_text( "test", fg: "red" )
Drawing test "test"
Foreground:  red
Background:  white
:ok

iex(5)> Canvas.draw_text( "test", [])
Drawing test "test"
Foreground:  black
Background:  white
:ok

iex(6)> Canvas.draw_text( "test")
Drawing test "test"
Foreground:  black
Background:  white
:ok

マップ

キーと値のセットというデータ構造を扱うもの。制限が緩いので多用途?

iex(1)> defaults = %{ fg: "black", bg: "white", font: "Arial" }
%{bg: "white", fg: "black", font: "Arial"}

iex(2)> Map.keys defaults
[:bg, :fg, :font]

iex(3)> Map.values defaults
["white", "black", "Arial"]

iex(6)> Map.drop defaults , [:font, :bg]
%{fg: "black"}
iex(7)> Map.put defaults, :sound, "none"
%{bg: "white", fg: "black", font: "Arial", sound: "none"}
iex(8)> defaults
%{bg: "white", fg: "black", font: "Arial"}
iex(9)> Map.has_key? defaults, :fg
true
iex(10)> Map.has_key? defaults, :ssg
false

iexから h ... で説明も得られる. popの返り値は 値と新しいmapのタプルだ. キーがあれば, popで取得した key, valueのセットを除いたmapを返してくれる. キーが見つからなければそのまま, 値は第三引数を返す(省略するとnilね).

iex(11)> h Map.pop

                       def pop(map, key, default \\ nil)

  @spec pop(map(), key(), value()) :: {value(), map()}

Returns and removes the value associated with key in map.

If key is present in map with value value, {value, new_map} is returned where
new_map is the result of removing key from map. If key is not present in map,
{default, map} is returned.

## Examples

    iex> Map.pop(%{a: 1}, :a)
    {1, %{}}
    iex> Map.pop(%{a: 1}, :b)
    {nil, %{a: 1}}
    iex> Map.pop(%{a: 1}, :b, 3)
    {3, %{a: 1}}

マップのパターンマッチ

前述の Map.pop を使わなくても, パターンマッチで値を取得できる. ただし キーがなければそこでエラー終了してしまう..

iex(12)> %{ fg: col } = defaults
%{bg: "white", fg: "black", font: "Arial"}
iex(13)> col
"black"
iex(14)> %{ bgm: col } = defaults
** (MatchError) no match of right hand side value: %{bg: "white", fg: "black", font: "Arial"}

マップの更新

Elixirは Immutableな変数を扱うので, 更新処理は右辺で行う。すなわち、もともとのmap変数の中を上書きすることはなく、変更した結果のマップを新たに左辺の変数 or パターンで束縛する(?束縛の意味があやしい)。

※パターンマッチ(左辺をマップ式で受ける?)で、キーワードを設定すれば それを含むものだけがマッチする。複数指定も可能。

構造体

Mapの制限を付与したデータ構造。構造体にすると, Mapに比べて以下の制限がある

  • キーに使えるのはアトムだけ
  • キーはフィールドと呼び?、定義時に初期値を設定できる
  • module内で宣言し, 固有の関数を定義する(クラスのようなもの?)
defmodule ImageHeader do
  defstruct magic: 0xCafeBabe, length: <<0::size(32)>>, body: <<>>
  def set_magic(base=%ImageHeader{}, <<value>>), do: %ImageHeader{base | magic: <<value>>} 
  def is_valid_magic(%ImageHeader{magic: val}), do: val == 0xDeadBeef
end

iex(3)> a = %ImageHeader{}
%ImageHeader{body: "", length: <<0, 0, 0, 0>>, magic: 3405691582}                                       
iex(4)> ImageHeader.is_valid_magic(a)
false
iex(5)> aa = %ImageHeader{a | magic: 0xdeadbeef}
%ImageHeader{body: "", length: <<0, 0, 0, 0>>, magic: 3735928559}                                       
iex(6)> ImageHeader.is_valid_magic(aa)
true

構造体の入れ子

入れ子記述は容易に気付けるだろう。問題は階層深くのメンバへのアクセス方法。変更を加える場合には、元のデータを渡していく必要があるので、階層分すべての構造体を理解してマッチングして...記述しなければならない.

簡略化のためのマクロ・関数が用意されている。

put_in(var.var1.var2, "new value")

ここで

  • var1: なにか(strucnt2)の構造体を束縛した、構造体struct1の変数
  • var2: struct2の変数名

関数を渡して引数に元の値、結果に書き換えてくれるマクロ update_in()がある. 取得するための get_in(), 取得して更新する get_and_update_in(),...

ヘルプを見て あとは実践(TODO)..

最後に書かれていたダークサイド...

この構造体や moduleに閉じ込めた 関数について、オブジェクト指向ぽく記述ができそう、というのはよろしく無いとある。まさにそう考えていただけにガツーンですよまったく。

Elixirは関数型言語、その特徴・メリットを知る(第九章、1ページ)

第9章 型とはなにか

失敗(TODO)

構造体名のとおり、本来はバイナリを扱いたかった。 c++のクラスとは異なり、すべて static method相当になってしまうのかな. thisポインタの代わりに 明示的にインスタンスを受けて, 変更後のインスタンスを返すような作りになりそう. 何らかの表現を返すなら タプルにすれば解決するだろう...

値をもらって バイナリ列に変換したいだけなのかな. ライブラリとかありそう.

defmodule ImageHeader do
  defstruct magic: <<0 :: size(32)>>, length: <<0::size(32)>>, body: <<>>
  def set_magic(base=%ImageHeader{}, <<value>>), do: %ImageHeader{base | magic: <<value>>} 
  defp set_magic_oct(value) when value >= 256, do: (
    <<rem(value,256) , set_magic_oct( div(value, 256) )>>
  )
  defp set_magic_oct(value) when value < 256, do: <<value>>
  def set_magic(base=%ImageHeader{}, value), do: (
    %ImageHeader{base | magic: set_magic_oct(value)}
  )
  def is_valid_magic(%ImageHeader{magic: val}), do: val == <<0xDe,0xad,0xBe,0xef>>
end