Skip to content

Instantly share code, notes, and snippets.

@niku
Last active August 29, 2015 14:21
Show Gist options
  • Save niku/cb69db08aaceac83f8ad to your computer and use it in GitHub Desktop.
Save niku/cb69db08aaceac83f8ad to your computer and use it in GitHub Desktop.
2015-06-13 OSC Hokkaido 2015 で発表する プログラミング言語Elxiir の資料のアウトライン

(WIP)

プログラミング言語Elixir

2015-06-13 OSC Hokkaido 2015 で発表する プログラミング言語 Elxiir の資料

資料の目的

  • Elixir を触ってみたことがないプログラマが,Elixir を触ってみることができるようになること
  • Elixir でやりたいことがあったとき,どの辺を眺めたり探せばよいか,推測できるようになること

Elixir自己紹介

Elixir のトップページに載っている特徴について紹介と簡単にまとめる

ぼくがえりくさーですきなとこ

なじみのある文面でプログラムを書くと ErlangVM で処理できるところ

インストール

どうしようかな.

インストールは

  • つまづきやすい
  • 変わりやすい

ことがあるから

  • URLを示して簡潔に書く
  • 丁寧に書く

どちらか.簡潔に書こうかなあ.

iexコマンド

IEx

  • これから紹介することを簡単に試すための REPL の使いかたを書く
  • 評価を中断させるための #iex:break について書く
  • def がトップレベルに書けないことについて書く

Basic types - Elixir

プログラミングによくある要素/操作が普通にあることを示す

  • 値と,値を操作する主要なモジュールについて書く
  • 値のまとまり (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") のように使うもののこと.

なぜ最初にこんな面倒そうなことを伝えるかというと,みなさんが自分でドキュメントを引けるようにするため.

例えば 1 + 1+ について知りたい場合

  • 1 + 1Kernel.+(1, 1) のことだ
  • モジュールは Kernel 関数は + 引数の数は 2 つだから Kernel.+/2 だな
  • iex から h Kernel.+/2 とするとドキュメントが引ける
  • ちなみに h Kernel.+/1 もある.正の数 +1 のこと.
  • h Kernel.+ と引数の数を省略すると, Kernel.+/1Kernel.+/2 の説明が出てくる

h で出てくる内容も日本語の方がよい場合は Elixir - iexでの日本語版ヘルプの使い方 - Qiita を参考にするとできるみたい(試していない)

整数を操作するモジュールはもっぱら以下の 2 つを眺めるとよさそう

  • Kernel+/2 -/2 div/2 などがある
  • Integeris_odd/1 などがある

小数

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)
  • Kernelround/1 trunc/1 などがある
  • Floatceil/1foor/1 などがある

ブーリアン

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 エンコードされているバイナリをうまく扱うことができます

複数の値を格納する値

Basic types - Elixir

Keywords, maps and dicts - Elixir

値を格納する型について書く

  • タプル,リスト,マップなど値を格納する型について書く
  • 操作するモジュールについて書く

ここまでは,値について話してきましたね. 次に,値を格納する値について話します. つまり,配列やハッシュマップなどのことです.

List

いわゆる配列です.値はなんでも,何個でも入ります. [] でくくります.

IO.inspect [1,2,3]
IO.inspect [:a, "b", 'c']
IO.inspect [[:x], [:y, :z]]

Tuple

中に入る個数が決まった入れ物です. {} でくくります.

IO.inspect {:a, 1}
IO.inspect {"x", "y", "z"}

Map

あるキーと,それに対応する値を一組に持つ入れ物です. いわゆるキーバリューストアです. キーは重複して定義できず,上書きされます. %{} でくくります.

IO.inspect %{:a => 1, :b => 2}
IO.inspect %{a: 1, b: 2}
IO.inspect %{:a => 1, :b => 2, :a => 3}

Keyword

1 つめの要素がアトム,2 つめの要素が任意の値になっているタプルを持つ配列です. Map とは異なり,同じ名前のキーを 2 つ保持することができます.

IO.inspect [{:foo, "x"}, {:bar, "y"}]
IO.inspect [{:foo, "x"}, {:bar, "y"}, {:foo, "z"}]

束縛

Pattern matching - Elixir

いわゆる代入のような,値へ名前をつける方法について書く

  • 値の束縛と ^ について書く
  • パターンマッチングについて書こうかなあ

Elixir では以下のような形式で値を変数に結びつけることができます. y の例では,同じ名前で 2 回値を結びつけているように見えますけど, 実際にはコンパイル時には y1y2 のように別の変数名に変えられているそうです.

  • 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 を読むといいです.

ともかく,文字も”複数の値を格納している値”とみなして, 分解して変数を割り当てられることがおわかりいただけたと思います.

無名関数

Basic types - 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

制御構造

case, cond and if - Elixir

場合わけする構文について書く

  • if-else
  • case
  • ガード節
  • cond

ふつうのIF式です. else もありますね. Elixir で false として扱われるのは, falsenil だけです. それ以外は 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式だと truefalse のみを扱います. それで十分なことも多いのですが, 値が 123 の場合に動作を変えたい場合はどうしたらいいでしょうか? 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

xz が等しいので “x === z” の方を期待していたのに,”x === y” が出てしまいました.

何が起こっているかというと, yx の値が再束縛されて, ここでの 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豆知識

case, cond and if - Elixir

  • 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

モジュールと関数の定義

Module attributes - Elixir

  • モジュールの目的について書く

モジュールは,関数をあるグループにまとめて,探しやすくするためにあります.

例えば 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 の特徴であるプロセスについて

OTP

  • GenServer
  • SuperVisor
  • Application
  • その他にもあるよ

mixコマンド

  • mix new

マクロ

  • 関数でできることは関数でやる
  • 関数でできないことの例
  • 実装してみる

elixirコマンド

  • コンパイル
  • 実行
  • .ex.exs の意図の違い

再帰

  • while ループがない
  • 自分を呼び出す
  • [h|t] について書くかも

内包表記

  • for の使いどころ
  • 二重ループ

モジュール紹介

  • 代表的な処理をいくつか書いてみる
  • 処理に使うモジュール
  • Systemelixir コマンドから使ってみる

一歩進んだモジュール,関数定義

  • 関数のガード、パターンマッチングについて書こうかなあ
  • import use require について書く
  • ネストについて書く
  • アトリビュートについて書く
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment