(WIP)
2015-06-13 OSC Hokkaido 2015 で発表する プログラミング言語 Elxiir の資料
- Elixir を触ってみたことがないプログラマが,Elixir を触ってみることができるようになること
- Elixir でやりたいことがあったとき,どの辺を眺めたり探せばよいか,推測できるようになること
Elixir のトップページに載っている特徴について紹介と簡単にまとめる
なじみのある文面でプログラムを書くと ErlangVM で処理できるところ
どうしようかな.
インストールは
- つまづきやすい
- 変わりやすい
ことがあるから
- URLを示して簡潔に書く
- 丁寧に書く
どちらか.簡潔に書こうかなあ.
- これから紹介することを簡単に試すための REPL の使いかたを書く
- 評価を中断させるための
#iex:break
について書く def
がトップレベルに書けないことについて書く
プログラミングによくある要素/操作が普通にあることを示す
- 値と,値を操作する主要なモジュールについて書く
- 値のまとまり (List, Tuple, Map) などは後で紹介する
IO.inspect 1
IO.inspect 0x1F
IO.inspect 1 + 2
IO.inspect 5 * 5
IO.inspect 10 / 2
IO.inspect div(10, 2)
IO.inspect rem(10, 3)
1 + 2
を題材に,中置演算子は一部の演算子だけのシンタックスシュガーであること,括弧は省略できることを伝える.つまり下の式は全部同じ.
IO.inspect 1 + 2
IO.inspect Kernel.+(1, 2)
IO.inspect Kernel.+ 1, 2
同様に
IO.inspect div(10, 2)
IO.inspect Kernel.div(10, 2)
も同じ
ErlangVM では関数は モジュール名.関数名/引数の数
という形式で一意に表される.
例えば IO.inspect/1
の場合, IO
モジュールの inspect
という関数で,引数の数は 1
つのもの.
つまり IO.inspect("hello")
のように使うもののこと.
なぜ最初にこんな面倒そうなことを伝えるかというと,みなさんが自分でドキュメントを引けるようにするため.
- Elixir のドキュメント Elixir v1.0.4 Documentation がよくできているので web から眺めるときはここを引こう
- k1complete さんが日本語に訳してくださった elixirリファレンスのぺーじ もある
例えば 1 + 1
の +
について知りたい場合
1 + 1
はKernel.+(1, 1)
のことだ- モジュールは
Kernel
関数は+
引数の数は2
つだからKernel.+/2
だな - iex から
h Kernel.+/2
とするとドキュメントが引ける - ちなみに
h Kernel.+/1
もある.正の数+1
のこと. h Kernel.+
と引数の数を省略すると,Kernel.+/1
とKernel.+/2
の説明が出てくる
h で出てくる内容も日本語の方がよい場合は Elixir - iexでの日本語版ヘルプの使い方 - Qiita を参考にするとできるみたい(試していない)
整数を操作するモジュールはもっぱら以下の 2 つを眺めるとよさそう
IO.inspect 1.0
IO.inspect 1.0e-5
IO.inspect 1.0e-5 === 0.00001
IO.inspect round(3.58)
IO.inspect trunc(3.58)
IO.inspect true
IO.inspect false
IO.inspect !true
ブーリアンには特に Boolean
モジュールというものは用意されていない
自分の名前が自分の値を表すような定数.他の言語だとシンボルと言ったりしますね.
IO.inspect :foo
IO.inspect :"foo-bar"
実はブーリアン値はアトムでした
IO.inspect :true === true
IO.inspect :false === false
- Atom にシンボルから文字列に変換する関数があります
文字列です.Elixir では "
でくくった文字列と '
でくくった文字列は違うものとして扱われます.
'
は Erlang の歴史あるライブラリとのインターフェイスで利用することがありますが,一旦忘れましょう.
ほぼ全ての場合において "
の方を利用します.
IO.inspect "abc"
IO.inspect "こんにちは"
IO.inspect "1 + 2 は #{ 1 + 2 } です"
Elixir では文字列はバイナリとして扱われています. バイナリで扱われていると,文字の境界をちゃんと判断できているか不安になりますね. 確かめてみましょう.
IO.inspect is_binary("abc")
IO.inspect byte_size("日本語")
IO.inspect String.length("日本語")
IO.inspect String.at("日本語", 1)
String モジュールを使うと,UTF-8 エンコードされているバイナリをうまく扱うことができます
Keywords, maps and dicts - Elixir
値を格納する型について書く
- タプル,リスト,マップなど値を格納する型について書く
- 操作するモジュールについて書く
ここまでは,値について話してきましたね. 次に,値を格納する値について話します. つまり,配列やハッシュマップなどのことです.
いわゆる配列です.値はなんでも,何個でも入ります.
[]
でくくります.
IO.inspect [1,2,3]
IO.inspect [:a, "b", 'c']
IO.inspect [[:x], [:y, :z]]
中に入る個数が決まった入れ物です.
{}
でくくります.
IO.inspect {:a, 1}
IO.inspect {"x", "y", "z"}
あるキーと,それに対応する値を一組に持つ入れ物です.
いわゆるキーバリューストアです.
キーは重複して定義できず,上書きされます.
%{}
でくくります.
IO.inspect %{:a => 1, :b => 2}
IO.inspect %{a: 1, b: 2}
IO.inspect %{:a => 1, :b => 2, :a => 3}
1 つめの要素がアトム,2 つめの要素が任意の値になっているタプルを持つ配列です. Map とは異なり,同じ名前のキーを 2 つ保持することができます.
IO.inspect [{:foo, "x"}, {:bar, "y"}]
IO.inspect [{:foo, "x"}, {:bar, "y"}, {:foo, "z"}]
いわゆる代入のような,値へ名前をつける方法について書く
- 値の束縛と
^
について書く - パターンマッチングについて書こうかなあ
Elixir では以下のような形式で値を変数に結びつけることができます.
y
の例では,同じ名前で 2 回値を結びつけているように見えますけど,
実際にはコンパイル時には y1
と y2
のように別の変数名に変えられているそうです.
- TODO 名前があったはず.twilogを検索する
だけど,あんまりいいことはないので,やめた方がいいですね.
IO.inspect x = 1
IO.inspect x
IO.inspect y = :abc
IO.inspect y
IO.inspect y = :def
IO.inspect y
さて,Elixir では値を変数に結びつけるときに少し変わったことができます. 複数の値を格納している値を,複数の変数に結びつけることができます.
[h|t] = [1,2,3]
IO.inspect h
IO.inspect t
{x, y} = {123, 456}
IO.inspect x
IO.inspect y
%{i: a} = %{i: "あい", j: "じぇい", k: "けい"}
IO.inspect a
%{i: b} = %{j: "じぇい", k: "けい"}
# => (MatchError) no match of right hand side value: %{j: "じぇい", k: "けい"}
- TODO 興味のない値に対しては
_
か,_
で始まる変数名をつけるとうまく扱える例を書く
実は文字にも同じようなことができるんです.
<<h, t :: binary>> = "abcdef"
IO.inspect h
IO.inspect t
IO.inspect <<97>> === "a"
おっ, 97
という数字が出てしまいましたね.
これが何を表しているかというと,文字コードポイントの値です.
Elixir の文字はバイナリとして扱っています.バイナリの 97 は, a という文字を表します.
この場合は 97 と表示した方がいいか,a と表示した方がいいかを
コンピュータで判断がつけられないので 97
と表示してしまっているんですよねえ.
<<97>>
と "a"
は同じ内容を指しているけど,表示が異なるという認識で大丈夫です.
ここは今さらっといきますね. デバッグのときに文字をいじっていたはずなのに数字が出てきたらなんとなく思い出してください. 詳しく知りたい方は Binaries, strings and char lists - Elixir を読むといいです.
ともかく,文字も”複数の値を格納している値”とみなして, 分解して変数を割り当てられることがおわかりいただけたと思います.
用意されたモジュール/関数ではなく,独自の処理をやりたいときの方法について書く
fn
について書く&
についてもここで書こうかなあ
無名関数はこんな感じで書くことができます.
呼び出すときには .
をつけるのを忘れないでくださいね.
実のところ無名関数を変数へ束縛して使うことはほとんどありません.
関数(今回の例だと Enum.map
です)に無名関数を渡して,やりたい事を書くときに使うことが多いです.
よく使うので省略した書き方もできるようになっています.
fn(x) -> x + 5 end
は &(&1 + 5)
と書けます.
add = fn(x, y) -> x + y end
IO.inspect add.(2, 3)
x1 = Enum.map([1,2,3], fn (x) -> x + 5 end)
IO.inspect x1
x2 = Enum.map([1,2,3], &(&1 + 5))
IO.inspect x2
場合わけする構文について書く
if-else
case
- ガード節
cond
ふつうのIF式です. else
もありますね.
Elixir で false
として扱われるのは, false
と nil
だけです.
それ以外は true
と扱われます.Ruby と一緒ですね.
if true do
IO.inspect "true"
else
IO.inspect "false"
end
IF 式は返り値を持つので,結果を変数に束縛することもできます. 最近の言語だとそうなっているものが多いですね.
x = if true do
"**true**"
else
"**false**"
end
IO.inspect x
IF式だと true
か false
のみを扱います.
それで十分なことも多いのですが,
値が 1
と 2
と 3
の場合に動作を変えたい場合はどうしたらいいでしょうか?
Elixir には case 式もありますので,それを使ってみましょう.
x = 2
case x do
1 -> IO.inspect "x => 1"
2 -> IO.inspect "x => 2"
3 -> IO.inspect "x => 3"
end
うん.うまくいってますね.
変数と比較するときは少し注意が必要です.
x = 2
y = 3
z = 2
case x do
y -> IO.inspect "x === y, #{y}"
z -> IO.inspect "x === z, #{z}"
end
x
と z
が等しいので “x === z” の方を期待していたのに,”x === y” が出てしまいました.
何が起こっているかというと, y
に x
の値が再束縛されて,
ここでの y
の値が 3
から 2
へと変わってしまっています.
こういう場合に備えて,Elixir には y
の値が変わってほしくないんだよ.
ということを伝える書き方があります.それが ^y
です.
pin operator と呼ばれているようですね.
x = 2
y = 3
z = 2
case x do
^y -> IO.inspect "x === y, #{y}"
^z -> IO.inspect "x === z, #{z}"
end
うむ. ^
をつけると期待通り “x === z” になりましたね.
さて,先程の case
式に戻りましょう.
x
に 4 がきたときはどうなるでしょう?試してみます.
x = 4
case x do
1 -> IO.inspect "x => 1"
2 -> IO.inspect "x => 2"
3 -> IO.inspect "x => 3"
end
# ** (CaseClauseError) no case clause matching: 4
# elixir_src.exs:2: (file)
# (elixir) lib/code.ex:307: Code.require_file/2
あばば.エラーでプロセスが落ちてしまいました.
これを防止するには最後に true
か _
を使ってマッチングするとよいです.
x = 4
case x do
1 -> IO.inspect "x => 1"
2 -> IO.inspect "x => 2"
3 -> IO.inspect "x => 3"
_ -> IO.inspect "another x"
end
うまくいきましたね.これで未知の値も拾えるようになりました.
しかし!ここまで書いておいて何ですが,ErlangVM では “Let it crash” という哲学があります. 未知の値を無理に拾おうとせず,わかっている所だけ扱うようにしてください.
そうすると,先ほど見たようにプロセスは落ちてしまいますね. プロセスが落ちたらプログラムが終わる!と思うかもしれませんが, ErlangVM ではプロセスが落ちてもプログラムを終わらせない方法が標準で用意されているんです. OTP というところで説明しますね.
ひとまずここでは「予想できていること,状態についてだけ扱う」ということだけ覚えておいてください.
さて case
文でもパターンマッチングが利用できます.
x = 2
y = 3
result = case {x, y} do
{1, 2} -> :a
{1, 3} -> :b
{2, a} -> a
{3, _} -> :c
end
IO.inspect result
便利ですね.
さて,今までの case
では同じ型についてだけ取り扱ってきました.
異なる型,例えば整数の 1
と小数点つきの数 1.1
,文字列 "1"
をいっぺんに扱う方法はあるでしょうか?
Elixir では guard
というものを使えばできます.
x = 1
y = 1.1
z = "1"
result1 = case x do
a when is_integer(x) -> a
a when is_float(x) -> trunc(a)
a when is_binary(x) -> String.to_integer(a)
end
result2 = case y do
a when is_integer(y) -> a
a when is_float(y) -> trunc(a)
a when is_binary(y) -> String.to_integer(a)
end
result3 = case z do
a when is_integer(z) -> a
a when is_float(z) -> trunc(a)
a when is_binary(z) -> String.to_integer(a)
end
IO.inspect result1
IO.inspect result2
IO.inspect result3
do/end
と構文糖衣について書く- Keyword についてここで書こうかなあ
実はですねえ,if 式に限らず,Elixir の do … end は do: .... というキーワード引数のシンタックスシュガーなんですよ. ですから下の式は全て同じ意味になります.
if true do
"foo"
else
"bar"
end
if(true, do: "foo", else: "bar")
if(true, [{:do, "foo"}, {:else, "bar"}])
if を1行で書くことはそんなに多くないかもしれませんけど,
簡単な関数定義をするときに def
を 1 行で書くのはさっぱりしますね.
defmodule MultiLine
def bar do
"hoge"
end
def baz do
"fuga"
end
end
defmodule SingleLine
def bar do: "hoge"
def baz do: "fuga"
end
- モジュールの目的について書く
モジュールは,関数をあるグループにまとめて,探しやすくするためにあります.
例えば String に関する操作を探すとき,ひとまずは String
モジュールから探しはじめると見つかることが多いですね.
自分達でモジュールを定義するには defmodule
を使います.関数の定義は def
を使います.こんな感じです.
defmodule MyModule do
def plus(x, y) do
x + y
end
def minus(x, y) do
x - y
end
end
IO.inspect MyModule.plus(1, 2)
IO.inspect MyModule.minus(5, 3)
|>
の使いどころ- 第一引数に subject がくる法則
- メソッドチェーンのように
データに対して処理を連続して行いたいとき,例えば Ruby だとこんな感じで書けます.
[1,2,3,4,5,6] # => [1,2,3,4,5,6]
.map { |e| e + 1 } # => [2,3,4,5,6,7]
.select { |e| e.odd? } # => [3,5,7]
.select { |e| 3 < e } # => [5,7]
オブジェクトに関係つけている関数をオブジェクト経由で呼び出せるので,このように書けます. メソッドチェーンと呼ばれるものですね.
Elixir では,データに対しては関数が関係ついていません. ですからデータに対して処理を連続して行いたいときは,返り値を次の関数へ引数として渡します.
array = [1,2,3,4,5,6] # => [1,2,3,4,5,6]
mapped = Enum.map(array, &(&1 + 1)) # => [2,3,4,5,6,7]
odd = Enum.filter(mapped, &(rem(&1, 2) == 1)) # => [3,5,7]
over3 = Enum.filter(odd, &(3 < &1)) # => [5,7]
IO.inspect over3
読めるけど,1回しか使わない変数が多くて少し読みにくいですね.試しに変数をなくしてみましょう.
Enum.filter(
Enum.filter(
Enum.map([1,2,3,4,5,6], &(&1 + 1)),
&(rem(&1, 2) == 1)
),
&(3 < &1)
) # => [5,7]
変数はなくなったものの,上から下へ読んでいくのではなく, 括弧の内側から外側へと読むようになったのでちょっと慣れないと辛い感じです.
そこで,Elixir では変数を減らして,かつ上から下に読めるように |>
というものを用意してくれました.
F# という言語由来であると聞いたことがあります.
[1,2,3,4,5,6]
|> Enum.map(&(&1 + 1))
|> Enum.filter(&(rem(&1, 2) == 1))
|> Enum.filter(&(3 < &1))
# => [5,7]
|>
は,左(上)側の評価結果を,右側の関数の第一引数へと代入してくれる役割を持ったものです.
上と下の式はほとんど同じ意味を持っています.
"hoge" |> String.upcase |> String.replace("H", "M") # => MOGE
x1 = "hoge"
x2 = String.upcase(x1)
x3 = String.replace(x2, "H", "M") # => MOGE
Elixir では「subject は第一引数に取る」という鉄則があるので, |>
でデータを繋いでいくことができます.
皆さんが関数を定義するときにも,操作対象は第一引数に取るようにしましょう.
- 実際のソースコードをみてみる
- 字面は追えるようになっているはず
今までのプログラミング経験から特段大きなジャンプせずともできそうな感じしますよね!
- 第一章 完 -
- 送信
- 受信
- リンクあり
- リンクなし
- たくさん作ってみる
ここまでは, さてそれでは ErlangVM の特徴であるプロセスについて
- GenServer
- SuperVisor
- Application
- その他にもあるよ
mix new
- 関数でできることは関数でやる
- 関数でできないことの例
- 実装してみる
- コンパイル
- 実行
.ex
と.exs
の意図の違い
while
ループがない- 自分を呼び出す
[h|t]
について書くかも
for
の使いどころ- 二重ループ
- 代表的な処理をいくつか書いてみる
- 処理に使うモジュール
System
はelixir
コマンドから使ってみる
- 関数のガード、パターンマッチングについて書こうかなあ
import
use
require
について書く- ネストについて書く
- アトリビュートについて書く