Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
35
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

[Elixir] guard構文徹底攻略

やりたいこと

パターンマッチをバキバキに仕上げてくれるのが、whenから始まるguard構文。標準で様々な演算子と関数が使えるようになっている。やや量が多いので、しっかりと使いこなせるようにするため汎用性の高そうなguardを一通り書いてみた。

演算子

演算子は以下のものが利用可能。詳細はこちら参照

比較演算子(==,!=,===,!==,>,<,<=,>=)
論理演算子(and,or)と否定演算子(not,!)
算術演算子(+,-,*,/)
左側がリテラルの場合の<>と++
in演算子

チェック関数

で、以下がメインコンテンツ。標準ライブラリから様々なチェック関数が提供されているので、使いそうなものを片っ端から使ってみた。また、いくつかElixirの変数型仕様によるハマりどころもあったので、メモ書き程度だが付記しておく。

test/guard_test.exs
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で日付/時間を扱う

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
35
Help us understand the problem. What are the problem?