※この記事はとても古くなっています。注意してください。
個人的なメモです。
Get Started を読みながらまとめました。
流し読みすれば雰囲気くらいはつかめるかもしれません。
匿名関数
add = fn a, b -> a + b end
is_function add, 1 # false
add.(1, 2) # 3
即時実行
(fn -> x = 0 end).()
bool
Elixirではfalse
とnil
だけがfalseとして扱われます.他の全てはtrueと評価されます.
リスト と タプル
- リスト
[1, true, 2]
- タプル
{false, 1, "abc"}
どんな型も入れられる。
リスト [ ]
[1, 2, true, "abc"]
・連結したノードで、要素の一つ一つが次の要素のアドレスを指してる
つまり list = [1|[2|[3|[]]]]
→ [1, 2, 3]
※ノードというよりコンスと呼ぶらしい
- 要素アクセスやサイズ取得は遅い
- 先頭に追加するのは速い
[0] ++ list
-> 速い
list ++ [4]
-> 遅い ← 最初から最後まで見に行かなきゃいけないから
--演算子
※先にあるものから引かれる
[1, true, false, 1, 2, 3] -- [1,2,3]
→ [true, false, 1]
hd/1 :headを取得
tl/1 :tailを取得
タプル { }
{1, 2, true, "abc"}
- 連続したメモリに保存
- indexでのアクセスやサイズ取得が速い
- 要素の追加や変更は新しいタプルをつくるから遅い
要素の変更
put_elem(たぷる, インデックス, 内容)
※新しいタプルを返す
Elixir上での長さの呼び方
- 計算が必要なもの → length
- 既に計算されているもの → size
演算子
and, or, not -> 真理値のみ受け付ける
true and true
1 and true
← エラー
&&, ||, ! どんな型も受け付ける
falseとnil 以外は true
※ 評価されて帰ってくるだけ
== と ===
整数と浮動小数点を厳密に見るかどうか
<, > での 異なる型の比較をした場合
number < atom < reference < functions < port < pid < tuple < maps < list < bitstring
になる
#マッチ演算子 =
- 変数が左辺にあれば変数が割り当てられる
x = 1
- 式として妥当なら返す
1 = x
→ 1
- 式として妥当でなければMatchErrorが発生する
2 = x
→ エラー
1 = y
→ yは未定義だからエラー
パターンマッチング
{name, age, tall} = {:jon, 12, 164}
name # jon
※もちろん型やサイズがマッチしていなければエラーを返す
headとtailに分ける
[h | t] = [1, 2, 3]
「|」 は [0|list] のようにリストを生成することも出来る
Elixir では 変数は再束縛される
x = 1
x = 2
^ 演算子
再束縛ではなく、その変数をマッチさせたい時に使う
^x = 2
※マッチの左側で関数を呼ぶことはできない
length([1,[2],3]) = 3
** (CompileError) iex:1: illegal pattern
_ 演算子
受け取らない
{x, _} = {:ok, "gomi"}
case, cond, if
case
case {1, 2, 3} do
{4, 5 ,6} -> "この場合マッチしない"
{1, x, 3} -> "これにマッチする"
_ -> "どれでもマッチ"
end
# 2
すでにある変数とマッチするときは ^ が必要
iex> x = 1
1
iex> case 10 do
...> ^x -> "Won't match"
...> _ -> "Will match"
...> end
guardの指定
whenとかで条件を拡張できる構文。
iex> case {1, 2, 3} do
...> {1, x, 3} when x > 0 -> "マッチする"
...> _-> "マッチしない"
...> end
ガードにはいろんな式が使える。
- in
- and, or, not
- is_atom, is_binary...
- hd, tl, trunc, tuple_size...
※ ガードの中で起きたエラーは単純にガードの失敗とみなされてガードの外には影響しない
どの句にもマッチしなければエラーになる
iex> case :ok do
...> :error -> "Won't match"
...> end
# ** (CaseClauseError) no case clause matching: :ok
匿名関数でも句やガードを持てる
iex> f = fn
...> x, y when x > 0 -> x + y
...> x, y -> x * y
...> end
#Function<12.71889879/2 in :erl_eval.expr/5>
iex> f.(1, 3)
4
iex> f.(-1, 3)
-3
cond
trueになるまで複数の条件をチェックする構文。
iex> cond do
...> 2 + 2 == 5 ->
...> "This will not be true"
...> 2 * 2 == 3 ->
...> "Nor this"
...> 1 + 1 == 2 ->
...> "But this will"
...> true ->
...> "全部falseだった場合" #何もtrueにならないとエラーになるからこうする
...> end
"But this will"
condではnilとfalse以外はtrue
iex> cond do
...> hd([1,2,3]) ->
...> "nilとfalse以外はtrue"
...> end
"nilとfalse以外はtrue"
if, unless
if true do
"ok"
end
条件文がfalseかnilならnilが返って来て実行されない。
unlessはifの逆。
else
if false do
"this won't be seen"
else
"this will"
end
メモ: この言語でif/2やunless/2がマクロとして実装されているのがおもしろい点です;これらは他の様々な言語でそうであるような特別な言語構造ではありません.モジュールKernelのドキュメントでif/2のドキュメントやソースを調べることができます.モジュールKernelには他にも演算子+/2や関数is_function/2といったような,あなたがコードを書くときにデフォルトで自動importされて使えるようになっているものが定義されています.
do
if true do
a = 1 + 2
end
キーワードリスト
if true, do: 1 + 2
if false, do: "this", else: "that"
if true, do: (
a = 1 + 2
a + 10
)
引数にifを渡す
is_number(if true do
1 + 2
end)
UTF-8
byte_size "a"
1
byte_size "å"
2
byte_size "🎃"
4
バイナリ
byte_size <<0, 1, 2, 3>>
4
バイナリと文字列
<<100>>
"d"
String.valid?(<<100, 101, 102>>)
true
バイナリの結合
<<0, 1>> <> <<2, 3>>
↓
<<0, 1, 2, 3>>
ヌルバイト`<<0>>を結合させて文字列のバイナリ表現を見る
"hełło" <> <<0>>
↓
<<104, 101, 197, 130, 197, 130, 111, 0>>
文字リスト
二重引用符で囲まれたものは文字列**(つまりバイナリ)**
単一引用符で囲まれたものは文字のリスト(つまりリスト)
'hełło'
[104, 101, 322, 322, 111]
is_list 'hełło'
true
文字列、文字リストの変換
to_char_list "hełło"
[104, 101, 322, 322, 111]
to_string 'hełło'
"hełło"
to_string :hello
"hello"
to_string 1
"1"
キーワードリスト [hoge: foo, ...]
"最初の要素がアトムになっているタプル"のリスト。
連想データ構造。
[{あとむ, 要素}, {あとむ, 要素}, ...] って感じ
list = [{:a, 1}, {:b, 2}]
こうもかける
list = [a: 1, b: 2]
アクセス
list[:a]
++演算子で追加できる
list ++ [c: 3]
※同じキーがある場合は前のが取得される
list = [a: 0, a: 1, b: 2]
list[:a]
0
キーワードリストの特徴
- キーの順序を開発者が指定したとおりに保持する
- キーを複数回与えることができる
if関数 も キーワードリストを受け取っている
実はifのこの書き方は、if関数の最後の引数にキーワードリストを最後に渡してる。
if false, do: "abc", else: 123
↓これと同じ
if(false, [do: "abc", else: 123])
キーワードリストにもパターンマッチできる
iex> [a: a] = [a: 1]
[a: 1]
iex> a
1
iex> [a: a] = [a: 1, b: 2]
** (MatchError) no match of right hand side value: [a: 1, b: 2]
iex> [b: b, a: a] = [a: 1, b: 2]
** (MatchError) no match of right hand side value: [a: 1, b: 2]
キーワードリストを操作するKeywordモジュールがあります。
キーワードリストとマップの使い分け
キーワードリストは単にリストなので、長いリストはキーを見つけるのがより長くなり、要素を数えるのも長くなる。
だからキーワードリストは主にオプションに使われる。
もし沢山の要素を保持しなければならなかったり、1つのキーが多くとも1つの値しか持たないことを保証したい場合はマップを使った方がいい。
マップ %{:hoge => foo, ...}
map = %{:a => 1, 2 => :b}
- マップはキーをどんな値にもできる
- 1つのキーが1つの値をもつ
- マップのキーは順番通りにならない
同じキーを渡すと最後が勝つ
%{1 => 1, 1 => 2}
%{1 => 2}
キーが全部atomならキーワード構文が使える
map = %{a: 1, b: 2}
マップはパターンマッチングに使いやすい
キーワードリストとは対照的に使いやすい。
iex> %{} = %{:a => 1, 2 => :b}
%{:a => 1, 2 => :b}
iex> %{:a => a} = %{:a => 1, 2 => :b}
%{:a => 1, 2 => :b}
iex> a
1
iex> %{:c => c} = %{:a => 1, 2 => :b}
** (MatchError) no match of right hand side value: %{2 => :b, :a => 1}
キーが全部atomなら使える構文
map.a
とか
%{map | :a => 2}
↓ 例
iex> map = %{:a => 1, 2 => :b}
%{:a => 1, 2 => :b}
iex> map.a
1
iex> %{map | :a => 2}
%{:a => 2, 2 => :b}
iex> %{map | :c => 3}
** (ArgumentError) argument error
上にあるアクセスと更新のどちらの構文でも既にあるキーを必要とします.例えば最後の行ではマップにcがないので失敗しています.これはマップに既にキーがあることをあなたが期待している場合にすごく便利に使うことができます.
マップを操作するMapモジュールがあります。
キーワードリストもマップも「Dicts」
ディクショナリ。
ディクショナリはインターフェイスのようなもの。
キーワードリストもマップも、ディクショナリのインターフェイスを実装している。
※ Elixirではインターフェイスをbehaviours(ビヘイビア)と呼ぶ。
iex> keyword = []
[]
iex> map = %{}
%{}
iex> Dict.put(keyword, :a, 1)
[a: 1]
iex> Dict.put(map, :a, 1)
%{a: 1}
モジュール
defmodule Math do
def sum(a, b) do
a + b
end
end
- boolを返す関数には?をつける
- when句を使える
defmodule Math do
def zero?(0) do
true
end
def zero?(x) when is_number(x) do
false
end
end
Math.zero?(0) #=> true
Math.zero?(1) #=> false
Math.zero?([1,2,3])
#=> ** (FunctionClauseError)
引数の数が違う関数は別の関数として扱われる
is_function/1
is_function/2
のように表す
関数のキャプチャ
&をつけることで、関数を代入したりできるようになる。
f = &Math.add/1
f.(1, 2)
キャプチャ構文は関数を作るときのショートカットにもなる
add = &(&1 + 1)
add.(1) #2
デフォルト引数
def join(a, b, sep \ " ") do
defmodule Concat do
def join(a, b, sep \\ " ") do
a <> sep <> b
end
end
IO.puts Concat.join("Hello", "world") #=> Hello world
IO.puts Concat.join("Hello", "world", "_") #=> Hello_world
複数句がある場合は最初にデフォルト引数を決めておくことができる
defmodule Concat do
def join(a, b \\ nil, sep \\ " ")
def join(a, b, _sep) when is_nil(b) do
a
end
def join(a, b, sep) do
a <> sep <> b
end
end
IO.puts Concat.join("Hello", "world") #=> Hello world
IO.puts Concat.join("Hello", "world", "_") #=> Hello_world
IO.puts Concat.join("Hello") #=> Hello
forとかないから再帰する
Enumモジュールの mapとreduce
map : 配列の全要素に関数を適用
reduce : 配列の全要素に関数を適用して足す
iex> Enum.reduce([1, 2, 3], 0, fn(x, acc) -> x + acc end)
6
iex> Enum.map([1, 2, 3], fn(x) -> x * 2 end)
[2, 4, 6]
# キャプチャ構文
iex> Enum.reduce([1, 2, 3], 0, &+/2)
6
iex> Enum.map([1, 2, 3], &(&1 * 2))
[2, 4, 6]
Enumモジュールにある関数はEnumerableプロトコルを実装したどんなデータ型でも動作するようになっています.
範囲
iex> Enum.map(1..3, fn x -> x * 2 end)
[2, 4, 6]
iex> Enum.reduce(1..3, 0, &+/2)
6
パイプライン
1..100_000 |> Enum.map(&(&1 * 3)) |> Enum.filter(odd?) |> Enum.sum
100000までの数を三倍して奇数だけにして足す。
Enumの関数の多くはEnumerableを受け取って1つのリストを返します。
つまり複数の操作をした時に結果が出るまで全ての操作が中間リストを作るということになる。
それは流石にヤバイ。
例えば以下を実行すると物凄く時間かかる。終わらない。
1..1000000000 |> Enum.map(&(&1 * 3))
そこでStreamモジュールというものを使う。
Streamモジュールをつかうと、一連の処理を作ってくれる。
大きいEnumerableや無限かもしれない集まりを扱うのに便利。
StreamをつくってEnum.takeで少しだけ実行する
> s = 1..1000000000 |> Stream.map(&(&1 * 3))
#Stream<[enum: 1..1000000000, funs: [#Function<45.113986093/1 in Stream.map/2>]]>
> Enum.take(s, 3)
[3, 6, 9]
Stream.cycle
Enumerableを無限に繰り返す。
iex> stream = Stream.cycle([1, 2, 3])
#Function<15.16982430/2 in Stream.cycle/1>
iex> Enum.take(stream, 10)
[1, 2, 3, 1, 2, 3, 1, 2, 3, 1]
Stream.unfold
初期値をとって、関数を再帰的に実行していく。
関数で{要素, 次の値}を返す。
Stream.unfold(5, fn 0 -> nil; n -> {n, n-1} end) |> Enum.to_list()
[5, 4, 3, 2, 1]
ファイルから10行とる
iex> stream = File.stream!("path/to/file")
iex> Enum.take(stream, 10)
プロセス
Elixirでは全てのコードはプロセスの内部で動きます.プロセス同士は独立,並行して動き,メッセージパッシングでやり取りします.プロセスはElixirにおける並行の基本となるばかりではなく,分散や高可用なプログラムの構築にも役立ちます.
ElixirのプロセスをOSのプロセスと混同してはいけません.プロセスは(他の多くのプログラミング言語におけるスレッドとは異なり)メモリとCPUにとって非常に軽量です.ですから,同時に動作する数千のプロセスを保持することは特別珍しいことではありません.
Spawn
プロセスを生み出し、関数を開始、PIDを返す。
iex> spawn(fn -> 1 + 2 end)
PID<0.43.0>
プロセスが生きてるか確認
iex> Process.alive?(pid)
false
self/0 で自分のプロセスのPIDを入手
iex> self()
#PID<0.58.0>
iex> Process.alive? self()
true
send
↓自分のプロセスにメッセージを送る
send self(), {:abc, "dope"}
# {:abc, "dope"}
receive
sendされたメッセージはメールボックスに入る。
receive do
{:abc, msg} -> msg
{:woo, msg} -> "aaa"
end
# "dope"
もしパターンにマッチするメッセージがメールボックスに無ければ,現在のプロセスはマッチするメッセージがくるまで待ち続けます.タイムアウトを指定することもできます:
receive do
{:wtf, msg} -> msg
{:woo, msg} -> "aaa"
after
1000 -> "1秒待ったけど何も来なかった..."
end
flush/0 でメールボックス全部表示&消す
iex> send self(), :hello
:hello
iex> flush()
:hello
:ok
独立してるので子プロセスでエラー起きても落ちないスゴイ!!
iex> spawn fn -> raise "oops" end
[error] Error in process <0.58.0> with exit value: ...
spawn_link 使うことで同時に親も落ちさせることもできる
spawn_link fn -> raise "oops" end
Process.link/1を呼べばリンクを手動で行うこともできます.
"早く失敗する"はElixirでソフトウェアを書く際の一般的な指針
プロセスとリンクは可用性の高いシステムを構築するのに大事な役割を担っています.Elixirのアプリケーションではしばしば,プロセスが死んでしまった際に検知し,同じ場所で新しいプロセスを動かすため,私たちが作るプロセスとスーパーバイザー(監視役)をリンクさせます.これができるのはプロセスがデフォルトでは何も共有しないためです.そしてもしプロセスが独立しているなら,プロセス内での失敗が他のプロセスをクラッシュさせたり状態を壊したりすることは絶対にありません.
他の言語では例外は受けとったり操作することが求められますが,Elixirではスーパーバイザーがシステムを再起動してくれるであろうため,プロセスは失敗するがままでかまいません."早く失敗する"はElixirでソフトウェアを書く際の一般的な指針です!
状態
設定を保持したり、メモリを保持するには?
↓
プロセスをつくってそこに保存する!!!
キーと値を保持するモジュールを作ってみる
KV(key-value)
defmodule KV do
def start_link do
{:ok, spawn_link(fn -> loop(%{}) end)}
end
defp loop(map) do
receive do
{:get, key, caller} ->
send caller, Map.get(map, key)
loop(map)
{:put, key, value} ->
loop(Map.put(map, key, value))
end
end
end
起動
{:ok, pid} = KV.start_link
保存
send pid, {:put, :name, "taro"}
取得
send pid, {:get, :name, self()}
flush()
Agentモジュール
Elixirが提供してる状態保存のモジュール。
起動
{:ok, pid} = Agent.start_link(fn -> %{} end)
保存
Agent.update(pid, fn map -> Map.put(map, :name, "taro") end)
取得
Agent.get(pid, fn map -> Map.get(map, :name) end)
ファイル系モジュール
- File: ファイルの読み書き
- Path: ファイルパス
!(バン)
読み込みが必ず成功するときはバンをつける。
バンをつけて読み込みに失敗するとエラーになる。
File.read "hello"
{:ok, "world"}
File.read! "hello"
"world"
こう書いてはいけない
{:ok, body} = File.read(file)
こう書く
case File.read(file) do
{:ok, body} -> # handle ok
{:error, r} -> # handle error
end
グループリーダー
どのプロセスにもグループリーダーがある。
:stdio
と書いた場合、実際にはグループリーダーへメッセージを送っている。
グループリーダーはSTDIOをファイルディスクリプタに書き込む。
iex> IO.puts :stdio, "hello"
hello
:ok
iex> IO.puts Process.group_leader, "hello"
hello
:ok
グループリーダーはプロセスごとに設定できる。
alias
defmodule Math do
alias Math.List, as: List
end
asを省略すると最後の部分を自動的に設定するので
これも同じ意味になる。
alias Math.List
lexically scoped
レキシカルスコープ。
関数の中でエイリアスを設定した場合はその関数の中でのみ有効になる。
defmodule Math do
def plus(a, b) do
alias Math.List
# ...
end
def minus(a, b) do
# ...
end
end
require
マクロとはコンパイル時に実行して展開されるコード片。
マクロをつかうためにはrequire
しないと使えない。
下記の例ではInteger.is_odd
はマクロとして実装されているためrequire
する必要がある。
iex> Integer.is_odd(3)
** (CompileError) iex:1: you must require Integer before invoking the macro Integer.is_odd/1
iex> require Integer
nil
iex> Integer.is_odd(3)
true
※requireもレキシカルスコープ。
import
importを使うと、そのモジュールの 関数やマクロ を モジュール名を省略して書くことができる。
モジュールをimportすると自動的にrequireする。
iex> import List, only: [duplicate: 2]
nil
iex> duplicate :ok, 3
[:ok, :ok, :ok]
上記の例では List.duplicate/2 のみをimportしてる。
全てのマクロをインポートする場合
import Integer, only: :macros
全ての関数をインポートする場合
import Integer, only: :functions
※importもレキシカルスコープ。
エイリアスの実態はatom
iex> is_atom(String)
true
iex> to_string(String)
"Elixir.String"
iex> :"Elixir.String"
String
モジュールはatomで表現されている
iex> :lists.flatten([1,[2],3])
[1, 2, 3]
iex> mod = :lists
:lists
iex> mod.flatten([1,[2],3])
[1,2,3]
デフォでエイリアスされてる
defmodule Foo do
defmodule Bar do
end
end
実は、これは↓と同じ
defmodule Elixir.Foo do
defmodule Elixir.Foo.Bar do
end
alias Elixir.Foo.Bar, as: Bar
end
Module attributes
「@」アトリビュート機能をつかってモジュールにドキュメントなどを埋め込むことができる。
「"""」でヒアドキュメント。
ヒアドキュメント内ではMarkdown形式で書ける!
defmodule Math do
@moduledoc """
Provides math-related functions.
## Examples
iex> Math.sum(1, 2)
3
"""
@doc """
Calculates the sum of two numbers.
"""
def sum(a, b), do: a + b
end
ExDocと呼ばれているドキュメントからHTMLページを生成するためのツールも提供しています。
定数としても使える
defmodule MyServer do
@initial_state %{host: "147.0.0.1", port: 3456}
IO.inspect @initial_state
end
※値が入るのはコンパイル時
構造体
構造体は、マップのフィールドの存在をコンパイル時に保証する。
定義
defmodule User do
defstruct name: "john", age: 27
end
↓インスタンス生成
%User{}
構造体はただのマップ。
> is_map(%User{})
true
構造体のupdate
iex> john = %User{}
%User{age: 27, name: "john"}
iex> john.name
"john"
iex> meg = %{john | name: "meg"}
%User{age: 27, name: "meg"}
iex> %{meg | oops: :field}
** (ArgumentError) argument error
↑ 存在しないフィールドに対してはupdateできない。
構造体のパターンマッチング
iex> %User{name: name} = john
%User{age: 27, name: "john"}
iex> name
"john"
iex> %User{} = %{}
** (MatchError) no match of right hand side value: %{}
構造体はマップの中に__struct__
というフィールドを持っている
iex> john.__struct__
User
だからパターンマッチングができる。
※構造体はディクショナリではない
プロトコル
例えば、アプリケーションによっては,それがブランクであると考えられるべきときに真偽値を返すようなblank?プロトコルを規定することが重要になるかもしれません.
defprotocol Blank do
@doc "Returns true if data is considered blank/empty"
def blank?(data)
end
↓ それぞれの型へのBlankプロトコルの実装
# 整数は決してブランクにならない - Integers are never blank
defimpl Blank, for: Integer do
def blank?(_), do: false
end
# 空リストのときだけブランクになる - Just empty list is blank
defimpl Blank, for: List do
def blank?([]), do: true
def blank?(_), do: false
end
# 空のマップのときだけブランクになる - Just empty map is blank
defimpl Blank, for: Map do
# Keep in mind we could not pattern match on %{} because
# it matches on all maps. We can however check if the size
# is zero (and size is a fast operation).
def blank?(map), do: map_size(map) == 0
end
# アトムがfalseとnilのときだけブランクになる - Just the atoms false and nil are blank
defimpl Blank, for: Atom do
def blank?(false), do: true
def blank?(nil), do: true
def blank?(_), do: false
end
↓ プロトコルが実装された型はblankで受け取れる
iex> Blank.blank?(0)
false
iex> Blank.blank?([])
true
iex> Blank.blank?([1, 2, 3])
false
##プロトコル実装をすることで構造体も受け取れるようになる
defimpl Blank, for: User do
def blank?(_), do: false
end
プロトコルのデフォルト実装
@fallback_to_any
をtrue
にすると全部trueを返すようになる。
defprotocol Blank do
@fallback_to_any true
def blank?(data)
end
そして型に Any
を指定することもできる。
defimpl Blank, for: Any do
def blank?(_), do: false
end
すでにある色んなプロトコル
EnumではEnumerableプロトコルを実装している型を受け取れる。
iex> Enum.map [1, 2, 3], fn(x) -> x * 2 end
[2,4,6]
iex> Enum.reduce 1..3, 0, fn(x, acc) -> x + acc end
6
to_string では String.Charsプロトコルを実装している型を受け取れる。
iex> to_string :hello
"hello"
これも to_string
iex> "age: #{25}"
"age: 25"
tuple は String.Charsプロトコルを実装してないのでエラーになる。
iex> tuple = {1, 2, 3}
{1, 2, 3}
iex> "tuple: #{tuple}"
** (Protocol.UndefinedError) protocol String.Chars not implemented for {1, 2, 3}
Inspectプロトコルを実装していれば inspect を使える。
iex> "tuple: #{inspect tuple}"
"tuple: {1, 2, 3}"
※iexで結果を表示するのにもinspectを使用している。
※ 評価された値が#から始まるときは,エリクサーの構文ではない方法でデータ構造を表しているという意味を表すならわしになっている
エラー
普通のエラー
iex> :foo + 1
** (ArithmeticError) bad argument in arithmetic expression
:erlang.+(:foo, 1)
raiseを使うとわざとエラーを起こせる
raise/1でランタイムエラー
iex> raise "oops"
** (RuntimeError) oops
raise/2で他のエラーを起こせる
iex> raise ArgumentError, message: "invalid argument foo"
** (ArgumentError) invalid argument foo
自分でエラーを定義できる
iex> defmodule MyError do
iex> defexception message: "default message"
iex> end
iex> raise MyError
** (MyError) default message
iex> raise MyError, message: "custom message"
** (MyError) custom message
try rescue文でエラーを受け取る
iex> try do
...> raise "oops"
...> rescue
...> e in RuntimeError -> e
...> end
%RuntimeError{message: "oops"}
ただしほとんど使うことはないらしい。
たとえばElixirでの関数File.read/1は,ファイルを開くのに成功した/失敗した情報を含んだタプルを返すようになってるかららしい。
Throw, catch
途中で処理をジャンプしたりする時につかう?
iex> try do
...> Enum.each -50..50, fn(x) ->
...> if rem(x, 13) == 0, do: throw(x)
...> end
...> "Got nothing"
...> catch
...> x -> "Got #{x}"
...> end
"Got -39"
exit
プロセスが死んだ時、exit
という信号が送られる。
また、わざとexit
を送ることで死ぬことができる。
iex> spawn_link fn -> exit(1) end
#PID<0.56.0>
** (EXIT from #PID<0.56.0>) 1
↑「1」という値を送って死んでいる。
exitはtry/catchでキャッチされる
iex> try do
...> exit "I am exiting"
...> catch
...> :exit, _ -> "not really"
...> end
"not really"
try/catchを使うことが既に珍しいですが,イグジットをキャッチするのはもっと珍しいことです.
exitシグナルはErlang VMで提供されている耐障害性において重要な部分を担っています.プロセスは通常,単にプロセスのexit信号を待つだけの監視プロセスを備えた監視ツリーにくみこまれて動作しています.監視プロセスがイグジット信号を受けとると,監視作戦が開始され,監視対象のプロセスは再起動させられます.
監視システムの構造自体がtry/catchやtry/rescueに似ているため,Elixirではそれらの構文があまり用いられていないのです.確実にエラーを拾うかわりに,エラーのあとに"早く失敗する"ことで監視ツリーがアプリケーションを既知の初期状態へと戻すことを保証しています.
after
エラーが起きても必ずそのあとに実行してくれる。
try do
raise "oops"
after
IO.puts "あふたー"
end
↓
あふたー
** (MatchError) no match of right hand side value: {:error, :enoent}
※ try/catch/rescue/afterブロックの中で定義した変数は外では見えない
for
iex> for n <- [1, 2, 3, 4], do: n * n
[1, 4, 9, 16]
iex> for n <- 1..4, do: n * n
[1, 4, 9, 16]
iex> values = [good: 1, good: 2, bad: 3, good: 4]
iex> for {:good, n} <- values, do: n * n
[1, 4, 16]
iex> require Integer
iex> for n <- 1..4, Integer.is_odd(n), do: n * n
[1, 9]
for dir <- dirs,
file <- File.ls!(dir),
path = Path.join(dir, file),
File.regular?(path) do
File.rm!(path)
end
iex> pixels = <<213, 45, 132, 64, 76, 32, 76, 0, 0, 234, 32, 15>>
iex> for <<r::8, g::8, b::8 <- pixels>>, do: {r, g, b}
[{213,45,132},{64,76,32},{76,0,0},{234,32,15}]
into:
処理した結果の行き先。
↓下記の例では空文字以外の文字が""へ追加されていく。
iex> for <<c <- " hello world ">>, c != ?\s, into: "", do: <<c>>
"helloworld"
セット,マップそして他のディクショナリも:intoオプションに渡せます.たいていの場合,:intoはCollectableプロトコルを実装してさえいればどんなものでも受けつけます.
シギル