webネタ

Webエンジニアが業務に関係することをメモしていく

ErlangVMで動くRuby風の関数型言語Elixir(エリクサー) : 基礎編

Erlangの奇妙な文法を覚えることなく、Erlangの機能が使えるエリクサー。

Ruby風の文法なのでRubyistには馴染みの関数などが使えるが、以下のことを頭にいれておくべし。

楽天Technologyカンファレンスでセッションがあったり、Erlangを簡単に書ける言語としてこれから普及していくのではないでしょうか!

インストール

brew install elixir

iex

iex

irbのようにiexで、試し書きが簡単にできます。(iexはerlのラッパー)

基本文法

四則演算

1 + 1    # 2
1 - 1    # 0
1 * 1    # 1
1 / 1    # 1.0
div 1, 1 # 1
rem 2, 4 # 2
# 2 % 4 not work

"a"             # string
1               # integer
1.2             # float
:id             # atom
[1,2,3]         # list
{1,2,3}         # tuple
[a: "a", b: 2]  # keyword

文字列

s = "str"
"[" <> s <> "]"          # [str]
"[#{s}]"                 # [str]
String.reverse "str"     # rts
String.slice "str", 1, 2 # tr
String.first "str"       # f
String.last "str"        # r
String.at "str", 1       # t
String.graphemes("str") # ["s", "t", "r"]

変数

v = "v"
# v = "vv" 不変なので代入不可

デバッグ

IO.inspect [1,2,3]
IO.puts "foo"  # 文字列のみ(改行コード付き)
IO.write "foo" # 文字列のみ(改行コードなし) 

コレクション

[head | tail] = [1,2,3]
head # 1
tail # [2,3

Enum.map [1,2,3], fn(i) -> i * i end   # [1,4,9]
Enum.map [1,2,3], &(&1 * &1)           # [1,4,9]

Enum.filter [1,2,3], &(Integer.odd?(&1)) # [1,3]
Enum.find [1,2,3], &(&1 == 1)    # 1
Enum.all? [1,3], &(Integer.odd?(&1))     # true  [scala = forall]
Enum.any? [1,3], &(Integer.odd?(&1))     # true  [scala = exists]
Enum.at [1,2,3], 1               # 2
Enum.max [1,2,3]                 # 3
Enum.join [1,2,3], "-"           # 1-2-3

f = fn
  i when rem(i, 2) == 0 -> [i]
  _ -> []
end
Enum.flat_map(1..10, f)              # [2, 4, 6, 8, 10]
Enum.map(1..10, f) |> List.flatten # [2, 4, 6, 8, 10]

パイプ

[1,2,3,4,5] |> Enum.filter(&(rem(&1,2) != 0)) |> Enum.map(&(&1 * &1)) # [1,9,25]

"str" |> String.graphemes |> Enum.map(&("-#{&1}-")) |> Enum.join # "-s--t--r-"

気軽に繋げて書くことができる。

条件

if

if true do
  "ok" # ok
else
  "ng"
end

# 簡易版
if true, do: "ok", else: "ng" # ok
if true, do: 1           # 1
if false, do: 1, else: 2 # 2

unless

unless false, do: "unless"

case

case 1 do
  1 -> p "ok" # ok
  _ -> p "ng"
end

case [1,2,3] do
  [1 | _] -> p "ok" # ok
  _ -> p "ng"
end

case {1,2,3} do
  {1,2,3} -> p "ok" # ok
  _ -> p "ng"
end

case [1,2,3] do
  [_ | [2,3]] -> p "ok"
  _ -> p "ng"
end

case [1] do
  [h | []] -> p h # 1
  _ -> p "ng"
end

a = 1
case true do
  ^a -> p "not match"
  _ -> p "match"
end

関数

基本

defmodule Math do
  def sum(a, b) do
    a + b
  end
end
Math.sum(1,2) # 3 defmoduleの中でないとdefは定義できない
Math.sum 1,2 # rubyのように()省略可

# ネスト
defmodule Math do
  defmodule Foo do
    def sum(a, b) do
      a + b
    end
  end
end
Math.Foo.sum 1,2 # 3

# 短く
defmodule Math do
  def sum(a, b), do: a + b
end
Math.sum 1, 2 # 3

匿名関数

f = fn(x, y) -> x + y end
f.(1,2) # 3

# 短く
f = &(&1 + &2)
f.(1,2) # 3

高階関数

ff = fn(f) -> f.(2,3) + f.(4,5) end
ff.(f) # 14

ガード

f2 = fn
  x when x < 1 -> "ng"
  _ -> "ok"
end
f2.(1) # ok

サンプル

# フィボナッチ
defmodule Fib do
  def f(l), do: f(l,1,1)
  def f(l, a, b) do
    if l > b, do: [b] ++ f(l, b, a + b), else: []
  end
end
Fib.f(30) # [1, 2, 3, 5, 8, 13, 21]
# リバース
defmodule Reverse do
  def r([head | tail]), do: r(tail) ++ [head]
  def r([]), do: []
end
Reverse.r [1,2,3]    # [3,2,1]
Enum.reverse [1,2,3] # [3,2,1]

クラスのようなもの

defrecord Person, name: "", age: 0 do
  def say(mine) do
    "I am " <> mine.name <> "."
  end
end
tanaka = Person.new(name: "tanaka", age: 20)
tanaka.say # I am tanaka.