LoginSignup
12
4

More than 5 years have passed since last update.

Elixirのちょっと特殊な文法のまとめ

Posted at

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で始まる式を付加することができます。

case式のガード節
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_widthdoブロックでもスコープに入っています。
マッチに失敗すると評価を止めて右辺のマッチしない場合の値を返します。

マッチ失敗
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を返します。

12
4
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
12
4