ヽ(´・肉・`)ノログ

How do we fighting without fighting?

プログラミング言語Elixir

2015-06-13 オープンソースカンファレンス2015 Hokkaido - オープンソースの文化祭!プログラミング言語 Elxiir という内容を発表した.

当日は朝最初の発表にもかかわらず 80 人部屋の席が半分くらい埋まっていてびっくりした.

スライドは OSC 2015 Hokkaido Elixir - niku - Rabbit Slide Show にあがっている.

スライドのベースにした org ファイルをここに残す.だいたい 45 分普通に話して分量は丁度よかった.

またブースにも出展してみた.こちらも含めたOSCふりかえりをここに書く

資料の目的

Elixirの特徴

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

Elixir の特徴 - Qiita

Elixirの特徴

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

インストール

Installing Elixir - Elixir

iexコマンド

IEx

Basic types - Elixir

代表的なものを軽く紹介する

1          # 整数
0x1F       # 整数
1.0        # 小数
true       # ブーリアン
:atom      # アトム
"elixir"   # 文字列
[1, 2, 3]  # リスト
{1, 2, 3}  # タプル

整数

1          # 整数 <-
0x1F       # 整数 <-
1.0        # 小数
true       # ブーリアン
:atom      # アトム
"elixir"   # 文字列
[1, 2, 3]  # リスト
{1, 2, 3}  # タプル
1          # => 1
0x1F       # => 31
1 + 2      # => 3
5 * 5      # => 25
10 / 2     # => 5.0
div(10, 2) # => 5
rem(10, 3) # => 1

1 + 2 を題材に,中置演算子は一部の演算子だけのシンタックスシュガーであることを伝える. ついでに,括弧が省略できることも伝える.

同じものを指す
div(10, 2)        # => 5
div 10, 2         # => 5
Kernel.div(10, 2) # => 5
同じものを指す
1 + 2             # => 3
Kernel.+(1, 2)    # => 3
Kernel.+ 1, 2     # => 3

Elixirの関数

よりみち. ErlangVM では関数は モジュール名.関数名/引数の数 という形式で一意に表される. 例えば IO.inspect/1 の場合, IO モジュールの inspect という関数で,引数の数は 1 つのもの. つまり IO.inspect("hello") のように使うもののこと.

関数の調べかた

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

1 + 1+ について知りたい場合

iex(1)> h Kernel.+/2

                        def +(left, right)

Arithmetic plus.

Allowed in guard tests. Inlined by the compiler.

Examples

┃ iex> 1 + 2
┃ 3

ちなみに引数の数を省略するとマッチする全てを返す

h Kernel.+ と引数の数を省略すると, Kernel.+ で,引数の数を問わないで探す

iex(3)> h Kernel.+
                           def +(value)
(省略)
                        def +(left, right)
(省略)

web からは

Elixir v1.0.4 Documentation

をみるとよい.

web のドキュメントは日本語版を作ってくれた人もいる( @k1complete さん作)

elixirリファレンスのぺーじ

h で表示する内容を日本語にもできるみたい(試していない)

Elixir - iexでの日本語版ヘルプの使い方 - Qiita

整数を操作する関数

整数を操作する関数もっぱら以下の 2 つモジュールにまとまっていそう

小数

1          # 整数
0x1F       # 整数
1.0        # 小数 <-
true       # ブーリアン
:atom      # アトム
"elixir"   # 文字列
[1, 2, 3]  # リスト
{1, 2, 3}  # タプル
1.0                # => 1.0
1.0e-5             # => 1.0e-5
1.0e-5 === 0.00001 # => true
round(3.58)        # => 4
trunc(3.58)        # => 3

小数を操作する関数

ブーリアン

1          # 整数
0x1F       # 整数
1.0        # 小数
true       # ブーリアン <-
:atom      # アトム
"elixir"   # 文字列
[1, 2, 3]  # リスト
{1, 2, 3}  # タプル

ブーリアンには特に Boolean モジュールというものは用意されていない

true  # => true
false # => false
!true # => false

ブーリアンを操作する関数

みつからなかった!

アトム

1          # 整数
0x1F       # 整数
1.0        # 小数
true       # ブーリアン
:atom      # アトム <-
"elixir"   # 文字列
[1, 2, 3]  # リスト
{1, 2, 3}  # タプル

自分の名前が自分の値を表すような定数.他の言語だとシンボルと呼ばれるようなもの.

:foo             # => :foo
:"foo-bar"       # => :"foo-bar"
ブーリアン値はアトムでした
:true === true   # => true
:false === false # => true

アトムを操作する関数

文字列

1          # 整数
0x1F       # 整数
1.0        # 小数
true       # ブーリアン
:atom      # アトム
"elixir"   # 文字列 <-
[1, 2, 3]  # リスト
{1, 2, 3}  # タプル

Binaries, strings and char lists - Elixir

"abc"                      # => "abc"
"こんにちは"               # => "こんにちは"
"1 + 2 は #{ 1 + 2 } です" # => "1 + 2 は 3 です"
Elixir では文字列はバイナリとして扱われる
is_binary("abc")           # => true

文字列を操作する関数

byte_size("日本語")     # => 9
String.length("日本語") # => 3
String.at("日本語", 1)  # => "本"

複数の値を格納する値

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

1          # 整数
0x1F       # 整数
1.0        # 小数
true       # ブーリアン
:atom      # アトム
"elixir"   # 文字列
[1, 2, 3]  # リスト <-
{1, 2, 3}  # タプル <-

Basic types - Elixir

Keywords, maps and dicts - Elixir

複数の値を格納する値

[1, 2, 3]                   # リスト <-
{1, 2, 3}                   # タプル
%{foo: "hoge", bar: "fuga"} # マップ
[{:foo, "hoge"},
 {:bar, "fuga"},
 {:foo, "moge"}]            # キーワードリスト

リスト

[1,2,3]          # => [1, 2, 3]
[:a, "b", 'c']   # => [:a, "b", 'c']
[[:x], [:y, :z]] # => [[:x], [:y, :z]]

リストを操作する関数

Enum.map([1,2,3], fn(x) -> x * 2 end) # => [2, 4, 6]
List.last([4,5,6])                    # => 6

経験上まず Enum を探すのがよい

タプル

[1, 2, 3]                   # リスト
{1, 2, 3}                   # タプル <-
%{foo: "hoge", bar: "fuga"} # マップ
[{:foo, "hoge"},
 {:bar, "fuga"},
 {:foo, "moge"}]            # キーワードリスト
{:a, 1}         # => {:a, 1}
{"x", "y", "z"} # => {"x", "y", "z"}

タプルを操作する関数

elem({:a, "abc"}, 1)                # => abc
Tuple.delete_at({"x", "y", "z"}, 1) # => {"x", "z"}

マップ

[1, 2, 3]                   # リスト
{1, 2, 3}                   # タプル
%{foo: "hoge", bar: "fuga"} # マップ <-
[{:foo, "hoge"},
 {:bar, "fuga"},
 {:foo, "moge"}]            # キーワードリスト
%{:a => 1, :b => 2}          # => %{a: 1, b: 2}
%{a: 1, b: 2}                # => %{a: 1, b: 2}
%{:a => 1, :b => 2, :a => 3} # => %{a: 3, b: 2}

マップを操作する関数

Enum.any?(%{a: 1, b: 2}, fn({k,_v}) -> k === :b end) # => true
Dict.update!(%{a: 1, b: 2}, :a, fn(v) -> v + 10 end) # => %{a: 11, b: 2}
Map.new                                              # => %{}

Enum -> Dict -> Map の順番で探すとよい

キーワードリスト

[1, 2, 3]                   # リスト
{1, 2, 3}                   # タプル
%{foo: "hoge", bar: "fuga"} # マップ
[{:foo, "hoge"},
 {:bar, "fuga"},
 {:foo, "moge"}]            # キーワードリスト <-
[{:foo, "x"}, {:bar, "y"}]              # => [foo: "x", bar: "y"]
[foo: "x", bar: "y"]                    # => [foo: "x", bar: "y"]
[{:foo, "x"}, {:bar, "y"}, {:foo, "z"}] # => [foo: "x", bar: "y", foo: "z"]

キーワードリストを操作する関数

Keyword.get_values([foo: "x", bar: "y", foo: "z"], :foo) # => ["x", "z"]

特定のユースケースで多用されるため, Keyword モジュールにある関数で処理することが多い

モジュールと関数の定義

主要な値の紹介が終わったので,それを操作する関数の定義と,関数をグループ化するモジュールの定義について話す

Modules - Elixir

defmodule MyOperand do
  def plus(x, y) do
    x + y
  end

  def minus(x, y) do
    x - y
  end
end
MyOperand.plus(1, 2)  # => 3
MyOperand.minus(5, 3) # => 2

do-endはキーワードリストで実装されている

case, cond and if - Elixir

if true do
  "foo"
else
  "bar"
end
if(true, do: "foo", else: "bar")
if(true, [{:do, "foo"}, {:else, "bar"}])
defmodule SingleLine
  def bar do: "hoge"
  def baz do: "fuga"
end

|>

自分で定義する関数でも,第一引数には subject がくるようにする 大きな理由の一つに,多用する |> の流れを壊さないようにするというものがある

[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]
array  = [1,2,3,4,5,6]                                    # => [1,2,3,4,5,6]
mapped = Enum.map(array,     fn(x) -> x + 1 end)          # => [2,3,4,5,6,7]
odd    = Enum.filter(mapped, fn(x) -> rem(x, 2) == 1 end) # => [3,5,7]
over3  = Enum.filter(odd,    fn(x) -> 3 < x end)          # => [5,7]
over3                                                     # => [5,7]
Enum.filter(
  Enum.filter(
    Enum.map([1,2,3,4,5,6], fn(x) -> x + 1 end),
    fn(x) -> rem(x, 2) == 1 end
  ),
  fn(x) -> 3 < x end
) # => [5,7]
[1,2,3,4,5,6]
|> Enum.map(fn(x) -> x + 1 end)
|> Enum.filter(fn(x) -> rem(x, 2) == 1 end)
|> Enum.filter(fn(x) -> 3 < x end)
=> [5,7]
"hoge" |> String.upcase |> String.replace("H", "M") # => MOGE

x1 = "hoge"
x2 = String.upcase(x1)
x3 = String.replace(x2, "H", "M") # => MOGE

束縛

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

x = 1
x        # => 1
y = :abc
y        # => :abc
↑実質y1
y = :def
y        # => :def
↑実質y2

パターンマッチング

[h|t] = [1,2,3]
h # => 1
t # => [2,3]
{x, y} = {123, 456}
x # => 123
y # => 456
%{i: a} = %{i: "あい", j: "じぇい", k: "けい"}
a # => "あい"
%{i: b} = %{j: "じぇい", k: "けい"}
=> (MatchError) no match of right hand side value: %{j: "じぇい", k: "けい"}

Pattern matching - Elixir

文字へのパターンマッチング

<<h, t :: binary>> = "abcdef"
h              # => 97
t              # => "bcdef"
<<97>> === "a" # => true

97 という数字が出てしまった.これが何を表しているかというと,文字コードポイントの値である. Elixir の文字はバイナリとして扱っている.バイナリの 97 は, a という文字を表す. この場合は 97 と表示した方がいいか,a と表示した方がいいかを コンピュータで判断がつけられないので 97 と表示してしまっている. <<97>> と =”a”= は同じ内容を指しているけど,表示が異なるという認識でよい.

ここは今さらっといく. デバッグのときに文字をいじっていたはずなのに数字が出てきたらなんとなく思い出すとやくに立つ. 詳しく知りければ Binaries, strings and char lists - Elixir を読むとよい.

ともかく,文字も”複数の値を格納している値”とみなして, 分解して変数を割り当てられている.

匿名関数

Basic types - Elixir

モジュール内に名前つきの関数を定義する方法は説明した その他,名前をつけずに関数を使い捨てで利用する方法

add = fn(x, y) -> x + y end
add.(2, 3) # => 5

匿名関数はこんな感じで書くことができる. 呼び出すときには . をつけるのを忘れないようにすること. 実のところ匿名関数を変数へ束縛して使うことはほとんどない.

x1 = Enum.map([1,2,3], fn (x) -> x + 5 end)
x1 # => [6, 7, 8]
x2 = Enum.map([1,2,3], &(&1 + 5))
x2 # => [6, 7, 8]

関数(今回の例だと Enum.map )に匿名関数を渡して,やりたい事を書くときに使うことが多い. よく使うので省略した書き方もできるようになっている.

制御構造

case, cond and if - Elixir

if式

if true do
  "true"
else
  "false"
end
=> "true"
x = if true do
      "**true**"
    else
      "**false**"
    end
x # => **true**

case式

x = 2
case x do
  1 -> "x => 1"
  2 -> "x => 2"
end
=> "x => 2"
x = 2; y = 3; z = 2
case x do
  y -> "x === y, #{y}"
  z -> "x === z, #{z}"
end
=> "x === y, 2"
x = 2; y = 3; z = 2
case x do
  ^y -> "x === y, #{y}"
  ^z -> "x === z, #{z}"
end
=> "x === z, 2"
x = 3
case x do
  1 -> "x => 1"
  2 -> "x => 2"
end
** (CaseClauseError) no case clause matching: 3
    elixir_src.exs:2: (file)
    (elixir) lib/code.ex:307: Code.require_file/2
x = 3
case x do
  1 -> "x => 1"
  2 -> "x => 2"
  _ -> "another x"
end
=> "another x"

しかし!ErlangVM では “Let it crash” という哲学があり, 未知の値は無理にハンドリングせずに落としてしまう方が好ましい.

プロセスは落ちてしまう.プロセスが落ちるとプログラム自体が終わる!と思うかもしれないが ErlangVM ではプロセスが落ちてもプログラム自体を終わらせない方法が標準で用意されている.

今回は取り上げないが OTP や Supervisor について調べるとよい. ひとまずここでは「予想できていること,状態についてだけ扱う」ということだけ覚えておいてほしい.

case 式でもパターンマッチングが利用できる

x = 2
y = 3
result = case {x, y} do
           {1, 2} -> :a
           {1, 3} -> :b
           {2, a} -> a
           {3, _} -> :c
         end
result # => 3

ガード

case, cond and if - Elixir

x = 1
result = 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
result # => 1
x = 1.1
result = 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
result # => 1
x = "1"
result = 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
result # => 1

今日やったこと

簡単なElixirのはじめかた

このエントリーをはてなブックマークに追加