概要📖
Haskell
ではエラーが発生した際に対応するためにEither
というモナドを使うEither
はLeft(エラー時)
とRight(正常時)
の2つの値を持ち、それぞれ別の型の値を受け取ることが可能(Either = Left a | Right b
)Left
にはString
がよく用いられ、エラーメッセージを格納することが多いEither
が必要なのは呼び出し元に、なぜエラーになったのかを伝えるためMaybe
ではエラー、値なしの理由を呼び出しもとに伝えることが出来ないElixir
で実装するにはTuple
とAtom
を使うのが良さげ
前回までのあらすじ📖
「入門Haskellプログラミング」を読み進めながら、理解を深めるためにElixir
を使ってHaskell
のFunctor
とApplicative
, Monad
を実装しました。
[asin:B07SFCMP66:detail]
以上の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”).
[https://hackage.haskell.org/package/base-4.15.0.0/docs/Data-Either.html:embed:cite]
ドキュメントに書かれているように、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
から閲覧可能です。
[https://replit.com/@okabeyuya/EithterinElixir:embed:cite]