Tambourine作業メモ

主にスキル習得のためにやった作業のメモ。他人には基本的に無用のものです。

Elixirで遊んでみる(5)

7章は「リスト」。いわゆるconsリストについて説明している。まあ、いいでしょう。理解はしてる。使えるかはアレだけど。

8章は「マップ、キーワードリスト、セット、構造体」。盛りだくさんだ。

マップのパターンマッチの仕方は慣れる必要がありそうだ。

iex> person = %{name: "Tambara", age: 45}
%{age: 45, name: "Tambara"}
iex> person.name
"Tambara"
iex> %{name: n} = person
%{age: 45, name: "Tambara"}
iex> n
"Tambara"

:nameの値を取り出すだけならperson.nameでいいわけだけど、パターンマッチでも取り出せる。

で、ここで内包表現の例が出てくる。Pythonでよくわからないアレだ。 いや、ネストしなければわからなくはないんだけど。やってみよう。forを使っているところがそれらしい。

力士 = [
    %{ 四股名: "白鵬", 番付: "横綱", 部屋: "宮城野", 体重: 151.0 },
    %{ 四股名: "朝乃山", 番付: "大関", 部屋: "高砂", 体重: 174.0 },
    %{ 四股名: "照ノ富士", 番付: "関脇", 部屋: "伊勢ヶ濱", 体重: 173.0 },
    %{ 四股名: "隆の勝", 番付: "関脇", 部屋: "常盤山", 体重: 163.0 },
    %{ 四股名: "御嶽海", 番付: "小結", 部屋: "出羽海", 体重: 172.0 },
]

IO.inspect(for i = %{体重: h} <- 力士, h < 170.0, do: i)

イマドキの言語なので、UTF-8の並びならなんでも識別子になる。

実行すると

iex> c "query.exs"
[
  %{体重: 151.0, 四股名: "白鵬", 番付: "横綱", 部屋: "宮城野"},
  %{
    体重: 163.0,
    四股名: "隆の勝",
    番付: "関脇",
    部屋: "常盤山"
  }
]
[]

こんな感じ。なんで改行が入ったのかはよくわからない。

i = %{体重: h} <- 力士は、パターンマッチがネストになっている。まず、<-の方がマッチされる (ここが=じゃないのは、力士リストに軽量力士がいない可能性もあるからだ。=だと例外がでちゃう)。 で、マッチしたものが、iに束縛されている。なので、Pythonのようにループ変数を指定する必要がないみたい。 その後ろに、ガードっぽいものが書いてあって最初のマッチで束縛されたhがここで使われている。 けど、単に,で並べればいいのかな。 どんな式なら並べて良いのかわからないので、釈然としない。

さて、Elixirなのでマップもイミュータブルだ。えっ?そうなの?

リストはconsリストの構造や、スライスになじみがあるからリストがイミュータブルですと言われても、「そうですね」という気がする。 でも、マップに新しいキーと値のペアを追加したら新しいマップが生成されると聞くと、なんかビックリする。 リストと同じように出来るものなの?

マップの変更はこんな感じ

iex> m = %{a: 1, b: 2, c: 3}
%{a: 1, b: 2, c: 3}
iex> m1 = %{ m | b: "two", c: "three" }
%{a: 1, b: "two", c: "three"}
iex> m
%{a: 1, b: 2, c: 3}
iex> m1
%{a: 1, b: "two", c: "three"}

キーの追加はこの構文では出来ない。Map.put_newを使う。

iex> m2 = %{m | d: 4}
** (KeyError) key :d not found in: %{a: 1, b: 2, c: 3}
    (stdlib 3.14) :maps.update(:d, 4, %{a: 1, b: 2, c: 3})
    (stdlib 3.14) erl_eval.erl:259: anonymous fn/2 in :erl_eval.expr/5
    (stdlib 3.14) lists.erl:1267: :lists.foldl/3
iex> m2 = Map.put_new m, :d, 4
%{a: 1, b: 2, c: 3, d: 4}

次は、構造体。構造体はモジュールの中で定義する。 というか、モジュールとして名前がついたキーが固定されたマップである。 オブジェクトはすべてハッシュというJSに近い感じがする。

定義はこんな感じ

defmodule SumoFighter do
  defstruct 四股名: "", 部屋: "", 番付: "", 体重: 0.0
end

マップと同じように作る。更新もおんなじ感じ。

iex(109)> shodai = %SumoFighter{ 四股名: "正代", 部屋: "時津風", 番付: "関脇", 体重: 170.0}
%SumoFighter{
  体重: 170.0,
  四股名: "正代",
  番付: "関脇",
  部屋: "時津風"
}
iex(110)> shodai2 = %SumoFighter{ shodai | 番付: "大関" }
%SumoFighter{
  体重: 170.0,
  四股名: "正代",
  番付: "大関",
  部屋: "時津風"
}

大関に昇進させたいけど、イミュータブルなので別のマップになります(笑)

キーを固定できるだけじゃなくて、モジュールに関数を定義することで オブジェクトっぽくできる。

その力士が三役力士かどうかを判定する関数を作ってみる。 もちろん、ifで判定するんじゃなくて、パターンマッチで判定するのだ。

defmodule SumoFighter do
  defstruct 四股名: "", 部屋: "", 番付: "", 体重: 0.0
  def is_三役?(%SumoFighter{番付: "横綱"}) , do: true;
  def is_三役?(%SumoFighter{番付: "大関"}) , do: true;
  def is_三役?(%SumoFighter{番付: "関脇"}) , do: true;
  def is_三役?(%SumoFighter{番付: "小結"}) , do: true;
  def is_三役?(%SumoFighter{番付: _}) , do: false;
end

ダサい。猛烈にダサい。でも、今の私のElixir力ではここからどうして良いのかはわからない。うむー。

まあ、これでなんとか判定は出来る。

iex> SumoFighter.is_三役?(shodai)
true
iex> okinoumi = %SumoFighter{ 四股名: "隠岐の海", 部屋: "八角", 番付: "前頭", 体重: 160.0 }
%SumoFighter{
  体重: 160.0,
  四股名: "隠岐の海",
  番付: "前頭",
  部屋: "八角"
}
iex> SumoFighter.is_三役?(okinoumi)                                                        
false

でもなあ・・・。

そして、構造体を作ると、当然、ネストすることもある。 つまり、構造体のあるキーの値がまた別の構造体ってこと。 すると、更新が面倒くさくなるので、マクロと関数が用意されている。

しかし、本のP092にある

authors = [
  %{ name: "José", language: "Elixir" },
  %{ name: "Matz", language: "Ruby" },
  %{ name: "Larry", language: "Perl" }
]

languages_with_an_r = fn (:get, collection, next_fn) ->
  for row <- collection do
    if String.contains?(row.language, "r") do
      next_fn.(row)
    end
  end
end

IO.inspect get_in(authors, [languages_with_an_r, :name])
#=> [ "José", nil, "Larry" ]

は完全に理解できない。なんだこりゃ?