やわらかテック

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

【レポート】第9回清流elixir勉強会を開催しました【ウェルカムElixir入門会】

トピック

今回で第9回目の勉強会を開催致しました
elixir-sr.connpass.com

東京で参加したElixir&ErlangFest2019やfukuoka.exさんの勉強会にリモート参加させて頂いた中で
コミュニティの盛り上がり、参加する人の多さが非常に重要であると痛感した

fukuokaex.connpass.com

それもそのはず。3人集まれば文殊の知恵と言うように
多くの人が集まればそれだけ多くの知識、アイディアが生まれる

同じ学習でも自分以外を交えて知識を交換しつつ学ぶ方が明らかに効率が良い

またElixirを4ヶ月程触る中で、いわゆる「良さ」というものを身にしみて理解することが出来た
この素晴らしい仕様と快適さを多くの人に体験してほしいと思った

そんな最もらしいこともあり、今回はElixirの入門会を開催した
結果、新規の方に2名参加していただき、Elixirの良さを伝える機会を頂くことが出来た
スピーカーとして自分は100点ではないのが申し訳ないが、良さは伝えられてたと思っている

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

過去最高の参加者!! ありがとうございます!!

第9回の活動内容

Elixirの入門会を開催した
今までの勉強会の内容を振り返りつつ軽く手を動かしながら進めた
当日のアジェンダは以下

  • やさしいElixir -> Elixirって何やねん。化粧品? ポーション?
  • Elixirの特徴と機能的な話
  • なんで僕がElixirに興味を持っているか
  • Elixirのパワフルなシンタックス

やさしいElixir

まずは新規の参加者のお二人になぜElixirに手を出そうと思ったのかを聞いてみた
普段はpythonなどをメインに書かれており、どっから情報仕入れたのかなと思った

「名前がかっこいいからやろうと思った」

素晴らしいセンスを持っていらっしゃった
当然、Elixirという名前からファイナルファンタジーを連想するが前にもお話した通り
Elixirの作者Joseはファイナルファンタジーをプレイしたことが無いため、関係はない

定番の決まり文句でまずは場を盛り上げつつ、Elixirの概要に触れていく

  • Elixirって何?(全快のポーションではない) 2011年に誕生 -> まだ8歳
  • 2019年にわざわざ学ばなくてもいいプログラミング言語ランキングに7位でランクイン
  • 東海地方ではあまり名を聞くことのないプログラミング言語(2015年あたりに一度バズりがあったらしい) -> Qiitaの記事を種火にRuby勢が盛り上げた

Elixirの特徴と機能的な話

  • Erlangの仮想環境上(BEAM)で動作している
  • Erlangの機能が使用可能 -> 大量の独自プロセスを作成&管理可能
  • Erlang同様に非常に堅牢で、まず落ちない -> 任天堂switchの通知システムは2年間の間、一度も落ちていない(Erlang)
  • ErlangRubyシンタックスの良さを組み合わせ、Erlangの書きにくさが解消された
  • プロセス単位のガベージコレクタでメモリの使い方がかなりエコ
  • 大量の並行処理が気軽に記述可能
  • 終了したプロセスは消してしまうという考えなため、プロセスを再起動させて...なんてことをしなくていい
  • パワフルなシンタックス

Erlangの良さがつらつらと書かれているが、僕はErlang経験がないのでElixirから感銘を受けたのは並行処理、パワフルなシンタックスの部分である
なので上ら辺に書かれているErlangの強みがという部分に確信した情報を持っていないので一度、Erlangドキュメントに目を通さねばと思った
ErlangのOTPの仮想環境(BEAM)を一種のOSのように捉えていたが、本当にそうなのか?という議論になり非常に勉強になった

近日、勉強をしていたコンピューターサインエンスの話と繋がり、やっていてよかったと痛感
.exもしくは.exsの形式で保存されたファイルに記述された文字はElixirもしくはErlangによってコンパイルされ
BEAM(VM)上でbinarycodeに変換され実行されるという認識に落ち着いた
そのためElixirはErlangの機能を使用することが可能であり、OSに依存しない独自のプロセスが使えるということなのではと

なんで僕がElixirに興味を持っているか

このテーマに関しては過去の記事で何度も扱っているので、ざっくりとだけ記述しておく
より文字を読みたい方は以下のリンクを参照してほしい

www.okb-shelf.work

当日はこんな感じで話した(かな

  • 名前がかっこよい
  • 日本(特に東海では先駆者がいない) ではマイナー
  • 関数型言語をやってみたかった
  • 大量のプロセスによる並列処理がビックデータの処理にぴったり

Elixirのパワフルなシンタックス

ここでいうパワフルという意味は「少ない記述で高度な事が可能」という意味で扱っている
まずはEnumの関数達。入門という事で扱ったのは「map, filter, reduceの3つ」
そもそもElixirというか一般的に関数型言語にはfor文(繰り返し処理)が用意されていない
そんな時に使うのがElixirであればEnumの関数(内包表記など)
それぞれの使い方は以下の記事で紹介しているので詳しい話を以下のリンクを参照してほしい

話の落ちどころとしては、オブジェクト言語や手続き型言語で記述していたfor文を使った処理は
全て、Enum.map, Enum.filter, Enum.reduceなどの組み合わせで記述が可能であるということ
(まとめは参加者の方のお言葉をパクリスペクトさせて頂きました)

Enum.map, Enum.filterについて
www.okb-shelf.work

Enum.reduceについて(アキュムレーターという考え方がmapやfilterと異なるため、別に展開)
www.okb-shelf.work

Enumの関数は列挙可能なデータ構造に対して使用可能
列挙可能なデータ構造は以下

  • リスト
  • マップ
  • レンジ

今回は例としてリストを対象に手を動かした
まずはmapとfilterから

lst = [1,2,3,4,5,6,7,8,9,10]

# map: 全ての要素を2倍する
Enum.map(lst, fn num -> num * 2 end)
# [2, 4, 6, 8, 10, 12, 14, 16, 18, 20]

# filter: 7より大きい要素を破棄
Enum.filter(lst, fn num -> num < 7 end)
# [1, 2, 3, 4, 5, 6]

mapやfilterはリストを受け取ってリストを出力するため、データの型の変換を行うことが出来ない
そこで役立つのがreduce関数。accumlatorの考え方は上記のリンクをみて欲しい
以下の例ではリストの合計値を求めている。つまりはリストから数値への変換を行なっている

lst = [1,2,3,4,5,6,7,8,9,10]

# 要素の合計値を求める(リスト -> 数値)
Enum.reduce(lst, 0, fn num, acc -> num + acc end)
# 55

うん、すごい
しかし、単純にmap, filter, reduceはpythonにもある
でもElixirはもっとすごい。パイプ演算子という文法が用意されている
上記の3つの処理を順次pythonで行おうとするとこんな感じに

from functools import reduce

lst = [1,2,3,4,5,6,7,8,9,10]
res = map(lambda x: x * 2, lst)
res = filter(lambda x: x < 7, res)
res = reduce(lambda x, acc: x + acc, res)
print(res)
# 12

悪くはないが、都度、変数resに値を渡して、次へ次へと面倒臭い
Elixirであればパイプ演算子を使ってこんな風に記述することが出来る

lst = [1,2,3,4,5,6,7,8,9,10]

Enum.map(lst, fn num -> num * 2 end)
|> Enum.filter(fn num -> num < 7 end)
|> Enum.reduce(fn num, acc -> num + acc end)
|> IO.puts() 
# 12

何をやっているかが一目瞭然で、流れがよく分かる
不要になった部分はコメントアウトして以下のようにすれば良いし、新たに処理を追加するときも楽々だ

lst = [1,2,3,4,5,6,7,8,9,10]

# 処理を減らす
Enum.map(lst, fn num -> num * 2 end)
# |> Enum.filter(fn num -> num < 7 end)
|> Enum.reduce(fn num, acc -> num + acc end)
|> IO.puts()
# 110

# 処理を追加
Enum.map(lst, fn num -> num * 2 end)
|> Enum.filter(fn num -> num < 7 end)
|> Enum.map(fn num -> (num + 10) * 4 end)
|> Enum.reduce(fn num, acc -> num + acc end)
|> Integer.to_string()
|> IO.puts()
# 168

最後にパターンマッチについて触れた
(今回は関数の引数でのパターンマッチとガード説を用いた例を紹介-> fizzbuzzのため)

Elixirでは関数の引数に実際の値を記述しておくことで、その場合にのみ特定の関数を呼び出す事が可能であり
同名の関数を何個でも記述することが可能
(関数を定義するときはモジュール内部に記述する必要あり)

それぞれの対応する値段をパターンマッチを用いて返すという関数を作成した

defmodule Sample do
  def price("apple") do
    110
  end
  def price("banana") do
    150
  end
  def price("orange") do
    90
  end
  def price("grape") do
    540
  end
  def price(_fruit) do
    200
  end
end

in_cart = ["banana", "apple", "fish", "orange", "banana", "grape", "meat"]
convert_to_price = Enum.map(in_cart, fn item -> Sample.price(item) end)
IO.inspect(convert_to_price)

# [150, 110, 200, 90, 150, 540, 200]

まぁマップを使ってvalueを抜く方法は当然あるが、普通に書こうと思うとifをたくさん使うことになる
しかしElixirではパターンマッチを使って簡単に条件を書く事が可能であり、同名の関数を何個でも書けるので仕様変更にも強い
price関数にcherryを追加したければ、price("cherry")という関数を追加するだけだ

また受け取った値に対して、何らかの演算を行なってからパターンマッチを行たいときはガード節というものを使用する
例を見せた方が早い
映画の料金を年齢別に返す関数を作ってみた

defmodule Sample do
  def price(age) when age >= 20 do #20歳以上
    1500
  end
  def price(age) when age >= 10 do #10歳以上
    1000
  end
  def price(_age) do #10歳より下
    800
  end
end

Enum.map([25, 12, 9, 35, 4, 20, 10], fn age -> Sample.price(age) end)
|> IO.inspect()
# [1500, 1000, 800, 1500, 800, 1500, 1000]

パターンマッチは上から順にマッチされていくので2つ目のprice関数にマッチをする際には
1つ目の条件(age >= 20)が適用された状態なので、「20 > age >= 10」としなくても思ったように動く

以上の踏まえてElixirでfizzbuzzを作ってみる
rem()は除算の余りを求める関数である(eg: rem(30, 15) -> 0)

defmodule Sample do
  def fizz_buzz(num) when rem(num, 15) == 0 do
    "fizz_buzz: #{num}"
  end
  def fizz_buzz(num) when rem(num, 5) == 0 do
    "buzz: #{num}"
  end
  def fizz_buzz(num) when rem(num, 3) == 0 do
    "fizz #{num}"
  end
  def fizz_buzz(num) do
    "another #{num}"
  end
end

Enum.map(1..100, fn num -> Sample.fizz_buzz(num) end) |> IO.inspect()
# ["another 1", "another 2", "fizz 3", "another 4", "buzz: 5", "fizz 6",
# "another 7", "another 8", "fizz 9", "buzz: 10", "another 11", "fizz 12",
# "another 13", "another 14", "fizz_buzz: 15"....]

先ほども記述した通りに、仮に7の倍数にヒットさせたいとなった時は
同名のfizzbuzz関数を用意してrem(num, 7) == 0という条件を作成すれば良い
カスタムが非常に楽な上に強力。パターンマッチいいね

感想・まとめ

かなり駆け足で紹介したいものを紹介し尽くした
初めての試み故にうまく出来なかった部分もあるし、思ってたよりもウケた部分もあった
総じて反省、学習して次に生かせればと思う

最後に「Elixirめっちゃいいですね」と言ってもらえて良かった
また秋にでも入門会は開いてみようと思う

次回はfukuoka.exさんの開催するもくもく会にリモートで参加する予定です
また宜しくお願いします

fukuokaex.connpass.com

おまけのコーナー

さらに関数を省略形で記述することでfizzbuzzはもっと短くすることが出来る

defmodule ShortFizzbuzz do
  def fizz_buzz(num) when rem(num, 15) == 0, do: "fizzbuzz: #{num}"
  def fizz_buzz(num) when rem(num, 5) == 0, do: "buzz: #{num}"
  def fizz_buzz(num) when rem(num, 3) == 0, do: "fizz: #{num}"
  def fizz_buzz(num), do: "another: #{num}"
end

Enum.map(1..100, fn num -> ShortFizzbuzz.fizz_buzz(num) end)

いいね