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 の Binaries, strings, and charlists を見るとわかるでしょう.
文字列操作
結合
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, 100, 200, 300]
List.insert_at(ary, 1, 2) # => [100, 2, 200, 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]
パターンマッチについては Pattern matching を読むとよくわかります.
マップ
マップは %{}
を使います.
前述のリストやマップで共通してできることは Enum に書いてあります.まずはここを探してみましょう.
マップのように「キー」と「値」をペアで持つデータ構造でしかできないようなことは Map に書いてあります.
マップや,ここでは紹介しませんでしたがマップ風のデータ構造を持つキーワードリストについて詳しく知りたいときは Keyword lists and maps を見てみましょう.
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 でも条件文で偽とみなされるのは false
と nil
のみで,それ以外のデータは全て真とみなされます.
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 の再帰について知りたければ 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]
繰り返しについて知りたければ 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 関連について詳しく知りたければ IO and the file system を読むとよいでしょう.
読み込み
とりあえず File.read/1
だけ覚えておけばなんとかなると思います.
{:ok, str} = File.read("foo.txt") # => うまく読み込めた場合 str に foo.txt の内容が丸ごと文字列で代入される
書き込み
File.write/2
だけ覚えておけばなんとかなると思います.
File.write("foo.txt", "hogehoge") # => foo.txt に hogehoge と書き込む
知っておいた方がよい文法
真偽値
Ruby と同じで false
と nil
が偽,それ以外の全ての値は真です.
コマンドライン引数や環境変数
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 で自作できます(!)
興味があれば 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"}
のように {}
でくくります.
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 には「操作対象は必ず関数の第一引数になる」という掟があります.上の例で言うと list
や filtered
が操作されていますね.
つまり「関数の結果を受けて,次の関数の第一引数へ渡す」という記法があれば操作を繋げることができます.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 をはじめてみたいなあという人へ
プログラミングElixir がとてもよくできています.日本語でもありますし,興味をもった人が最初に手にとる書籍として非常におすすめです.
公式ページにある Getting Started もとてもよくできているので,プログラミングElixirを読みおわったあと,折をみて読みましょう.
この 2 つを読めば大体のことを始められ,わからないことも調べられるでしょう.わからないことは Elixir のコミュニティで聞いてみましょう.私が知っている,日本で Elixir を弄って遊んでいるコミュニティには
があります.