ヽ(´・肉・`)ノログ

How do we fighting without fighting?

すごいE本をElixirでやる(9)

3.1 パターンマッチ

defmodule X do
  def greet(gender, name) do
    if(gender === :male) do
      IO.puts "Hello, Mr. #{name}!"
    else
      if(gender === :female) do
        IO.puts "Hello, Mrs. #{name}!"
      else
        IO.puts "Hello, #{name}!"
      end
    end
  end
end

X.greet(:male, "太郎")      # => Hello, Mr. 太郎!
X.greet(:female, "花子")    # => Hello, Mrs. 花子!
X.greet(:unknown, "ななし") # => Hello, ななし!

Elixir でも Erlang と同じようにパターンマッチを関数の定義に使える.これ好き.

事前条件を関数定義のところ書けるから関数の中に書くことを減らせて見通しがよくなる.

defmodule X do
  def greet(:male, name), do: IO.puts "Hello, Mr. #{name}!"
  def greet(:female, name), do: IO.puts "Hello, Mrs. #{name}!"
  def greet(_, name), do: IO.puts "Hello, #{name}!"
end

X.greet(:male, "太郎")      # => Hello, Mr. 太郎!
X.greet(:female, "花子")    # => Hello, Mrs. 花子!
X.greet(:unknown, "ななし") # => Hello, ななし!

Erlang だと同じ名前の関数定義をセミコンでつないで,最後はピリオドで終わらせるそうだ. Elixir はそういった制限がなく,いつもの関数定義と何もかえなくてよい.

defmodule X do
  def head([h|_]), do: h
  def second([_,x|_]), do: x
end

X.head([1,2,3,4])   # => 1
X.second([1,2,3,4]) # => 2

Erlang は変数の再束縛ができないようになっているようだ. Elixir は変数の再束縛ができるので,これとは少し話が異なる. ただし Elixir でも変数の束縛時に ^ をつけると「再束縛しない」という振舞いになる.

x = 1
x = 2
x # => 2
^x = 3
#> ** (MatchError) no match of right hand side value: 3
#>   orgmode_elixir_src.exs:4: (file)
#>   (elixir) lib/code.ex:307: Code.require_file/2

日付の束縛もみてみる.

defmodule X do
  def valid_time({date={y,m,d}, time={h,min,s}}) do
    IO.puts "The Date tuple #{inspect date} says today is: #{y}/#{m}/#{d}"
    IO.puts "The time tuple #{inspect time} indicates: #{h}:#{min}:#{s}"
  end

  def valid_time(_), do: IO.puts "Stop feeding me wrong data!"
end

X.valid_time({{2013,12,12},{09,04,43}})
#> The Date tuple {2013, 12, 12} says today is: 2013/12/12
#> The time tuple {9, 4, 43} indicates: 9:4:43
X.valid_time({{2013,09,06},{09,04}})
#> Stop feeding me wrong data!

ところでオーム社 PDF 1.0 版だと P37 にあるコード例の

5> functions:valid_time({{2013,12,12},{09,04,43}}).
The Date tuple ({2013,9,6}) says today is: 2013/9/6,
The time tuple ({9,4,43}) indicates: 9:4:43.
ok

{2013,12,12} を引数に渡しているけど,それと結果が合っていないように見えた.既知かもしれないが報告してみよう.

3.2 ガードだ、ガード!

Elixir でも Erlang のガードを利用できる.

defmodule X do
  def old_enough(age) when age >= 16, do: true
  def old_enough(_), do: false

  def right_age(age) when age >= 16 and age <= 104, do: true
  def right_age(_), do: false

  def wrong_age(age) when age < 16 or age > 104, do: false
  def wrong_age(_), do: true
end

X.old_enough(15) # => false
X.old_enough(16) # => true
X.right_age(15)  # => false
X.right_age(16)  # => true
X.right_age(104) # => true
X.right_age(105) # => false
X.wrong_age(104) # => true
X.wrong_age(105) # => false

Erlang と同様に,Elixir でもガードの中で利用できる関数には限りがある. 利用できる関数は case, cond and if - Elixir に記載がある.一般的には is_ で始まる関数はガード節の中で利用できるようだ.

3.3 Ifってなんだ?!

Erlang の If とは異なり,Elixir の If はまあまあ普通の If と似ており,評価がマッチしなくてもかまわない. もしマッチしなかった場合は nil が返る.

Erlang の If と似ているのは Elixir の case case, cond and if - Elixir かなあ.

defmodule WhatTheIf do
  def heh_fine_if do
    if 1 === 2 do
      "never match"
    end
  end

  def heh_fine_case(x) do
    case x do
      1 -> "works"
    end
  end

  def oh_god(x) do
    case x do
      2 -> :might_succeed
      _ -> :always_does
    end
  end

  def help_me(animal)
    talk = case animal do
  end
end

WhatTheIf.heh_fine_if      # => nil
WhatTheIf.heh_fine_case(1) # => "works"
WhatTheIf.heh_fine_case(2)
#> warning: this check/guard will always yield the same result
#> ** (CaseClauseError) no case clause matching: 2
#>     orgmode_elixir_src.exs:9: WhatTheIf.heh_fine_case/1
#>     orgmode_elixir_src.exs:17: (file)
#>     (elixir) lib/code.ex:307: Code.require_file/2
WhatTheIf.oh_god(2)        # => :might_succeed
WhatTheIf.oh_god(3)        # => :always_does
WhatTheIf.help_me(:dog)        # => {:dog, "says bark!"}
WhatTheIf.help_me("It hurts!") # => {"It hurts!", "says fgdafgna!"}

3.4 もしも…の場合(In Case … of)

Elixir では Erlang の case … of に相当するものを cond case, cond and if - Elixir で書くといいのかなあ(自信なし)

場合によっては普通に case でも書けそう.

defmodule X do
  def insert(x, set) do
    cond do
      Enum.empty?(set) -> [x]
      Enum.member?(set, x) -> set
      !Enum.member?(set, x) -> [x|set]
    end
  end
end

X.insert(1, [])        # => [1]
X.insert(1, [2, 3])    # => [1,2,3]
X.insert(1, [1, 2, 3]) # => [1,2,3]

3.5 どれを使えばいいの?

Elixir だとどうしたらいいのかなあ.

個人的な感覚としては 関数 > case = if > cond くらいな感じ.

何か良い指針があれば教えてもらいたい.

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