【レポート】第3回清流elixir勉強会in丸の内を開催しました【マップのパターンマッチ】

トピック

昨日、愛知県の丸の内にて私が運営しているコミュニティ清流elixir
第3回目となる勉強会を開催させて頂きました
さすがに3回目ともなると多少は要領が分かってきてわりとスマートに
活動できるようになってきたかと思ってます(力不足ですません

清流elixir-infomation
開催場所: 丸の内(愛知)
人数: 2 -> 3
コミュニティ参加人数 : 3 -> 5 update!!
20190427現在

第3回の活動内容

今回は前回の勉強会中につまづいたマップのパターンマッチについて学習しました
どうすればマップから指定のkeyを持つデータをマッチできるのかをいろいろ試した

f:id:takamizawa46:20190427100226j:plain:w450
マップから値を取得(マッチ)するには...?

ただElixirにはマップの記述方法が2種類あり、それぞれで取得の仕方が微妙に異なるので注意
それぞれに名称も特になく勝手に命名しておく

  • Atomを使用する楽な記述(Atom式と命名)
  • 普通の書き方%{"name" => "okb"}(温故知新スタイルと命名)

f:id:takamizawa46:20190427100233j:plain
アトム式と温故知新スタイル

Rubyにも同様に2種類の記述方法があるようで
それは歴史的な流れでより簡単に記述するためにということで
Elixirでいうアトム式が誕生したとのこと
使い分けについて議論しましたが完璧な答えは出なかった
温故知新スタイルでは記号でも日本語でも何でも(バイナリ)をkeyにできるが
それにメリットがあるかは分からなかった

どちらを使うにしろチームで統一して使えばいいんじゃね?という結論になりました

Atom式からのマッチング

このマップデータから:nameのkeyを様々な方法でマッチしてみる

info = %{name: "nobunaga", age:45}

1.最もシンプルなマッチ

info[:name]  #nobunaga

#この書き方はerror
info[name] #error

#存在しないkeyをマッチさせようとしてもerrorにはならない
info[:company] #nil

2.次にシンプルなマッチ

info.name #nobunaga

#存在しないkeyをマッチさせようとするとerror
info.company #error

3.keyの存在を確認しつつのマッチ

#%{key_name: variable_name} = data
%{name: name} = info
name #nobunaga

#変数名は自由(アンダースコアはNG)
%{name: human_name} = info
human_name #nobunaga

4.Map.getを使う

Map.get(info, :name) #nobunaga

#第2引数にはAtomで渡す必要がある(errorにはならない)
Map.get(info, "name") #nil

5.Map.fetchを使う

#マッチに成功した場合に{:ok, value}のタプルで返ってくる
Map.fetch(info, :name) # {:ok, value}
{judge, name} = Map.fetch(info, :name)
name #nobunage

#存在しないkeyを指定した場合は:errorのみが返ってくる
Map.fetch(info, :company) # :error

f:id:takamizawa46:20190427100254j:plain:w450

温故知新スタイルからの取得

info = %{"name" => "nobunaga", "age" => 45}

同様にAtom式の5つのやり方を試してみる

1.最もシンプルなマッチ

info["name"] #nobunaga
info[:name] #nil

2.次にシンプルなマッチ
実はAtom式でないとこれは使えない

info."name" #error

#警告がでる上に失敗する
warning: found quoted call "name" but the quotes are not required. Calls made exclusively of Unicode letters, numbers, and underscore do not require quotes
  iex:40

** (KeyError) key :name not found in: %{"name" => "nobunaga"}

3.keyの存在を確認しつつのマッチ

#記述の仕方がAtom式と異なるので注意
%{"name" => n} = info 
n #nobunaga

#存在しないとやはりerror
%{"company" => c} = info #error

4.Map.getを使う

#第2引数にはkeyと同様の型で渡す(今回はstring)
Map.get(info, "name") #nobunaga

#errorにはならない
Map.get(info, name)

5.Map.fetchを使う

Map.fetch(info, "name") #{:ok, "nobunaga"}

#やっぱり存在しないと:error
Map.fetch(info, "company") # :error

基礎を踏まえてデータで遊ぶ

info = %{"name" => "nobunaga", "age" => 45, "from" =>  "gifu", "style" => "bushidou"}

このデータから"name"と"from"を取得して
"name"と"from"の値を結合することに(stringの結合)

name = "nobunaga"
from = "gihu"

name <> from # nobunagegihu

温故知新スタイルであることを留意して取り組むとこんな感じになった

info["from"] <> info["name"] #nobunagegihu
Map.get(info, "from") <> Map.get(info, "name") #nobunagegihu

{a, b} = Map.fetch(info, "name")
{c, d} = Map.fetch(info, "from")
b <> d #nobunagegihu

前回のリベンジ

前回はリストinマップから値をマッチするという作業でつまづいた
これだけ知識がついたのでリベンジするかということでリストinマップの情報を用意

info = [
  %{age: 22, height: 180, name: "a"},
  %{age: 16, height: 142, name: "b"},
  %{age: 43, height: 165, name: "c"},
  %{age: 67, height: 156, name: "d"},
  %{age: 81, height: 161, name: "e"},
  %{age: 8, height: 126, name: "f"}
]

リスト内の各データへのアクセスをするためにはもちろんEnumを使う
あとは今までやってきたようにマップのkeyを使ってマッチさせるだけ

Enum.map(infos, fn row -> row.age end)
#[22, 16, 43, 67, 81, 8]

Enum.map(infos, fn row -> row[:age] end)
#[22, 16, 43, 67, 81, 8]

Enum.map(infos, &(&1.age))
#[22, 16, 43, 67, 81, 8]

Enum.map(infos, fn row -> Map.get(row, :age) end)
#[22, 16, 43, 67, 81, 8]

もう楽勝ですね
このマッチは一瞬で終わってしまった

なぜ関数型言語がいいのかって

作業を進めている中で参加者の業界20年のベテランの方から
オブジェクト言語に疲れたという話がはじまり、かなり盛り上がった

動物や車などがオブジェクトのサンプルとしてよく挙げられる
しかし、機械の操作や目に見えない概念をオブジェクトにして考えるということは 難しい上に正解がなく、人それぞれで理解しにくい場合が多いとのこと

そうすると流れの掴みやすい関数型の良さがよく分かるとのことで
「なるほどなー」と強く感銘を受けました

まとめ

今回はマップのパターンマッチについて勉強会を行いました
途中から会話が無くなり少年のように没頭する時間がおもろかった
それぞれが様々な記述を発見していく感じも良かった
僕が目指している「開催者<=>参加者」という構図が少し見えてきたがする

次回はGW明けの5/10(金)の19:30からの開催予定です
お気軽に覗きにきてください