Elixirを勉強した時に感じた、Elixirのちょっと特殊な文法を書いています。
特殊かどうかは人それぞれですが、あくまで個人的な考えです。
doブロック
複数の式をグループ化し、他のコードに渡すためのものです。
モジュールや名前付き関数の定義、制御構造などで使われます。
def double(n) do
n * 2
end
do..end
はただの糖衣構文で、次のようにも書けます。
def double(n), do: n * 2
括弧でグループ化することで、do: に複数行を渡すことができます。
iex> if true, do: (
...> a = 1 + 2
...> a + 10
...> )
13
do...end
はコンパイル時にdo:
形式に変換されます。do:
形式は単なるキーワードリストの一単語です。
iex> if false, do: :this, else: :that
:that
主に、一行のブロックのときにはdo:
形式、複数行のときにはdo...end
形式を使います。
パイプライン演算子(|>)
左辺の関数の戻り値が右辺の関数の第一引数として渡されます。
例えば、リストをソートして反転した上で、カンマで連結するといった処理を行うとして
パイプ演算子を利用しない場合、次のように処理の順番と逆順に記述することになります。
iex> Enum.join(Enum.reverse(Enum.sort([1,3,2,5,4])), ",")
"5,4,3,2,1"
パイプ演算子を利用すれば、処理の順番通りに記述できるので分かりやすいです。
iex> [1,3,2,5,4] |> Enum.sort |> Enum.reverse |> Enum.join(",")
"5,4,3,2,1"
<>演算子
iex> "hoge" <> "fuga"
"hogefuga"
++ 演算子
iex> [1, 2] ++ [3, 4, 5]
[1, 2, 3, 4, 5]
-- 演算子
iex> [1, 2, 3, 4, 5] -- [5, 6]
[1, 2, 3, 4]
hd と tl 関数
リストの頭部(head)と尾部(tail)を取得します。
iex> hd [1, 2, 3, 4, 5]
1
iex> tl [1, 2, 3, 4, 5]
[2, 3, 4, 5]
cons 演算子(|)
右辺がリストの場合、左辺の値を右辺のリストの先頭に追加します。
iex> a = [1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
iex> [0 | a]
[0, 1, 2, 3, 4, 5]
リストを頭部と尾部に分けるのに|
を使うこともできます。
iex> [head | tail] = [1, 2, 3, 4, 5]
[1, 2, 3, 4, 5]
iex> head
1
iex> tail
[2, 3, 4, 5]
無名関数
文字列やアトムなどと同様に「値」の一種です。次のように無名関数を定義して変数f
にセットします。
iex> f = fn (a, b) -> a + b end
iex> f.(2, 3)
5
キャプチャ演算子(&)
関数捕捉演算子とも呼ばれます。&
を付けることで、関数を無名関数に変換することができます。
iex> f = &Enum.sort/1
iex> f.([1, 3, 2, 5, 4])
[1, 2, 3, 4, 5]
&
のもう一つの用法は無名関数の省略記法です。引数を&1
、&2
など(プレースホルだーと呼ぶ)として扱うことができます。
iex> f = &(&1 + &2)
iex> f.(2, 3)
5
ガード節
when
で始まる式を付加することができます。
iex> case {1, 0, 3} do
...> {1, x, 3} when x > 0 -> "マッチする"
...> _ -> "マッチしない"
...> end
"マッチしない"
関数の引数に条件を付けることで、同じ関数名で異なる処理を実現することができます。
defmodule Hoge do
def hello(names) when is_list(names) do
for name <- names do
Hoge.hello(name)
end
end
def hello(name) do
IO.puts "Hello, #{name}"
end
end
iex> Hoge.hello(["Alice", "Bob"])
Hello, Alice
Hello, Bob
無名関数でもガード節を使うことができます。
double = fn
n when is_integer(n) -> n * 2
x when is_float(x) -> x * 2
s when is_binary(s) -> s <> s
end
iex> double.(1)
2
iex> double.(0.5)
1.0
iex> double.("abc")
"abcabc"
ガード節に使える式はこちらです。
https://hexdocs.pm/elixir/master/guards.html
first..last
1..10
のように範囲(Range)を定義します。内部では構造体として表現されます。
範囲の作成とマッチングの最も一般的なフォームはKernel
から自動インポートされる../2
マクロによってです。
first..last
Returns a range with the specified first and last integers
iex> range = 1..10
1..10
iex> first .. last = range
1..10
iex> first
1
iex> last
10
iex> range.__struct__
Range
with式
マッチング節(matching clauses)の結合をするために使います。
全ての節がマッチしたら、do
ブロックが実行されてその結果を返します。
iex> width = nil
iex> opts = %{width: 10, height: 15}
iex> with {:ok, width} <- Map.fetch(opts, :width),
...> double_width = width * 2,
...> {:ok, height} <- Map.fetch(opts, :height),
...> do: {:ok, double_width * height}
{:ok, 300}
iex> width
nil
with
式の内側の変数はwith
式の中でのみ有効です。
そのため、内側の変数width
は外側のスコープの変数width
には影響しません。
double_width
はdo
ブロックでもスコープに入っています。
マッチに失敗すると評価を止めて右辺のマッチしない場合の値を返します。
iex> opts = %{width: 10}
iex> with {:ok, width} <- Map.fetch(opts, :width),
...> {:ok, height} <- Map.fetch(opts, :height),
...> do: {:ok, width * height}
:error
この場合は、:height
がないのでMap.fetch/2
は:error
を返します。