Edited at

Elixir 文法 メモ

※この記事はとても古くなっています。注意してください。

個人的なメモです。

Get Started を読みながらまとめました。

流し読みすれば雰囲気くらいはつかめるかもしれません。


匿名関数

add = fn a, b -> a + b end

is_function add, 1 # false
add.(1, 2) # 3


即時実行

(fn -> x = 0 end).()


bool

Elixirではfalsenilだけが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_anytrueにすると全部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プロトコルを実装してさえいればどんなものでも受けつけます.


シギル

http://elixir-ja.sena-net.works/getting_started/19.html

  • if, unless
  • else
  • do
  • 引数にifを渡す
  • UTF-8
  • バイナリ
  • バイナリと文字列
  • バイナリの結合
  • 文字リスト
  • 文字列、文字リストの変換
  • キーワードリスト [hoge: foo, ...]
  • ++演算子で追加できる
  • ※同じキーがある場合は前のが取得される
  • キーワードリストの特徴
  • if関数 も キーワードリストを受け取っている
  • キーワードリストにもパターンマッチできる
  • キーワードリストとマップの使い分け
  • マップ %{:hoge => foo, ...}
  • 同じキーを渡すと最後が勝つ
  • キーが全部atomならキーワード構文が使える
  • マップはパターンマッチングに使いやすい
  • キーが全部atomなら使える構文
  • キーワードリストもマップも「Dicts」
  • モジュール
  • 引数の数が違う関数は別の関数として扱われる
  • 関数のキャプチャ
  • キャプチャ構文は関数を作るときのショートカットにもなる
  • デフォルト引数
  • 複数句がある場合は最初にデフォルト引数を決めておくことができる
  • Enumモジュールの mapとreduce
  • 範囲
  • パイプライン
  • Streamモジュールをつかうと、一連の処理を作ってくれる。
  • StreamをつくってEnum.takeで少しだけ実行する
  • Stream.cycle
  • Stream.unfold
  • ファイルから10行とる
  • プロセス
  • Spawn
  • send
  • receive
  • flush/0 でメールボックス全部表示&消す
  • 独立してるので子プロセスでエラー起きても落ちないスゴイ!!
  • spawn_link 使うことで同時に親も落ちさせることもできる
  • "早く失敗する"はElixirでソフトウェアを書く際の一般的な指針
  • 状態
  • キーと値を保持するモジュールを作ってみる
  • Agentモジュール
  • ファイル系モジュール
  • !(バン)
  • グループリーダー
  • alias
  • lexically scoped
  • require
  • import
  • エイリアスの実態はatom
  • モジュールはatomで表現されている
  • デフォでエイリアスされてる
  • Module attributes
  • 定数としても使える
  • 構造体
  • プロトコル
  • プロトコル実装をすることで構造体も受け取れるようになる
  • プロトコルのデフォルト実装
  • すでにある色んなプロトコル
  • エラー
  • 普通のエラー
  • raiseを使うとわざとエラーを起こせる
  • 自分でエラーを定義できる
  • try rescue文でエラーを受け取る
  • Throw, catch
  • exit
  • exitはtry/catchでキャッチされる
  • after
  • for
  • into:
  • シギル