概要📖
Haskellではエラーが発生した際に対応するためにEitherというモナドを使うEitherはLeft(エラー時)とRight(正常時)の2つの値を持ち、それぞれ別の型の値を受け取ることが可能(Either = Left a | Right b)LeftにはStringがよく用いられ、エラーメッセージを格納することが多いEitherが必要なのは呼び出し元に、なぜエラーになったのかを伝えるためMaybeではエラー、値なしの理由を呼び出しもとに伝えることが出来ないElixirで実装するにはTupleとAtomを使うのが良さげ
前回までのあらすじ📖
「入門Haskellプログラミング」を読み進めながら、理解を深めるためにElixirを使ってHaskellのFunctorとApplicative, Monadを実装しました。
以上の3つで「入門Haskellプログラミング」のUNIT5で扱われていたコンテキストでの型の操作に関しての項目が終了しました。
いやー、長かった!抽象的な概念が続いたので理解するのにかなり苦労しましたが、何とかUNIT5を終了することが出来ました。
これでコンテキスト内に存在する値に対しての操作(関数適応)が可能となりました🎉
最後のモナド🔥
本書で紹介されているモナドはあと1つです。
Eitherと呼ばれるエラーを扱うためのモナドで、呼び出し元になぜエラーになったのかを伝えるために利用されています。
(The Either type is sometimes used to represent a value which is either correct or an errorより判断)
The Either type represents values with two possibilities: a value of type Either a b is either Left a or Right b.
The Either type is sometimes used to represent a value which is either correct or an error; by convention, the Left constructor is used to hold an error value and the Right constructor is used to hold a correct value (mnemonic: "right" also means "correct").
ドキュメントに書かれているように、EitherはLeftとRightという2つの値を持つことが出来ます。
通常、Leftはエラー(想定外)が発生した時に用いられ、Rightは正常な場合に使用します。
LeftとRightはそれぞれ別の型の値を受け取ることが出来ます。本書をメインに使用例をいくつか確認しましたが、エラー時にはLeftにString型のエラーメッセージを。正常時にはRightに結果の値を渡しているケースが多いようです。
data Either a b = Left a | Right b -- Defined in ‘Data.Either’ Left "例外が発生しました!" Right True
なぜEitherが必要なのか🤔
Eitherの前身として紹介されていたのがMaybeというモナドです。Maybeは正常時にはJust aという値を返し、結果なしの場合にはNothingという値を返します。
maybeHead :: [a] -> Maybe a maybeHead [] = Nothing maybeHead (x:_) = Just x
Maybeは他言語でいうところの結果がnilやnullになる場合に対応します。値が存在しない時にはNothingという結果をただ返します。
先ほどのheadという関数を空配列に対して呼び出すと、実は例外が発生します。
Prelude> head [] *** Exception: Prelude.head: empty list
Maybeとパターンマッチを使って結果を内包させることで、例外が発生するのを防ぐことが出来ます。
またMaybeはMonadのインスタンスであるため、今まで紹介してきたFunctor, Applicative, Monadなど多くの恩恵を受けることが出来ます。
しかしながら、ただ「Nothing」という値が返ってきただけでは、なぜ取得することが出来なかったのか呼び出し元で知る術がありません。
maybeHead([]) -- Nothing -- 空配列には要素がないため本来は例外が発生する -> Maybeで対応 -> Nothingが返ってくるだけ
「空配列には要素がないからエラーだよ!」というメッセージが呼び出し元に返ってくると嬉しいケースがあります。(多くの人が使用するライブラリや関数を作成する場合など)
このような場合に対応するためにEitherが必要になります。
eitherHead :: [a] -> Either String a eitherHead [] = Left "Emplty List!" eitherHead (x:_) = Right x
eitherHead []
# Left "Emplty List!"
Elixirでの実装🧪
さっそくこのEitherをElixirで実装してみます。(EitherとElixirでゲシュタルト崩壊する...)
まずですが、LeftとRightという2つの値は構造体で定義するのは手間なので、Elixirの文化に従ってTuple({})を使用します。
Tupleを用いて:leftと:rightというAtomを使ってEitherを定義しました。
defmodule Either do @moduledoc """ Implement Either in Elixir Left -> { :left, a(any) } Right -> { :right, b(any) } """ def left(a), do: { :left, a } def right(b), do: { :right, b } end
これでLeftとRightという2つの値を表現することが出来ました。
Either.left("failed!") # {:left, "failed!"} Either.right("succcesed!") # {:right, "succcesed!"}
次にEitherを使ってhaed関数を定義します。空配列の場合にはLeftを返し、「空配列 -> エラー」ということが分かるメッセージを返します。
合わせて、リスト以外のデータが引数に指定された場合にも同じようにLeftを返します。
defmodule EitherExample do @moduledoc """ haed with Either haed -> Fetch first value from List """ def head([]), do: Either.left("Emplty List!") def head([h | _]), do: Either.right(h) def head(_), do: Either.left("head can only be used in List") end
呼び出してみます。
EitherExample.head([1,2,3,4,5]) # {:right, 1} EitherExample.head([]) # {:left, "Emplty List!"} EitherExample.head("") # {:left, "head can only be used in List"}
いい感じですね。いくつかテストを実行してみます。
def debugger(input, result, except) do is_match = result == except "[#{status(is_match)}] #{inspect input} -> #{inspect result} == #{inspect except} is #{is_match}" end def status(false), do: "Failure" def status(true), do: "Success" def executer(input, except) do result = EitherExample.head(input) output_format = debugger(input, result, except) IO.puts(output_format) end end [ { [1,2,3,4,5], Either.right(1) }, { [], Either.left("Emplty List!") }, { "", Either.left("Head can only be used in List") }, { 1, Either.left("Head can only be used in List") }, { {}, Either.left("Head can only be used in List") }, ] |> Enum.map(fn {input, except} -> TestHelper.executer(input, except) end)
[Success] [1, 2, 3, 4, 5] -> {:right, 1} == {:right, 1} is true [Success] [] -> {:left, "Emplty List!"} == {:left, "Emplty List!"} is true [Success] "" -> {:left, "Head can only be used in List"} == {:left, "Head can only be used in List"} is true [Success] 1 -> {:left, "Head can only be used in List"} == {:left, "Head can only be used in List"} is true [Success] {} -> {:left, "Head can only be used in List"} == {:left, "Head can only be used in List"} is true
全てのケースをpassしました。ElixirでhaskellのEitherを再現することに成功しました🎉
全体のコードはreplitから閲覧可能です。