やりたいこと
パターンマッチをバキバキに仕上げてくれるのが、when
から始まるguard構文。標準で様々な演算子と関数が使えるようになっている。やや量が多いので、しっかりと使いこなせるようにするため汎用性の高そうなguardを一通り書いてみた。
演算子
演算子は以下のものが利用可能。詳細はこちら参照
比較演算子(==,!=,===,!==,>,<,<=,>=)
論理演算子(and,or)と否定演算子(not,!)
算術演算子(+,-,*,/)
左側がリテラルの場合の<>と++
in演算子
チェック関数
で、以下がメインコンテンツ。標準ライブラリから様々なチェック関数が提供されているので、使いそうなものを片っ端から使ってみた。また、いくつかElixirの変数型仕様によるハマりどころもあったので、メモ書き程度だが付記しておく。
defmodule Demo.GuardTest do
use Demo.ConnCase
def this_is(x) when is_nil(x), do: "This is nil"
def this_is(x) when is_atom(x), do: "This is Atom"
def this_is(x) when is_binary(x), do: "This is Binary"
def this_is(x) when is_bitstring(x), do: "This is Bitstring"
def this_is(x) when is_boolean(x), do: "This is Boolean"
def this_is(x) when is_integer(x), do: "This is Integer"
def this_is(x) when is_float(x), do: "This is Float"
def this_is(x) when is_list(x), do: "This is List"
def this_is(x) when is_tuple(x) and tuple_size(x) == 2, do: "This is Tuple with two elements"
def this_is(x) when is_tuple(x), do: "This is Tuple"
def this_is(x) when is_map(x), do: "This is Map"
def this_is(x) when is_function(x, 2), do: "This is Function with two arguments"
def this_is(x) when is_function(x), do: "This is Function"
def this_is_number(x) when is_number(x), do: "This is Number; can be both Integer/Float"
def this_is_actually_boolean(x) when is_boolean(x), do: "This is Actually Boolean"
def they_are(x, y) when is_atom(x) and is_binary(y), do: "They are Atom and Binary"
test "nil in guards", _ do
assert this_is(nil) == "This is nil"
end
test "Atoms in guards", _ do
assert this_is(:foobar) == "This is Atom"
assert this_is(false) == "This is Atom"
end
test "Strings in guards", _ do
assert this_is("foobar") == "This is Binary"
assert this_is(<<102>>) == "This is Binary"
assert this_is(<< 1 :: size(1)>>) == "This is Bitstring"
assert this_is('foobar') == "This is List"
end
test "Booleans in guards", _ do
assert this_is(true) != "This is Boolean" # this matches is_atom so the function returns "This is Atom"
assert this_is_actually_boolean(true) == "This is Actually Boolean"
assert this_is_actually_boolean(:true) == "This is Actually Boolean" # this matches is_boolean too because true/false are technically atom
end
test "Numbers in guards", _ do
assert this_is(1) == "This is Integer"
assert this_is(0b10) == "This is Integer" # Binary
assert this_is(0o77) == "This is Integer" # Octal
assert this_is(0x1F) == "This is Integer" # Hexadecimal
assert this_is(0.1) == "This is Float"
assert this_is_number(101) == "This is Number; can be both Integer/Float"
assert this_is_number(1.1) == "This is Number; can be both Integer/Float"
end
test "Lists in guards", _ do
assert this_is([:ok, "foo", 0]) == "This is List"
assert this_is('foobar') == "This is List"
assert this_is([foo: 1, bar: 2]) == "This is List"
end
test "Tuples in guards", _ do
assert this_is({:error}) == "This is Tuple"
assert this_is({:ok, "foo"}) == "This is Tuple with two elements"
end
test "Maps in guards", _ do
assert this_is(%{:foo => "foo", "bar" => "bar"}) == "This is Map"
end
test "Functions in guards", _ do
double = fn(x) -> x * 2 end
remainder = &rem/2
assert this_is(double) == "This is Function"
assert this_is(remainder) == "This is Function with two arguments"
end
test "Multiple arguments in guards", _ do
assert they_are(:ok, "foobar") == "They are Atom and Binary"
end
end
注釈:Stringについて
Elixirは文字列についての扱いが若干ややこしく、学習を進める前にBinaries, strings and char listsを熟読しておくと良い。
かいつまんで解説すると、Elixirで文字列と言えば、ダブルクォーテーションで囲まれた文字列で表される(i.e "foobar"
)、UTF-8でエンコーディングされたバイナリである。また、String型というようなものは存在しないため、is_string
というモノが仮にあったとしても、厳密にそれが文字列かどうかの保証はできない。従って、is_binary/1
を使って文字列かどうかの大まかな判定を行うのがElixirでの習わしとなっている。
他方で、シングルクォーテーションで囲まれた文字列(i.e 'foobar'
)で表されるChar Listというものがあるが、これは主にErlangとの互換性を持つために用意された型である。
あとがき
guards素敵すぎ。パターンマッチは言語機能として非常に強力で、簡潔に期待する引数を記述できる上、書いてて楽しいという俺得プログラマー得な機能。是非マスターして、かっこ良くて分かりやすいコードを書けるようにしたい。
こんなのもあるよ
[Elixir] Task QueueのExqをPhoenixで使う
[Elixir] PhoenixでPlugの自作
[Elixir] Phoenixで日付/時間を扱う