8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

OKを使ってElixirの :ok, :error タプルをエレガントに処理

Last updated at Posted at 2018-09-22

はじめに

Elixir では、関数の戻り値として :ok:error を含むタプルが返されることが多々あります。通常これらの値はパターンマッチングを利用して扱うことと思いますが、 :ok などの記述が何度も繰り返されるのは見ようによっては冗長とも取れます。

この記事では、それら :ok:error を含む値の処理を簡潔に記述することができるライブラリ OK について紹介します。

:ok, :error を返す関数の処理

まずは、 OK を使用しない通常のコードについて確認をしてみましょう。次のコードを見てください。

greeting.ex
defmodule Greeting do
  def get_person1() do
    {:ok, "Bob"}
  end

  def get_person2() do
    {:ok, "Jack"}
  end

  def get_person3() do
    {:ok, "Hiroshi"}
  end
end

見ての通り、 Greeting module には人の名前を取得する関数が用意してあり、 :ok:error を含むタプルとして値を返します。
さらに、 Greeting に上記の関数を利用して挨拶をする関数 hello/0 を追加してみます。

greeting.ex
defmodule Greeting do
  # ...

  def hello() do
    with {:ok, p1} <- get_person1(),
         {:ok, p2} <- get_person2(),
         {:ok, p3} <- get_person3() do
      {:ok, "Hello, #{p1}, #{p2}, #{p3}"}
    else
      err -> err
    end
  end
end

Greeting.hello/0 を実行した時の結果は以下のようになります。

iex
iex(1)> Greeting.hello()
{:ok, "Hello, Bob, Jack, Hiroshi"}

もし、 Greeting.get_person2/0{:error, :not_found} を返す場合は、 Greeting.hello/0 の実行結果は次のようになります。

iex
iex(1)> Greeting.hello()
{:error, :not_found}

この関数の動作自体は特に問題にはなりませんが、 Greeting.hello/0 内の記述はタプルを多く含むため若干冗長な印象も受けます。
ここで OK を使用すると、この処理をより簡潔に記述することができるようになります。

OK.forの使用

OK を使うことで先のコードがどのように変化するでしょうか。
まずは下準備として、 mix.exs の deps に OK を追加します。

mix.exs
  defp deps do
    [{:ok, "~> 2.0"}]
  end

これで OK が使用できるようになりました。次に、 GreetingOK を require して、コードを次のように変更しましょう。

greeting.ex
defmodule Greeting do
  require OK

  def hello() do
    OK.for do
      p1 <- get_person1()
      p2 <- get_person2()
      p3 <- get_person3()
    after
      "Hello, #{p1}, #{p2}, #{p3}"
    end
  end
end

with の代わりに OK.for が追加され、同時に :ok を含めたタプルの記述が消えてスッキリしました。この場合でも Greeting.hello/0 を呼び出した場合の実行結果は、変更前と同じになります。

コードの内容から大方検討がつくように、 OK.for は、失敗する可能性がある複数の操作をまとめるマクロです。 {:ok} の記述を省略して値の格納が可能となり、 after で記述した値が {:ok} でラップされて戻り値となります。OK.for 内のいずれかの関数で {:error} が返されれば、その値が OK.for の結果となります。

このように、 OK.for を使用することで、 :ok:error の記述を省くことが可能となり、より本質的な処理に集中することができます。

それでは、 OK で使用可能な他のマクロについても見てみましょう。

OK.try

OK.for では、 {:error, _} が関数から返された場合はその値をそのまま戻り値としていましたが、エラーの値に応じて処理をハンドルしたいケースも考えられます。その場合に便利なのが OK.try です。次のコードを見てみましょう。

greeting.ex
defmodule Greeting do
  require OK

  def hello do
    OK.try do
      p1 <- get_person1()
      p2 <- get_person2()
      p3 <- get_person3()
    after
      "Hello, #{p1}, #{p2}, #{p3}"
    rescue
      :not_found -> {:ok, "Hello, there"}
      err -> {:error, err}
    end
  end
end

変更点としては、 OK.try を使用していること、そして rescue が加えられたことです。rescue では、 :error タプルと共に返された値に応じた処理を定義しています。このようにして、エラーに応じた処理を簡潔に記述することができます。

パイプ

OK は独自のパイプも提供していて、これらを利用することで :ok を返す関数を簡潔に並べて記述することができます。

~>

~> は、 OK.map/2 と同等の役割を果たすパイプであり、 :ok と共に渡された値の処理を記述することができます。以下の例を見てみましょう。

defmodule Math do
  use OK.Pipe

  def calc() do
    fetch()      # {:ok, 5}
    ~> double()  # {:ok, 10}
    ~> plus(3)   # {:ok, 13}
  end

  def fetch(), do: {:ok, 5}

  def double(a), do: a * 2

  def plus(a, b), do: a + b
end

このように、 {:ok} のタプルでラップされた値が ~> で連結された関数の第1パラメータとして渡されて処理が行われます。また、いずれかの関数で {:error} が返されれば、その値が結果となります。

~>>

~>>OK.flat_map/2 と同等の役割を果たすパイプであり、 ~> と同様に :ok と共に渡された値を指定された関数で処理します。 ~> との違いは、処理した結果を :ok のタプルでラップしない点にあります。先ほどの例を少し変更して、動作を確認しましょう。

defmodule Math do
  use OK.Pipe

  def calc() do
    fetch()       # {:ok, 5}
    ~>> double()  # 10
  end

  # ...
end

最後に

この記事では OK を使用することで :ok:error を含むタプルの処理を簡潔に記述できることを紹介しました。他にもいくつか便利な関数が提供されているので、詳細を知りたい方は ドキュメント を確認してみてください。

参考

8
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?