Elixirのすべての機能は紹介しきれないけど、とりあえずこれだけわかればfizzbuzzくらいは書けるでしょ程度の言語機能の一覧です。 並行処理関連の話とかは全く書いてないので注意。

リテラル

数値

整数は10進数、16進数、8進数、2進数が扱える

ex(1)> 123
123
iex(2)> 0x123
291
iex(3)> 0o123
83
iex(8)> 0b1111011
123

浮動小数点数も通常通りの記法。

iex(9)> 3.1415
3.1415
iex(10)> 1.2345e-3
0.0012345

アトム

アトム。言語によってはシンボルとかキーワードとか呼ばれるやつ。RubyとかLispとかPrologとかやってない人にはあまり馴染みがないかもしれない。

iex(13)> :hoge
:hoge
iex(14)> :hoge@fuga # なぜかアトムの名前には@も使える
:hoge@fuga
iex(15)> :foobar?
:foobar?
iex(16)> :baz!
:baz!
iex(17)> :"long name"
:"long name"

範囲

a..b は閉区間 [a, b] を表す範囲を意味する

iex(18)> 1..3
1..3
iex(19)> Enum.to_list 1..3
[1, 2, 3]

真偽値

true, false 。これらはそれぞれ :true, :false と等しい。

iex(21)> true and false
false
iex(23)> :true or :false
true

nil

null みたいなやつ。ほとんどの場合では、 nil false のみが偽として扱われる。

iex(24)> if nil, do: IO.puts "never called"
nil

タプル

固定された個数の要素の組。波括弧使うのは斬新。

iex(25)> { :ok, 3 }
{:ok, 3}
iex(26)> { "Taro", 22, "Hokkaido" }
{"Taro", 22, "Hokkaido"}

リスト

角括弧で括られたカンマ区切りの式の列はリストになる。配列リテラルみたいなのはなさそう。

iex(30)> [1, 2, 3, 4]
[1, 2, 3, 4]
iex(31)> ["hoge", true, 0.123] # 静的型付け言語じゃないからね……
["hoge", true, 0.123]

ちなみにLispと違って nil[] は別物。

iex(29)> [] == nil
false

cons 的な操作は [head | tail] と書く

iex(32)> tail = [3, 4, 5]
[3, 4, 5]
iex(33)> [2 | tail]
[2, 3, 4, 5]
iex(34)> [1, 2 | tail]
[1, 2, 3, 4, 5]

いわゆる improper list も作れるけど特に使い道はない

iex(19)> [ 1, 2 | 3 ]
[1, 2 | 3]

キーワードリスト

キーがアトムであるようなalistのことをキーワードリストという。キーワードリストはよく使われるので少し簡単に書けるようになっている。

iex(36)> [hoge: 3, fuga: 2] # これは以下と同等
[hoge: 3, fuga: 2]
iex(35)> [{:hoge, 3}, {:fuga, 2}]
[hoge: 3, fuga: 2]

マップ

いわゆる連想配列とかdictとか(悪い言い方だけど)ハッシュとか言われるやつ。

iex(37)> %{"hoge" => "fuga", "foo" => "bar", "baz" => nil}
%{"baz" => nil, "foo" => "bar", "hoge" => "fuga"}
iex(38)> %{hoge: "fuga", foo: :bar, baz: nil} # キーがアトムのときは省略記法がある
%{baz: nil, foo: :bar, hoge: "fuga"}

ちなみに内部実装としてはいわゆるハッシュテーブルではなくて、ハッシュ値をtrie木で管理しているらしい。

要素へのアクセスの方法は以下の通り。

iex(39)> map = %{hoge: "fuga", foo: :bar, baz: nil}
%{baz: nil, foo: :bar, hoge: "fuga"}
iex(41)> map[:hoge]
"fuga"
iex(40)> map.hoge # キーがシンボルの場合はこんな風に書ける
"fuga"

バイナリ

ビットの列。 <<...>> のような形で書く。

iex(43)> << 0, 1, 2, 3 >>
<<0, 1, 2, 3>>

バイナリの要素の型は binary, bits, bitstring, bytes, float, integer, utf8, utf16, utf32が使える。また、修飾子として、

  • size(n): 要素のサイズ(ビット数)
  • signed, unsigned: 符号付きかどうか
  • big, little, naive: エンディアン

をそれぞれ指定できる。

iex(44)> << 0 :: size(8), 1 :: size(1), 2 :: size(3), 5 :: size(2) >>
<<0, 41::size(6)>> # 後ろの3項がまとめられた
iex(45)> 0b1_010_01 # == << 1, 2, 5 :: size(2) >>
41
iex(48)> << -5 :: signed-integer-size(16) >>
<<255, 251>>
iex(49)> << -5 :: unsigned-integer-size(8) >>
<<251>>

文字のリスト

シングルクォートで括られた「文字列」は、各文字を表すコードポイントのリストとなる。

iex(53)> 'abc' # これは以下と等しい
'abc'
iex(54)> [97, 98, 99]
'abc'

文字列

ダブルクォートで括られた「文字列」は、UTF8エンコードされたバイト列(バイナリ)となる。Elixirでは通常こちらのほうを文字列と呼ぶ。リストとバイナリの両方のリテラルがあるのはたぶん歴史的経緯。

iex(55)> "abc"
"abc"
iex(56)> << 97 :: utf8, 98 :: utf8, 99 :: utf8 >>
"abc"

ちなみに、文字のリストも文字列も #{...}{...} の部分の評価結果を埋め込むことができる。

iex(35)> name = "John"
"John"
iex(36)> "Hello, #{name}"
"Hello, John"
iex(37)> 'Hello, #{String.upcase(name)}'
'Hello, JOHN'

また、文字のリストも文字列も3つの引用符で括るヒアドキュメントが使える

defmodule Hoge do
  # 三重の引用符で囲むと、インデントの部分がキャンセルされる。
  def heredoc_str do
    """
    This
    is
    a
    very
    long
    text
    """
  end

  def heredoc_list do
    '''
    This
    is
    also
    a
    very
    long
    text
    '''
  end
end
iex(28)> IO.puts Hoge.heredoc_str
This
is
a
very
long
text

:ok
iex(29)> IO.puts Hoge.heredoc_list
This
is
also
a
very
long
text

:ok

代入

左辺は変数ではなくパターン(パターンの詳細については後述)。なので、

iex(1)> x = %{ hoge: 3, fuga: 4 }
%{fuga: 4, hoge: 3}

はもちろんOKな上に、

iex(3)> 1 = 1
1

も、

iex(4)> x
%{fuga: 4, hoge: 3}
iex(5)> %{ fuga: fuga } = x
%{fuga: 4, hoge: 3}
iex(6)> fuga
4

もOK

パターン

数値、リスト、マップ、タプル、構造体(後述)など、基本的なリテラルはパターンとして扱える。

iex(20)> [ head | tail ] = Enum.to_list 1..5
[1, 2, 3, 4, 5]
iex(21)> head
1
iex(22)> tail
[2, 3, 4, 5]
iex(23)> { x, y } = { 3, 4 }
{3, 4}
iex(24)> x
3
iex(25)> y
4

いわゆるasパターンは pat1 = pat2 の形で書く。左辺と右辺はたぶん等価

iex(26)> [ %{ name: name, age: age } = head | tail ] = [ %{ name: "John Doe", age: 20 } ]
[%{age: 20, name: "John Doe"}]
iex(27)> [ head = %{ name: name, age: age } | tail ] = [ %{ name: "John Doe", age: 20 } ]
[%{age: 20, name: "John Doe"}]

バイナリはサイズ指定してパターンマッチできる

iex(38)> << first_bit :: size(1), rest_bits :: size(31) >> = << 0xdeadbeef :: size(32) >>
<<222, 173, 190, 239>>
iex(39)> first_bit
1
iex(40)> rest_bits
1588444911
iex(41)> 0x5eadbeef
1588444911

pin operator

パターンの中に特定の変数の値を使いたいときは、その変数の前に^(ピン演算子)を前置する。

iex(110)> x = 3
3
iex(111)> %{ hoge: x } = %{ hoge: 4 } # この場合、xに4が束縛される
%{hoge: 4}
iex(112)> x
4
iex(113)> x = 3
3
iex(114)> %{ hoge: ^x } = %{ hoge: 4 } # ピン演算子を使うことで、 %{ hoge: 3 }を解釈される
** (MatchError) no match of right hand side value: %{hoge: 4}

iex(114)> %{ hoge: ^x } = %{ hoge: 3 }
%{hoge: 3}
iex(115)> x
3

関数

基本的な定義方法

モジュール内でのみ定義可。

defmodule Hoge do
  def func(x, y) do
    x + y
  end
end

ちなみに

xyzzy do
  ...
end

は、

xyzzy do: ...

のシンタックスシュガー。これはdef以外でも使えるので、自分でマクロ書く時とかに便利。 関数の呼び方は以下の通り。

iex(13)> Hoge.func(3, 2)
5

(モジュール名を表すアトム).(関数名) で関数を呼べる。ちょっと脱線するけど、大文字で始まるトークンNameは頭に "Elixir." とつけられたアトム :"Elixir.Name" として解釈される。

iex(42)> IO.puts "hoge"
hoge
:ok
iex(43)> :"Elixir.IO".puts "hoge"
hoge
:ok
iex(44)> IO == :"Elixir.IO"
true

関数定義と呼び出しの括弧は省略できる。Rubyっぽい。

defmodule Hoge do
  def func x, y do
    x + y
  end
end
iex(14)> Hoge.func 3, 2
5

引数のパターンマッチ

例によって引数はパターン。

defmodule Hoge do
  def norm(%{ x: x, y: y }), do: :math.sqrt(:math.pow(x, 2) + :math.pow(y, 2))
end

(ちなみに、ここで出て来る:mathはErlangのモジュール)

パターンごとに関数の定義を分けることもできる。Haskellっぽい。

defmodule Hoge do
  def norm(%{ x: x, y: y, z: z }) do
    :math.sqrt(:math.pow(x, 2) + :math.pow(y, 2) + :math.pow(z, 2))
  end
  def norm(%{ x: x, y: y }) do
    :math.sqrt(:math.pow(x, 2) + :math.pow(y, 2))
  end
end
iex(50)> Hoge.norm(%{ x: 3, y: 4 })
5.0
iex(51)> Hoge.norm(%{ x: 3, y: 4, z: 1 })
5.0990195135927845

ガードパターンは以下のように書く

defmodule Hoge do
  def describe(x) when is_integer(x), do: "integer"
  def describe(x) when is_atom(x), do: "atom"
end
iex(53)> Hoge.describe(5)
"integer"
iex(54)> Hoge.describe(:hoge)
"atom"

ただし、ガードパターンに書ける述語は限られたものしか使えない。詳しくは以下を参照。

https://hexdocs.pm/elixir/master/guards.html

キーワード引数

キーワードリストを渡すための特別なシンタックスシュガーがある。

defmodule Hoge do
  def hoge args do
    IO.inspect args
  end
end
iex(77)> Hoge.hoge fuga: "foo", bar: "baz"
[fuga: "foo", bar: "baz"]
[fuga: "foo", bar: "baz"]

パラメータの側も同じような省略記法が使える

defmodule Hoge do
  def hoge fuga: fuga, foo: foo do
    { fuga, foo }
  end
end
iex(79)> Hoge.hoge fuga: "piyo", foo: "bar"
{"piyo", "bar"}

デフォルト引数

引数を省略可能にして、デフォルト値をつけることができる。

defmodule Hoge do
  def incr(x, dx \\ 1), do: x + dx
end
iex(56)> Hoge.incr 3
4
iex(57)> Hoge.incr 3, 2
5

デフォルト引数のせいで引数の数とかが紛らわしいことになった場合はコンパイル時にエラーにしてくれる。

defmodule Hoge do
  def incr(x, dx \\ 1), do: x + dx
  def incr(x), do: x + 1
  # => def incr/1 conflicts with defaults from incr/2
end

プライベートな関数定義

defp でプライベートな関数を定義できる。

defmodule Hoge do
  def you_may_call_it(x) do
    IO.puts "ok :)"
  end

  defp _do_not_call_it(x) do
    raise "fxxk!!!"
  end
end
iex(59)> Hoge.you_may_call_it(3)
ok :)
:ok
iex(60)> Hoge._do_not_call_it(5)
** (UndefinedFunctionError) function Hoge._do_not_call_it/1 is undefined or private
    Hoge._do_not_call_it(5)

パイプ演算子

x |> f(a1, .., aN)f(x, a1, .., aN) と解釈される。

iex(116)> Enum.each(1..10, fn x -> IO.puts x end)
1
2
3
4
5
6
7
8
9
10
:ok
iex(117)> 1..10 |> Enum.each(fn x -> IO.puts x end) # これは上の式と同じ意味になる。
1
2
3
4
5
6
7
8
9
10
:ok

Elmの |> 演算子にも似てるけど、Elixirの |> はマクロであり、構文木自体が書き換えられるという点でちょっと違う。

メソッドチェーンみたいに複数の関数を順番に適用したい時に便利。

iex(118)> 1..3 |> Enum.map(fn x -> x * 2 end) |> Enum.each(fn x -> IO.puts x end)
2
4
6
:ok

制御構文など

if, unless

あるけどそんな使わない。

iex(60)> x = 3
3
iex(61)> if x > 3 do
...(61)>   IO.puts "x > 3"
...(61)> else
...(61)>   IO.puts "x <= 3"
...(61)> end
x <= 3
:ok

cond

Lispとかのやつと一緒。ただし else 節が無いので、最後は true -> ... でしめる。

iex(63)> x = :hoge
:hoge
iex(65)> cond do
...(65)>   is_integer(x) -> "integer"
...(65)>   is_boolean(x) -> "bool"
...(65)>   is_atom(x) -> "atom"
...(65)>   true -> "other"
...(65)> end
"atom"

case

パターンマッチによる分岐。 when 節も使える。

defmodule Hoge do
  def print_name(name, country) do
    case name do
      %{ first: first, middle: middle, last: last } ->
        IO.puts "#{first} #{middle} #{last}"
      %{ first: first, last: last } when country == :jp ->
        IO.puts "#{last} #{first}"
      %{ first: first, last: last } ->
        IO.puts "#{first} #{last}"
    end
  end
end
iex(67)> Hoge.print_name %{ first: "Taro", last: "Yamada" }, :en
Taro Yamada
:ok
iex(68)> Hoge.print_name %{ first: "太郎", last: "山田" }, :jp
山田 太郎
:ok
iex(69)> Hoge.print_name %{ first: "Taro", middle: "John", last: "Yamada" }, :en
Taro John Yamada
:ok

with

他の関数型言語でいう let みたいなもん。

defmodule Hoge do
  def hoge do
    with x = 3,
         y = 2
      do x + y
    end
  end
end
iex(72)> Hoge.hoge
5

= じゃなくて <- で値を束縛することもできる。この場合、パターンにマッチしなかったらマッチしなかった値がそのまま返される。 一種のmonadic bindみたいなもん。型はつかないけど。 and-let とかにも似てる。

defmodule Hoge do
  def some_function_that_may_fail do
    { :err, "Damn"}
  end
  def do_awesome_thing1 do
    with { :ok, result } = some_function_that_may_fail,
      do: result
  end
  def do_awesome_thing2 do
    with { :ok, result } <- some_function_that_may_fail,
      do: result
  end
end
iex(75)> Hoge.do_awesome_thing1
** (MatchError) no match of right hand side value: {:err, "Damn"}
    hoge.exs:6: Hoge.do_awesome_thing1/0
iex(75)> Hoge.do_awesome_thing2
{:err, "Damn"}

構造体

構造体は一つのモジュールに一つだけ定義できる。構造体名はモジュール名と同名。

defmodule Person do
  defstruct name: "", age: 0
end

構造体は map と似たような方法で扱える

iex(82)> taro = %Person{ name: "Taro", age: 30 }
%Person{age: 30, name: "Taro"}
iex(83)> taro.name
"Taro"

というかそもそも構造体の正体はただの map

iex(86)> %{ __struct__: :"Elixir.Person", name: "Jiro", age: 5 }
%Person{age: 5, name: "Jiro"}

レコードの更新は以下のように行う。

iex(7)> taro = %Person{ name: "Taro", age: 20 }
%Person{age: 20, name: "Taro"}
iex(8)> %Person{ taro | age: 21 }
%Person{age: 21, name: "Taro"}

Elmの記法に似てるけど、 | の左が識別子だけじゃなくて任意の式が使えるのでElmより使いやすい。 ちなみに、同様の記法は map でも使える。

ラムダ関数

さっきまでにもちらっと登場したけど、基本の書き方は以下の通り。

fn arg1, arg2, .., argN -> body end

ラムダ関数の呼び出しには func.(arg1, .., argN) のようにする。

iex(119)> f = fn x, y -> x + y end
#Function<12.99386804/2 in :erl_eval.expr/5>
iex(120)> f.(2, 3)
5

略記法として、 &(expr...) という形がある。この中では i 番目の引数を &i という名前で使うことができる。

iex(121)> g = &(&1 + &2)
&:erlang.+/2
iex(122)> g.(2, 3)
5

さらに略記法として、 fn arg1, .., argN -> f(arg1, .., argN) end という形のラムダ関数は、単に &(モジュール名).(関数)/(arity) という形で書ける。

iex(123)> h = &:erlang.+/2
&:erlang.+/2
iex(124)> h.(2, 3)
5
iex(125)> i = &IO.puts/1
&IO.puts/1
iex(126)> i.("hoge")
hoge
:ok

Elixirでは同名の関数を引数の数ごとに複数定義できるので、一般に関数を指定するときはモジュール名、関数名、arityの3つを使って指定する。このへんPrologっぽい。

モジュールの読み込み

モジュールの読み込みは import 文で行える

iex(127)> puts "hoge" # putsはIOモジュールの関数
** (CompileError) iex:127: undefined function puts/1

iex(127)> import IO
IO
iex(128)> puts "hoge"
hoge
:ok

import の引数に only:, except: をつけることでインポートする/しない関数を指定できる。

defmodule Hoge do
  def foo, do: :Foo
  def bar, do: :Bar
  def bar(x), do: to_string(x)
end

only:, except: の引数は、 (関数名): (arity) という形のキーワードリスト

iex(131)> import Hoge, only: [ foo: 0, bar: 1 ] # Hoge.foo/0, Hoge.bar/1をimport
Hoge
iex(132)> foo
:Foo
iex(133)> bar # Hoge.bar/0はimportされていない
** (CompileError) iex:133: undefined function bar/0

iex(133)> bar 3
"3"

iex(2)> import Hoge, except: [ bar: 1 ]
Hoge
iex(3)> foo
:Foo
iex(4)> bar
:Bar
iex(5)> bar 3
** (CompileError) iex:5: undefined function bar/1

また、 alias でモジュールに別名をつけることもできる。

defmodule Very do
  defmodule Long do
    defmodule Module do
      defmodule Name do
        def hoge, do: "hogehoge"
      end
    end
  end
end

defmodule Very do
  defmodule Long do
    defmodule Module do
      defmodule Name do
        def hoge, do: "hogehoge"
      end
      defmodule Name2 do
        def fuga, do: "fugafuga"
      end
    end
  end
end

defmodule UseVeryLongModule do
  def hoge1 do
    Very.Long.Module.Name.hoge
  end

  def hoge2 do
    alias Very.Long.Module.Name, as: VLMN
    VLMN.hoge
  end

  def hoge3 do
    alias Very.Long.Module.Name # as: Name の省略
    Name.hoge
  end

  def hoge4 do
    alias Very.Long.Module.{Name, Name2} # 複数のモジュールに一括でaliasをつけられる
    Name.hoge
    Name2.fuga
  end
end

内包表記

Elixirでの内包表記の書き方は以下の通り

for x1 <- list1, x2 <- list2, .., xN <- listN, do: expr

x1, .., xNlist1, .., listN の各値の組に束縛した状態で expr が呼ばれ、結果のリストが返される。

iex(10)> for x <- 1..5, y <- 1..5, do: { x, y }
[{1, 1}, {1, 2}, {1, 3}, {1, 4}, {1, 5}, {2, 1}, {2, 2}, {2, 3}, {2, 4}, {2, 5},
 {3, 1}, {3, 2}, {3, 3}, {3, 4}, {3, 5}, {4, 1}, {4, 2}, {4, 3}, {4, 4}, {4, 5},
 {5, 1}, {5, 2}, {5, 3}, {5, 4}, {5, 5}]

into: 引数を指定すると、指定された引数に値を詰め込んだものを返す。

iex(15)> for x <- 1..5, y <- 1..5, do: { x, y }, into: [1, 2, 3]
[1, 2, 3, {1, 1}, {1, 2}, {1, 3}, {1, 4}, {1, 5}, {2, 1}, {2, 2}, {2, 3},
 {2, 4}, {2, 5}, {3, 1}, {3, 2}, {3, 3}, {3, 4}, {3, 5}, {4, 1}, {4, 2}, {4, 3},
 {4, 4}, {4, 5}, {5, 1}, {5, 2}, {5, 3}, {5, 4}, {5, 5}]

ちなみに、 into: に指定できるのはリストだけではなく Collectable プロトコル(Javaのインターフェースみたいなもん)を実装している任意の型。

# mapはCollectableインターフェースを実装している
iex(16)> for key <- ["hoge", "fuga", "foo"], into: %{}, do: { key, String.length(key) }
%{"foo" => 3, "fuga" => 4, "hoge" => 4}

ちなみに、 <- の右にくるものもリストだけではなく Enumerable プロトコルを実装している任意の型が使える。

iex(18)> map = %{ foo: :bar, baz: :quux }
%{baz: :quux, foo: :bar}
iex(20)> for { k, v } <- map, do: { k, v }
[baz: :quux, foo: :bar]

なお、 << ... >> で全体をくくることによってバイナリの内包表記も使える。

iex(24)> for << char <- "hoge" >>, into: "", do: << char - 32 >>
"HOGE"