LoginSignup
74
70

More than 3 years have passed since last update.

Elixir 文法 メモ

Last updated at Posted at 2015-07-28

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

個人的なメモです。

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プロトコルを実装してさえいればどんなものでも受けつけます.

シギル

74
70
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
74
70