はじめに
7月くらいから業務でElixirを書くようになり、見たことも書いたこともない状態からなんとか見よう見まねでひと通りのことは書けるようになってきました。
Elixirを書き始めて面食らった演算子や記法が多かったので、ここにまとめてみようと思います。なお筆者は7月以前主にrubyを書いていたのでrubyとの比較例が多いです。
なお、こちらのエントリに記載しているコンソール上での例はElixir 1.3.0で動作させています。
演算子編
パイプ演算子
iex(1)> sample = [1, 2, 3]
[1, 2, 3]
iex(2)> Enum.count(sample)
3
iex(3)> sample |> Enum.count
3
|>
の左側に記述した変数が、右側の関数の第一引数として渡されます。
例えば、あるパラメータ params
から
-
A module
で必要なデータを取得 -
B module
で取得したデータをフィルタリング -
C module
でフィルタリングしたデータを整形する
といった処理を行う時
rubyでは
result = C.format(B.filter(A.fetch(params)))
ないし
fetched_data = A.fetch(params)
filtered_data = B.filter(fetched_data)
formatted_data = C.format(filtered_data)
のように記述するかと思います。
elixirでも、パイプ演算子を利用しない場合には
result = C.format(B.filter(A.fetch(params)))
と処理を行う順番とは逆順に左側から利用するmethodを記述することになります。
これをパイプ演算子を利用すると
result = params
|> A.fetch
|> B.filter
|> C.format
のように記述することが出来ます。
パイプ演算子を利用すると、処理の順番通りに記述することができ直感的に params
の操作を理解することが出来ますね。
マッチ演算子
パイプ演算子の例でサラッと書いてしまいましたが、 Elixir では =
はマッチ演算子として使われています。
この演算子の左側に記述された変数に、右側に記述された内容を代入するようになっています。
簡単な例は以下です。
iex(4)> sample = 1
1
iex(5)> 1 = sample
1
iex(6)> 2 = sample
** (MatchError) no match of right hand side value: 1
最後の行で、2にsampleをマッチさせようとして MatchError
がraiseされているのが分かります。
また、マッチ演算子の強力な所は、右辺に記述されたコレクションの構造を利用して以下のように複数の変数に代入することが出来る点です。
iex(7)> sample = [1, 2, 3]
[1, 2, 3]
iex(8)> [element1, element2, element3] = sample
[1, 2, 3]
iex(9)> element1
1
iex(10)> element2
2
iex(11)> element3
3
iex(1)> map = %{key1: "value1", key2: "value2"}
%{key1: "value1", key2: "value2"}
iex(2)> %{key1: value1, key2: value2} = map
%{key1: "value1", key2: "value2"}
iex(3)> value1
"value1"
iex(4)> value2
"value2"
rubyでは同じような書き方では複数にマッチさせて代入するという書き方がHashの場合はできません。
irb(main):008:0> sample = [1, 2, 3]
=> [1, 2, 3]
irb(main):009:0> (element1, element2, element3) = sample
=> [1, 2, 3]
irb(main):010:0> element1
=> 1
irb(main):011:0> element2
=> 2
irb(main):012:0> element3
=> 3
irb(main):013:0> hash = {key1: "value1", key2: "value2"}
=> {:key1=>"value1", :key2=>"value2"}
irb(main):014:0> {key1: value1, key2: value2} = hash
SyntaxError: (irb):14: syntax error, unexpected '=', expecting end-of-input
{key1: value1, key2: value2} = hash
ピン演算子
マッチ演算子の左辺に置いた変数に対し、右辺に記述した内容で代入せず固定しておきたい場合に使います。
iex(14)> sample = 1
1
iex(15)> ^sample = 2
** (MatchError) no match of right hand side value: 2
色々なリファレンスでは、このような例が多いですが実際の業務では余り利用しませんでした…
Ecto.Query
というクエリを扱うmoduleでクエリを書く時に出てくる ^
の記法ですが、リファレンス を読むと
External values and Elixir expressions can be injected into a query expression with ^
ということなので、外部の変数やElixirの式をクエリ内で展開する場合は ^
で囲んでおきましょうというところですね。
ライブラリの内部実装を確認したところ(gist)、この実装はピン演算子として処理されているものと思われます。
iex(23)> User |> where([user], user.id == ^user_id)
#Ecto.Query<from s in User, where: s.id == ^1>
iex(24)> User |> where([user], user.id == ^(1 + 2))
#Ecto.Query<from s in User, where: s.id == ^3>
記法編
コレクション
Map
キーが Atom
であれば、ドット記法を使うことが出来ます。
iex(1)> map = %{key1: "value1", key2: "value2"}
%{key1: "value1", key2: "value2"}
iex(2)> map[:key1]
"value1"
iex(3)> map.key1
"value1"
iex(4)>
List
head
, tail
でそれぞれ「先頭の要素」「先頭以外の要素」を取得することが出来ます。
iex(4)> list = [1, 2, 3]
[1, 2, 3]
iex(5)> [head | tail] = list
[1, 2, 3]
iex(6)> head
1
iex(7)> tail
[2, 3]
このあたりはLispのcar, cdrと似ているので触ったことのある方は概念の理解がしやすいと思います。
(car '(1 2 3 4))
=>1
(cdr '(1 2 3 4))
=>(2 3 4)
無名関数
iex(27)> fn(a, b) -> a + b end
#Function<12.52032458/2 in :erl_eval.expr/5>
無名関数は変数にマッチさせることが出来、これを利用して呼ぶことが出来ます。呼び出すときには .
を付けて呼ぶことに注意です。
iex(28)> sum = fn(a, b) -> a + b end
#Function<12.52032458/2 in :erl_eval.expr/5>
iex(30)> sum.(1, 2)
3
またfn -> end
までのくくりを &()
、第一引数・第二引数…を &1, &2, ...
として簡略化することが出来ます。
iex(32)> sum = &(&1 + &2)
&:erlang.+/2
iex(33)> sum.(1, 2)
3
alias
あるmoduleから別のmoduleのメソッドを利用する場合に、いくつか依存を宣言する記述の仕方がありますが、 alias
はmoduleに別名をつけることが出来ます。
defmodule Hoge.Huga.A do
def do_something do
Hoge.Huga.B.some_method
end
end
通常上のように記述するところを、 alias
を利用すると以下のようにシンプルに書けます。
defmodule Hoge.Huga.A do
alias Hoge.Huga.B
def do_something do
B.some_method
end
end
また、 as:
オプションを利用すると、オプションで指定したmodule名で呼ぶことが出来ます。
defmodule Hoge.Huga.A do
alias Hoge.Huga.B, as: C
def do_something do
C.some_method
end
end
関数のガード節
引数に条件を付けて、同じ関数名で別の処理を書くことが出来ます。
defmodule Sample do
def func1(x) when is_number(x) do
IO.puts "#{x} is a number"
end
def func1(x) when is_atom(x) do
IO.puts "#{x} is an atom"
end
end
with式
処理の過程で一時的な変数が必要になった場合に、スコープを限定する場合に利用します。
下記の例では params
という変数が2回登場していますが、with式内に定義されたものはそのスコープ外では参照できないようになっています。
iex(8)> params = %{hoge: "huga"}
%{hoge: "huga"}
iex(9)> result = with params = %{another_hoge: "another_huga"} do "#{params[:another_hoge]}" end
"another_huga"
iex(10)> params
%{hoge: "huga"}
要素の結合
配列の結合は ++
で記述します。
iex(18)> array1 = [1, 2, 3]
[1, 2, 3]
iex(19)> array2 = [4, 5, 6]
[4, 5, 6]
iex(20)> array1 ++ array2
[1, 2, 3, 4, 5, 6]
iex(21)> array1 + array2
** (ArithmeticError) bad argument in arithmetic expression
:erlang.+([1, 2, 3], [4, 5, 6])
文字列の結合は <>
で記述します。
iex(21)> string1 = "hoge"
"hoge"
iex(22)> string2 = "huga"
"huga"
iex(23)> string1 <> string2
"hogehuga"
iex(24)> string1 + string2
** (ArithmeticError) bad argument in arithmetic expression
:erlang.+("hoge", "huga")
数値の場合は +
で記述します。
iex(31)> 1 + 2
3
rubyの場合は全て +
で記述していたので、これに慣れているともどかしいところです。。
おわりに
Elixirの演算子や記法は、他の言語であまり見られないものが色々とあり、初学の状態ではコードの内容を理解するのに苦労したためこのエントリを書いてみました。これからElixirを触られる方の一助になれば幸いです