#はじめに
前回はElixir超初級と称してどちらかというと概念的なところにフォーカスして記事を書きました
前回の記事→【Elixir超初級】代入、不変性、パターンマッチについて
今回は関数型言語のキモとも言える、関数について個人的にまとめていこうと思います。
前回の内容に比べ、関数については概念的な違いはあまりなく他の言語と同じように処理のまとまりです。
##無名関数
無名関数の基本形はfnとendで囲み、アロー(->)の左側に引数、右側に処理を記述する形となります。
関数の定義部分の引数の()は省略可能です。
sum = fn (a,b) -> a + b end
#sum = fn a,b -> a + b end でもOK
sum.(1,2)
# 3
一応、RubyとJavaScriptでも同じようなコードを書いてみました。ElixirはRubyを意識して作られているとはいいますが、結構違うような気がします。個人的にはJS書き方のほうが近く感じるかも…
sum = ->(a,b){a + b}
sum.call(1,2)
const sum = (a,b) => {
const result = a + b
return result
}
sum(1,2)
引数がなかったとしても()は記述する必要があります。
say = fn -> IO.puts "こんにちは" end
say.() #引数がなくても()は必要
###無名関数のパターンマッチ
前回の記事でElixirには代入はないと説明しました。
うっかりここで「sum.(1,2)は引数のaとbに値(1と2)を代入しているんだな」と考えたらアウトです。
Elixirに代入はないのです。
sum = fn 1,b -> 1 + b end
sum.(1,2)
# 3
この代入しないでパターンマッチを行うことを使って以下のようなコードも書けます。
Elixirでは一つの関数に複数の処理を含めることができます。そして引数のマッチした値によって処理を切り替えることもできます。
hoge = fn
1,b -> 1 + b
2,b -> 2 * b
3,b -> 3 - b
end
hoge.(1,2)
# 3
hoge.(2,10)
# 20
hoge.(3,1)
#2
hoge.(4,2)
#no function clause matching in anonymous
アンダースコア(_)を使えばすべてとマッチするので「それ以外」というのを表現できます。
hoge = fn
1,b -> 1 + b
2,b -> 2 * b
3,b -> 3 - b
_,b -> IO.puts "1~3以外が最初の引数のものは計算できません"
end
hoge.(1,2)
# 3
hoge.(2,10)
# 20
hoge.(3,1)
#2
hoge.(4,2)
# 1~3以外が最初の引数のものは計算できません
#※ただしこの書き方だと引数bを使っていないためwarning: variable "b" is unusedと警告が発生する。
#これを回避するには引数を(_,_b)にすればよい。詳しくは別の記事で
###無名関数でFizzBuzz
今まで学習してきた無名関数の機能を使ってFizzBuzzをしてみました。パターンマッチを使えばif文無しで実装することができます。
さらっとでてきましたがremというのは左の値を右の値で割った余りを変えす関数です。
func = fn
0,0,_ -> IO.puts "fizzbuzz"
0,_,_ -> IO.puts "fizz"
_,0,_ -> IO.puts "buzz"
_,_,a -> IO.puts a
end
fizzbuzz = fn n -> func.(rem(n,3),rem(n,5),n) end
fizzbuzz.(1) # 1
fizzbuzz.(3) # fizz
fizzbuzz.(5) #buzz
fizzbuzz.(15) #fizzbuzz
【発展】関数で関数を返す(カリー化、部分適用)
##モジュールと名前付き関数
モジュールと名前付き関数は書き方と見た目については少しRubyに似ています。(def~endで表現する感じが)
defmodule Sum do
def plusone(n) do
n + 1
end
end
IO.puts Sum.plusone(2)
# 3
module Sum
def plusone(n)
n + 1
end
module_function :plusone
end
p Sum.plusone(2)
# 3
###関数の処理はブロック
def~endで囲って記述しましたがこのようにdef func(n), do: ~のように書くこともできます。
関数名の後にカンマ(,)が付き、doの後にコロン(:)が付きます。そしてendは省略します。
そして括弧で囲めば複数行を渡すこともできます。
defmodule Sum do
def plusone(n), do: n + 1
end
IO.puts Sum.plusone(2)
# 3
defmodule Sum do
def plusone(n), do: (
IO.puts "1と#{n}を足します"
n + 1
)
end
IO.puts Sum.plusone(2)
# 1と2を足します
# 3
###名前付き関数のパターンマッチ
基本的な部分は無名関数と同じです。渡されたパラメータをもとにパターンマッチをします。
少し困惑するところは同じ名前の関数を複数定義できるというところです。
はじめの定義からパターンマッチを行っていってマッチしなかったら次の関数定義へと順に実行していきます。
これを利用すればfor文を使わずにループ処理が行えます。
最初の定義double(0)は引数が0以外にはマッチしません。
次の定義のdouble(n)は0以外の値にはマッチするので最初の引数10でマッチします。
先ほどの「関数の処理はブロック」で学んだように処理を括弧で囲んで複数行渡します。引数を2倍にした値をコンソールに出力後、関数(double(n-1))を呼んでいます。
するとdouble(9)が実行されdouble(n)の定義にマッチ、再び処理が行われ今度はdouble(8)・・・とループを行います。
そして最後にdouble(0)となったときはじめて最初の定義double(0)にマッチして0を返して終了します。
defmodule Sum do
def double(0), do: 0
def double(n), do: (
IO.puts n * 2
double(n - 1)
)
end
IO.puts Sum.double(10)
# 20 18 16 14 12 10... 0
これを再帰処理、再帰呼び出し*といいElixirでは割と頻繁に出てきます。
###名前付き関数でFizzBuzz(ループ有)
無名関数の時はループで処理できていないので、ここでもう一度再帰処理を利用してfizzbuzzを名前付き関数で実行してみました。
defmodule FizzBuzz do
def fizzbuzz(n,0,0), do: IO.puts "fizzbuzz"
def fizzbuzz(n,0,_), do: IO.puts "fizz"
def fizzbuzz(n,_,0), do: IO.puts "buzz"
def fizzbuzz(n,_,_), do: IO.puts n
def run(0), do: 0
def run(n), do: (
fizzbuzz(n,rem(n,3),rem(n,5))
run(n - 1)
)
end
FizzBuzz.run(100)
追記予定
##参考書籍
プログラミングElixir