トピック
今回で第5回目の勉強会を僕の運営しているコミュニティで開催することができました
清流elixir
先週にはfukuoka.exを運営されているpiacereさんと
Twitterで繋がらせて頂きまして多くの方に清流elixirの名を知って頂けました
本当にあざます!!!
やる気がめっさ出ました!!
清流elixir-infomation
開催場所: 丸の内(愛知)
参加人数: 3 -> 4 update!!
コミュニティ参加人数 : 5 -> 8 update!!
20190525現在
第5回の活動内容
関数型言語といったらやっぱり再帰関数でしょってことで軽いノリでこのテーマに
【自分的レシピ】elixirでの再帰関数の動かし方でも触れたように
関数型言語には一般的にfor文のような、いわゆるループ処理は用意されていない
じゃあ、どうやって書くのよ?という問いに対する答えは「再帰関数」を使おう
再帰関数ってなんぞ
関数が自分自身を呼び出す処理のこと
5回だけ「"hello"」と出力する再帰関数を作るとする
コードに落とし込むとこんな感じ
defmodule Sample do def hello do IO.puts("hello") hello() end end Sample.hello()
関数が自分自身を呼び出しているのが分かる
ただし、このままこのコードを実行するとまずい。無限再帰になってしまう
「いつ停止するの?」という問題がある
今回は5回「"hello"」を出力させた時点で再帰を終了させる必要がある
これは「停止性」といい、本当にこのコードが終了するかを保証する必要がある(停止性の議論)
たとえば5回で終了させたいならばこんな風に書き換える
defmodule Sample do def hello(counter) do if counter == 5 do :fin else IO.puts("hello") hello(counter+1) end end end Sample.hello(0) # hello # hello # hello # hello # hello
きちんと再帰が停止した
helloの引数に追加したcounterというのはアキュムレーターと呼ばれるもので
貯蔵庫のような意味があり、状態を保管するために使用している
今回の場合は何回呼び出したか?ということをカウントするためのカウンターとして扱っている
アキュムレーターについての詳しく話はこちらで解説しているのでぜひ
ただこのコードはElixirっぽいくない上にダサいので書き換える
defmodule Sample do def hello(counter) when counter == 5, do: :fin def hello(counter) do IO.puts("hello") hello(counter+1) end end Sample.hello(0) # hello # hello # hello # hello # hello
これでも上手く動く
defmodule Sample do def hello(5), do: :fin def hello(counter) do IO.puts("hello") hello(counter+1) end end
参加者の方が面白い書き方を発見した(凄い
「helloにdefaultの値を持たせたらhelloの呼び出し時に0を渡す必要がなくなるのでは?」
fmfm...
defmodule Sample do def hello(5), do: :fin def hello(counter \\ 0) do IO.puts("hello") hello(counter+1) end end Sample.hello()
実行すると上手く動くが以下のような警告が出る
warning: definitions with multiple clauses and default values require a header. Instead of:
def foo(:first_clause, b \ :default) do ... end
def foo(:second_clause, b) do ... end
errorの意味は分かるが、修正の方法が思いつかず
再帰関数作るときはアキュムレーターにdefault引数渡すの良くなさそうということで一旦落ち着いた
詳しい原因をご存知の方は教えてください
再帰関数で色々作って遊んでみる
配列の先頭の要素を取得して出力する
headとtailを使って実装する
リストが空となった場合に停止するように関数を実装する
sample = [1,2,3,4,5] [head | tail] = sample
defmodule Sample do def fetch([]), do: :fin def fetch([head | tail]) do IO.puts(head) fetch(tail) end end Sample.fetch([1,2,3,4,5]) # 1 # 2 # 3 # 4 # 5
配列の要素を合計する
アキュムレーターに先頭の要素をどんどん足していく
defmodule Sample do def sum([], accum), do: accum def sum([head | tail], accum), do: sum(tail, accum+head) end IO.puts(Sample.sum([1,2,3,4,5], 0)) #15
このままだと呼び出し時に0を渡すという操作があり
アキュムレーターの知識がない人には煩わしいのでhelper関数というものでラップする
defmodule Sample do def sum(lst), do: _sum(lst, 0) defp _sum([], accum), do: accum defp _sum([head | tail], accum), do: _sum(tail, accum+head) end IO.puts(Sample.sum([1,2,3,4,5])) #15
これならリストを渡すだけでsumをアキュムレーターを意識せずに使用することが可能
配列の大きさをカウントする
defmodule Sample do def length([], accum), do: accum def length([_head | tail], accum), do: length(tail, accum+1) end IO.puts(Sample.length([1,2,3,4,5], 0)) #5
良い感じですね
言語に実装されているEnumの関数は再帰関数を使えば再現することが可能
明日から新たなモジュールを開発できる。やったぜ
悲しいお知らせ
本日の勉強会で作成した再帰関数はお察しの通り、大体はEnumの関数やらの組み合わせで作成することが可能
再帰関数を作成するまえに必ず、この処理はEnumやらを使って実装することが出来ないかを考える必要がある
再帰関数で書いてもいいけどね。スピード感を大事にしたい
嬉しいお知らせ
20190601(土)に東京で開催される
Erlang & Elixir Fest 2019に行って参ります
恥ずかしながら、東京にいくのは人生で2度目で迷わずに行けるかが心配..
清流elixirを代表して参加しようと思っていたら
いつも勉強会に来て頂ける参加者の方が全員参加するようでワロタ
こんなすげー方達の話を聞ける機会は滅多にないので楽しんできます
次回の勉強会はElixirの実装力を上げるために競プロ問題をいくつかElixirで解いてみようと思ってます