50
43

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Elixirで面食らった演算子・記法まとめ

Last updated at Posted at 2016-09-14

はじめに

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を触られる方の一助になれば幸いです :relaxed:

50
43
7

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
50
43

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?