やわらかテック

興味のあること。業務を通して得られた発見。個人的に試してみたことをアウトプットしています🍵

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

トピック

今回で第4回目の勉強会を僕の運営しているコミュニティで開催することができました
清流elixir

connpassでの参加人数+開催直前に新規の1名の方に参加して頂けました
少しずつ賑わってきてる感があって素直に嬉しいです

また参加者の業種や立場がバラバラであるにも関わらず
ユースケース抜きにElixirに興味がある、触ってみたいという感覚が共通しているのが非常に面白い
少なからず色んなケースで需要が見えているということなんですかね(適当

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

第4回の活動内容

いつもは僕がテーマを設定して書籍等、参照しながら
テーマに沿って説明し、余裕があれば手を動かすというスタイルでやっている

何と今回はありがたいことに初回から皆勤参加の現役学生の方が
今回の内容について全て説明をしてくれた

ありがたや...ありがたや...
もう毎回これでいいんじゃないかな(無責任

値の束縛について

Elixirにはそもそも代入という考え方はない
すべては値がマッチするかどうかを確認している
「=」はElixirではマッチ演算子と呼ばれる

a = 1 #代入ではない
a = 1 #aは1にマッチする(aは元々宣言がないため)

多くの関数型言語(Haskellやら)では値は束縛される
すなわち、再代入することが出来ない

a = 1
a = 2 #再代入あかん

全く書いたことないけど何となくHaskellで再現できた

main = putStrLn "XXXXXXXX"
sub = "aa"
sub = "bb"

{--Main.hs:5:1: error:
    Multiple declarations of ‘sub’ --}

実はElixirでは再代入(再マッチ)することが出来る
さっきも普通にaの値を更新してた
え、じゃあ束縛できへんの?となるが
「ピン演算子(^)」を使うことで可能

a = 1
^a = 2

#マッチしないと怒られる(いいね)
#** (MatchError) no match of right hand side value: 2

ピン演算子で値の不変性を保証することが出来る
チームで開発する場合にはピン演算子を使うか使わないかという共通認識を持たなければいけないと思う

何個かピン演算子で遊んでいる内に面白いことに気づいた

#errorにならない
a = 2
^a = 5 - 3 #2

^a = 4 - a #2

^a = 2 -a #errr

右側の評価が先に行われて終了した時点でマッチするかどうかを見ているっぽい
なのでこんな風に書けば先ほどerrorになったものも通る(参加者の方が発見、すご)

a = 2
(^a = 2) -a #0

()を使うことで演算の優先度の変更が変わる

色んなデータ型からのマッチ

今まで値をいかに取り出すかという視点で考えていたが
Elixri的にはいかにしてマッチさせるかという視点で考える必要があるということを
再認識して若干反省。1日1マッチを目指して進んでいく

タプル

{a, b} = {1, 2}
#a = 1
#b = 2

{a, b, c} = {1, 2}
#** (MatchError) no match of right hand side value: {1, 2}

別にこの値を使うことないわって時には「_(アンダースコア)」を使う

{a, _} = {1,2}
#a = 1
#_ error

{a, _b} = {1, 2}
#_b = 2 となるがwarningが出る(アンダースコア消せやって)

マップ
タプルのようにはマッチすることは出来ない

%{a, b} = %{name: "okb", age: 22}
#** (CompileError) iex:19: expected key-value pairs in a map, got: a

取り出しかたは5種類ほどあるが過去に説明済みなので
【レポート】第3回清流elixir勉強会in丸の内を開催しました【マップのパターンマッチ】を参照してください

関数でのマッチ

f:id:takamizawa46:20190512102647j:plain:w450
今まで触れるようで触れてなかった部分
自分のブログでも関数の宣言の仕方の解説してないのに当たり前のようにdefmoduleとか使ってて反省
時間あれば書いときます

Elixirでは関数の引数やガード節たるものを使って関数の実行前に関数でマッチすることが可能
そもそも同名の関数を同じモジュール内に宣言することが出来るのがヤバい

defmodule GreetMan do
  def greet("nobunaga", greet), do: IO.puts("#{greet}, nobunaga")
  def greet("hideyoshi", greet), do: IO.puts("#{greet}, hideyoshi")
  def greet(name, greet), do: IO.puts("#{greet}, #{name}")
end


GreetMan.greet("nobunaga", "hello") #name="nobunaga"にマッチ
GreetMan.greet("hideyoshi", "good morning") #name="hideyoshi"にマッチ
GreetMan.greet("ieyasu", "bye") #その他(?)でマッチ

# hello, nobunaga
# good morning, hideyoshi
# bye, ieyasu

ただ、これだとnobunagaとか引数渡ししてるのに
IO.putsの中で使用していたりと変更に非常に弱いコードになっている
こんな時に便利なのがガード節
以下のように関数名を宣言した後にwhenと記述するだけ

def greet(name) when name == "nobunage", do

先ほどのモジュールをwhen使って書き直すとこんな感じに

defmodule GreetMan do
  def greet(name, greet) when name == "nobunaga", do: IO.puts("#{greet}, #{name}")
  def greet(name, greet) when name == "hideyoshi", do: IO.puts("#{greet}, #{name}")
  def greet(name, greet), do: IO.puts("#{greet}, #{name}")
end


GreetMan.greet("nobunaga", "hello")
GreetMan.greet("hideyoshi", "good morning")
GreetMan.greet("ieyasu", "bye")

# hello, nobunaga
# good morning, hideyoshi
# bye, ieyasu

マッチ後の処理が全く同じなので利便性を感じづらいが関数でマッチできるのはえぐい
タプルとかのデータ構造に対しても関数の引数を使って関数実行前に値をマッチすることが可能

defmodule GreetMan do
  def is_sucess({:ok, value}), do: IO.puts(value)
  def is_sucess({:error, reason}), do: IO.puts(reason)
end

GreetMan.is_sucess({:ok, :good}) #good
GreetMan.is_sucess({:error, :bad_value}) #bad_value

再帰やるにはこの関数マッチが必須
なので流れで次回は再帰について触れることになった

次回の開催について

今回新規で参加して頂いた方のご好意で
勤務されている会社の会議室を勉強会で使わせて頂けることになりました(しかも名駅付近)
狭苦しい部屋からは脱出できそうで、本当にありがたや...ありがたや...
つながりを持つのは本当に大事ですね

いきなり広い場所になると緊張しそう
まぁたくさん間違えて恥をかいた分だけ強くなれるのでラフにいきます
内容については「Elixirでの再帰のやり方」にしようと思ってます
詳細はまた追ってconnpassで展開します