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" ]
は完全に理解できない。なんだこりゃ?