Elixir 基礎文法最速マスター

  • 372
    いいね
  • 2
    コメント
この記事は最終更新日から1年以上が経過しています。

ErlangVMに載っている言語に関する話題やそうでない話題でわいわいやる集まりSapporo.beamの時間を使って,
Ruby基礎文法最速マスター を参考に Elixir 基礎文法を書きました.

  • Ruby が得意なところ
  • Ruby と Elixir の似ているところ
  • Elxir が得意なところ

という観点のうち,以下の紹介では 「Ruby と Elixir の似ているところ」を主に紹介していきます.

ここがおかしいとかわからないところがあればコメント歓迎いたします.お気軽にどうぞ.

基礎

インタラクティブ (iex)

iex を使うと,Elixir のプログラムを簡単に練習することができます.

/Users/niku% iex
Interactive Elixir (1.0.0) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> IO.puts "hello"
hello
:ok
iex(2)> 1 + 1
2

以下の説明は,iex を起動して,自分で試しながら読むと習得が早いと思います.

NOTE: 入力に失敗してよくわからなくなったとき,Ruby の irb だと Ctrl-C を連打して入力をキャンセルすると思います.一方 Elixir の iex で Ctrl-C を連打すると iex が終了してしまうので,キーボードから #iex:break と打ち込みましょう.これで入力をキャンセルできます.詳しくは IEx の Expressions in IEx に書いてあります.

表示 (IO.write/1, IO.puts/1, IO.inspect/1)

Elixir の関数は「モジュール名」「関数名」「引数の数」で一意になるので

  • モジュール名 :: IO
  • 関数名 :: write
  • 引数の数 :: 1

を組み合わせて IO.write/1 のように表します.

IO.write "foo" # => 改行なし
IO.puts "foo"  # => 改行あり
IO.inspect 123 # => デバッグ用

Ruby でいうところの「p デバッグ」は,Elixir では「IO.inspect デバッグ」になります.

変数の宣言

Elixir には変数宣言がありせん.また変数の型もないので、一つの変数にいろいろなクラスのオブジェクトを代入することができます。

a = 123
a = "abc" # => a を上書きする

Elixir では変数に ^ をつけると再代入を防止できます.

a = 123
^a = "abc" # => ** (MatchError) no match of right hand side value: "abc"

NOTE: 便宜上,ここでは代入 assign と呼んでいますが,実際には割り当て bind です.

オブジェクト指向言語ではないのでインスタンス変数はありませんが,
モジュールの中で @変数名 値 とすることでモジュール内で共通して値を利用できます.ただし値の変更はできません.

defmodule Foo do
  @val "hoge"
  def x do
    @val
  end
end

Foo.x # => "hoge"

コメント

# 以降はコメントになります.

# コメント

スクリプトの実行

Elixir はコンパイルすることもできますが,しなくても実行できます.コンパイルするソースコードは .ex,コンパイルしないで実行するソースコードは .exs をつけるならわしのようです.

スクリプトの実行は elixir ファイル名 で行います.

/Users/niku% echo 'defmodule My do; IO.puts("Hello World"); end' > hello.exs
/Users/niku% elixir hello.exs
Hello World

数値

整数や小数が使えます.足し算,引き算,割り算などの計算は Kernel にあります.その他整数なら Integer,小数なら Float をみるとできることが大体わかります.

num = 1
num = 1.234
num = 100_000_000

四則演算

num = 1 + 1
num = 1 - 1
num = 1 * 2
num = 5 / 2     # => 2.5 (Ruby とは異なり整数同士の割り算でも小数になる)
num = 5.0 / 2   # => 2.5
num = rem(5, 2) # => 5 % 2 という記法はない

インクリメントとデクリメント

Elixir にはありません.Ruby の +=-= という記法もありません.x = x + 1 などのように書いてください.私見ですがあんまり再代入を使わない方がいいような言語に見えますのでインクリメントやデクリメントしたくなったら立ち止まって本当は何をしたいのか考えてみた方がいいんじゃないかなあ.

文字列

文字列はシングルクォート ' やダブルクォート " で囲みます.
Elixir ではシングルクォートで囲った文字とダブルクォートで囲った文字は異なる性質を持ちます.ほとんど全ての場合ダブルクォートで囲った文字を使います.最初はシングルクォートのことを忘れましょう.どちらのクォートの中でも \t( タブ )\n( 改行 ) などの特殊文字を利用することができ,「#{}」を使って任意の式を展開することができます.

まずは String を眺めるとできることがわかるでしょう.そこに無い場合は Regex をみるとあるかもしれません.その他文字列の結合などいくつかは Kernel にあります.

str1 = 'abc'
str2 = "def"
str3 = "a\tbc\n"
str4 = "#{str1} def" # => "abc def"

シングルクォートとダブルクォートの違いについて詳しく知りたければ Getting Started の 6 Binaries, strings and char lists を見るとわかるでしょう.

文字列操作

結合

str1 = "aaa" <> "bbb"
str2 = Enum.join(["aaa", "bbb", "ccc"], ",")

分割

record = String.split("aaa,bbb,ccc", ~r/,/)

長さ

length = String.length("abcdef")

切り出し

substr = String.slice("abcd", 0, 2) # => "ab" (0 番目から 2 文字)
substr = String.slice("abcd", 0..1) # => "ab" (範囲 0 から 1 まで)

検索

[{idx, _}] = Regex.run(~r/bc/, "abcd", return: :index)
idx # => 1

リスト

リストは [] を使います.

リストや後述するマップで共通してできることは Enum に書いてあります.まずはここを探してみましょう.リストでできてマップでできないようなことは List に書いてあります Enum にない場合はこちらをみてみましょう.

ary = [100, 200, 300]

要素の参照と代入

参照

a = Enum.at(ary, 0) # => 100
b = Enum.at(ary, 1) # => 200

Ruby と違い既にあるリスト(配列)への挿入はできません.新しいリストを作って返すことはできます.

List.insert_at(ary, 0, 1) # => [1, 200, 300]
List.insert_at(ary, 0, 2) # => [100, 2, 300]
ary                       # => [100, 200, 300]

要素の個数

n = length(ary)
n = Enum.count(ary) # List に対しては同じ動作になりますが,動作が異なることもあります

リストの操作

パターンマッチを使うことが多いです.
リストのデータ構造上先頭に対して行うのは簡単なのですが,末尾に対して行うのは(コンピュータが)大変なのでパフォーマンス上おすすめできません.

ary = [1, 2, 3]
[a|tail] = ary
a                          # => 1
tail                       # => [2, 3]
[5|ary]                    # => [5, 1, 2, 3]
List.last(ary)             # => 3
List.insert_at(ary, -1, 9) # => [1, 2, 3, 9]
ary                        # => [1, 2, 3]

パターンマッチについては 4 Pattern matching を読むとよくわかります.

マップ

マップは %{} を使います.

前述のリストやマップで共通してできることは Enum に書いてあります.まずはここを探してみましょう.
マップのように「キー」と「値」をペアで持つデータ構造でできることが Dict に書いてあります.次にここを探してみましょう.
最後にマップでしかできないようなことは Map に書いてあります EnumDict にない場合はこちらをみてみましょう.

マップや,ここでは紹介しませんでしたがマップ風のデータ構造を持つキーワードリストについて詳しく知りたいときは 7 Keywords, maps and dicts を見てみましょう.

map = %{a: 1, b: 2}

要素の参照と代入

map[:a] # => 1
map[:b] # => 2

Ruby と違い既にあるマップ(ハッシュ)への挿入はできません.新しいマップを作って返すことはできます.

Map.put(map, :c, 5) # => %{a: 1, b: 2, c: 5}
Map.put(map, :d, 7) # => %{a: 1, b: 2, d: 7}
map                 # => %{a: 1, b: 2}

マップの操作

キーの取得

Map.keys(map) # => [:a, :b]

値の取得

Map.values(map) # => [1, 2]

キーの存在確認

Map.has_key?(map, :a) # => true

マップのペアの削除

Map.delete(map, :a) # => %{b: 2}

制御文

Elixir でも条件文で偽とみなされるのは falsenil のみで,それ以外のデータは全て真とみなされます.

if文

if a == 1 do
  # ...
else
  # ...
end

ループは再帰で表現します.while 文は用意されていません.

defmodule My do
  def loop(0) do
    IO.puts "done"
  end

  def loop(times) do
    IO.puts "#{times} times remaining"
    loop(times - 1)
  end
end

My.loop(5)
# => 5 times remaining
# => 4 times remaining
# => 3 times remaining
# => 2 times remaining
# => 1 times remaining
# => done

Elixir の再帰について知りたければ 9 Recursion を読むとよいでしょう.

Enum#each と無名関数を使った繰り返し

Enum.each([1, 2, 3], fn(i) ->    # fn(arg) -> ... end のことを無名関数と呼びます
  IO.puts(i)                     # Enum.each/2 は第一引数の各要素を第二引数に渡して実行してくれます
end)                             # 1, 2, 3 と順に表示します

Enum.each([1, 2, 3], &IO.puts/1) # fn(i) -> IO.puts(i) end を省略して &IO.puts/1 と書くこともできます

その他の繰り返し

Enum モジュールには繰り返しを行う便利な関数がたくさん定義されています.

条件に合うものだけを選ぶ

Enum.filter([1,2,3,4,5], &rem(&1, 2) == 0) # => [2, 4]

条件に合うものを除く

Enum.reject([1,2,3,4,5], &rem(&1, 2) == 0) # => [1, 3, 5]

条件に合う最初のものを返す

Enum.find([1,2,3,4,5], &rem(&1, 2) == 0)   # => 2

等しい値があるか調べる

Enum.member?([1,2,3,4,5], 3)               # => true

条件に合うものがあるか調べる

Enum.any?([1,2,3,4,5], &rem(&1, 2) == 0)   # => true

全ての要素が条件に合うかを調べる

Enum.all?([1,2,3,4,5], &rem(&1, 2) == 0)   # => false

条件に合うものの個数を調べる

Enum.count([1,2,3,4,5], &rem(&1, 2) == 0)  # => 2

最大のものを返す

Enum.max([1,2,3,4,5])                      # => 5

最小のものを返す

Enum.min([1,2,3,4,5])                      # => 1

加工結果が最大のものを返す

Enum.max_by([1,-2,3,4,-5], &abs/1)         # => -5

加工結果が最小のものを返す

Enum.min_by([1,-2,3,4,-5], &abs/1)         # => -1

昇順にソートする

Enum.sort([1,-2,3,4,-5])                   # => [-5, -2, 1, 3, 4]

加工結果で昇順にソートする

Enum.sort_by([-5, -2, 1, 3, 4], &abs/1)    # => [1, -2, 3, 4, -5]

加工結果を配列で返す

Enum.map([1,-2,3,4,-5], &abs/1)            # => [1, 2, 3, 4, 5]

繰り返しについて知りたければ 10 Enumerables and Streams を読むとよいでしょう.

サブルーチン

Elixir には「サブルーチン」というものはありません.Ruby のようにトップレベルでメソッド定義をすることもできませんが,モジュールとモジュール内関数を定義して,そのモジュールを import することで擬似的にトップレベルで関数を利用することができます.

def hoge do
  "hoge"
end
# => ** (ArgumentError) cannot invoke def/2 outside module

defmodule Foo do
  def sum(x, y) do
    x + y
  end
end

import Foo
sum(1, 2)  # => 3

ファイル入出力

ファイル入出力でできることは File に書いてあります.

IO 関連について詳しく知りたければ 12 IO を読むとよいでしょう.

読み込み

とりあえず File.read/1 だけ覚えておけばなんとかなると思います.

{:ok, str} = File.read("foo.txt") # => うまく読み込めた場合 str に foo.txt の内容が丸ごと文字列で代入される

書き込み

File.write/2 だけ覚えておけばなんとかなると思います.

File.write("foo.txt", "hogehoge") # => foo.txt に hogehoge と書き込む

知っておいた方がよい文法

真偽値

Ruby と同じで falsenil が偽,それ以外の全ての値は真です.

コマンドライン引数や環境変数

System から取得しましょう.

/Users/niku% echo 'defmodule My do; IO.inspect(System.argv); end' > system.exs
/Users/niku% elixir system.exs hoge fuga
["hoge", "fuga"]

unless

あります.if の反対です.

全く同じものを Elixir で自作できます(!)

興味があれば 2 Macros を眺めてみましょう.
ただし今ここが理解できなくても Elixir は普通に使えます.

後置IF

Ruby でいうところの

puts "ok" if x == 1

という記法は使えません.(もしあったら教えてください)

if 式を一行で書きたいなら

if x == 1, do: IO.puts "foo"

と書くことはできます.

モジュール定義

defmodule do ... end で定義します.

Ruby に慣れた人は defmodule X do と def x do の 2 つの do を忘れがちになるので注意しましょう.

defmodule Foo do
  def greeding(name) do
    "Hi! #{name}"
  end
end

Foo.greeding("niku") # => "Hi! niku

その他よく使うクラス

Ruby でいうところのシンボルに相当するものとして atom というものがあります.記法も :hoge と同じです.

Ruby でいうところのブロック(Procオブジェクト)に相当するものとして無名関数があります.記法は

add = fn a, b -> a + b end
add.(1, 2) # => 3

という感じです.

Ruby にはないのですが,Tuple という長さがあらかじめ決まっている配列のようなものを良く使います.記法は {:ok, "hello"} のように {} でくくります.

2 Basic types に目を通しておくと大体の登場人物がわかるでしょう.

余談

メソッドチェーン

Ruby などのオブジェクト指向だと結果がオブジェクトで返ってくるので,返ってきたオブジェクトにあるメソッドを . で呼び出すことで以下のようにメソッドチェーンできますよね.

[1, 2, 3, 4, 5].select { |e| e < 4 }.map { |e| e * 10 } # => [10, 20, 30]

Elixr だと結果がデータ型で返ってきて,データ型から関数を呼び出す方法はないのでメソッドチェーンできません.

list = [1, 2, 3, 4, 5]
filtered = Enum.filter(list, fn e -> e < 4 end)
Enum.map(filtered, fn e -> e * 10 end) # => [10, 20, 30]

これだと書きづらいですよねえ.

さて,Elixir には「操作対象は必ず関数の第一引数になる」という掟があります.上の例で言うと listfiltered が操作されていますね.

つまり「関数の結果を受けて,次の関数の第一引数へ渡す」という記法があれば操作を繋げることができます.Elixir ではこれを |> ( Kernel.|>/2 ) という記法で実現しています.

これを使うと以下のように書くことができます.

[1, 2, 3, 4, 5] |> Enum.filter(fn e -> e < 4 end) |> Enum.map(fn e -> e * 10 end) # => [10, 20, 30]

便利ですね.

さらに無名関数を & ( Kernel.SpecialForms.&/1 ) で省略して書く方法があるので慣れると以下のように圧縮して書くことができます.

[1, 2, 3, 4, 5] |> Enum.filter(&(&1 < 4)) |> Enum.map(&(&1 * 10)) # => [10, 20, 30]

慣れるまで読むのが大変そうですけど,必要な部分だけ書けて便利ですね.

Elixir をはじめてみたいなあという人へ

公式ページにある Getting Started がとてもよくできているので読みましょう.
英語で読むのが辛い人は少し古いですが有志が翻訳した Github のリポジトリがあるのでそちらを眺めてみましょう.

本だと Programming Elixir: Functional |> Concurrent |> Pragmatic |> Fun がとてもよくできています.

この 2 つを読めば大体のことを始められ,わからないことも調べられるでしょう.どちらか 1 つだけというのなら,Web ですぐに眺められる Getting Started を読みましょう.

わからないことは Elixir のコミュニティで聞いてみましょう.私が知っている,日本で Elixir を弄って遊んでいるコミュニティは

の 2 つです.