14
4

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 3 years have passed since last update.

HaskellのEitherについて

Last updated at Posted at 2020-04-14

HaskellのEitherについて

HaskellのEitherモナドとそれに関連するData.Eitherの関数についてまとめました。この記事で使われているGHCのバージョンは8.8.3です。

Eitherとは

Eitherとは、Haskellに標準で入っているモナド・型の1つです。GHCiで定義を見てみましょう。

data Either a b = Left a | Right b      -- Defined in ‘Data.Either’

GHCiによると、Eitherは2つの値をとり、それぞれ違う2つの状態をとりえる、ということです。例外処理の際、失敗した計算を扱えます。Maybeモナドでは失敗した場合Nothingが返るだけで詳しい情報が得られませんが、Eitherでは失敗をより詳細に表現できます。

失敗 成功
Maybe a Nothing Just a
Either a b Left a Right b

失敗した場合はLeft a、成功すればRight bとして値を返します。

Eitherで遊んでみる

ghcir> :t Right 1011
Right 1011 :: Num b => Either a b
ghci> :t Left "aaa"
Left "aaa" :: Either [Char] b

Eitherの使いどころ

失敗しただけではなく、失敗した際の情報を得る(または処理を中断させない)ために使います。

Prelude.headを例にとります。

headはリストを引数にとり、先頭の要素を返します。ただし、リストが空だと*** Exception: Prelude.head: empty listという例外が発生します。

ghci> head "abcdef"
'a'
ghci> head [1,4,5,2]
1
ghci> head []
`*** Exception: Prelude.head: empty list`

これをそのまま実装すると、以下のコードになります。

head :: [a] -> a
head [] = errorWithoutStackTrace "empty list"
head (x:xs) = x

これをEitherを使って実装してみます。

head' :: [a] -> Either String a
head' [] = Left "empty list"
head' (x:xs) = Right x

正しく動作しているのがわかります。

ghci> head' [1,3,2]
Right 1
ghci> head' "aaaaa"
Right 'a'
ghci> head' []
Left "empty list"

このRight xからxを取り出す方法はData.Eitherで紹介します。

Data.Either

EitherはData.Eitherで定義されています。Data.Eitherには、以下のようなEitherを扱う関数があります。

either :: (a -> c) -> (b -> c) -> Either a b -> c
fromLeft :: a -> Either a b -> a
fromRight :: b -> Either a b -> b
isLeft :: Either a b -> Bool
isRight :: Either a b -> Bool
lefts :: [Either a b] -> [a]
partitionEithers :: [Either a b] -> ([a], [b])
rights :: [Either a b] -> [b]

1つ1つ見ていきましょう。

either

either :: (a -> c) -> (b -> c) -> Either a b -> c

eitherはPreludeに含まれていて、関数を2つとEither a b型の値をとります。引数の値がLeftRightで処理を分けたいときに使えます。

ghci> let x = Right 1234 :: Either String Int
ghci> let y = Left "left" :: Either String Int
ghci> either length (*3) x
3702
ghci> either length (*3) y
4

ちなみに、1行目と2行目の束縛での型注釈は必須ではありません。型注釈をつけなかった場合、xとyの型は以下のようになります。

x :: Num b => Either a b
y :: Either [Char] b

このとき、xのEither aとyのEither bの型をコンパイラは知りませんが、このままeither length (*3)に突っ込んでもエラーは発生せず、上と同じ結果が得られます。なぜなら、eitherは値がLeftかRightかわかった時点でもう片方の関数は評価しないからです。なので、eitherがとる値はLeftかRightであればよいのです。

fromLeft, fromRight

fromLeft :: a -> Either a b -> a
fromRight :: b -> Either a b -> b

fromLeftはLeftから、fromRightはRightから値を取り出します。fromLeftはEitherの値がRightだった時の値を、fromRightはEitherの値がLeftだった時の値をそれぞれとります。ただし、2つの引数の型は同じ(Left Int → Int、Right String → String)である必要があります。

ghci> fromLeft "bananas" (Left "apple")
"apple"
ghci> fromLeft 493 (Right 34141)
493

isLeft, isRight

isLeft :: Either a b -> Bool
isRight :: Either a b -> Bool

それぞれ、引数の型がLeft、RightであればTrue、そうでなければFalseを返します。

ghci> isLeft (Left "aaa")
True
ghci> isRight (Right 381)
True

lefts, rights

lefts :: [Either a b] -> [a]
rights :: [Either a b] -> [b]

Eitherのリストから、それぞれLeft、Rightの値をフィルタします。

ghci> let ls = [Right 3,Left "apple",Right 4, Left "bananas",Right 10]
ghci> lefts ls
["apple","bananas"]
ghci> rights ls
[3,4,10]

partitionEithers

partitionEithers :: [Either a b] -> ([a], [b])

lefts、rightsで取り出した値を、1つのタプルに格納します。

ghci> partitionEithers ls
(["apple","bananas"],[3,4,10])

EitherとFunctor、Applicative

もうお気づきの方もいると思いますが、Eitherは実はFunctorのインスタンスです。1

instance Functor (Either a) -- Defined in ‘Data.Either’

ghc/libraries/base/Data/Either.hsに定義があります。

instance Functor (Either a) where
    fmap _ (Left x) = Left x
    fmap f (Right y) = Right (f y)

fmapにRightの値を与えると関数が適用され、Leftの値を与えるとそのまま返ってくる、ということです。

ghci> fmap (*3) (Left 2)
Left 2982
ghci> fmap (*3) (Right 2)
Right 6
ghci> (*3) <$> Right 4
Right 12
ghci> (*3) <$> Left 4
Left 4

また、EitherはApplicativeのインスタンスでもあるので、アプリカティブ・スタイルでコードを書くこともできます。

instance Applicative (Either e) where
    pure          = Right
    Left  e <*> _ = Left e
    Right f <*> r = fmap f r

pureの引数はRightで返ってきます。<*>の右辺の値がLeftならそれが返ってきて、Rightなら関数が適用されて返ってきます。

ghci> pure 3 :: Either String Int
Right 3
ghci> Right (*3) <*> Left 3
Left 3
ghci> Right (*3) <*> Right 3
Right 9
ghci> Left 3 <*> Right 3
Left 3
ghci> Left 3 <*> Left 3
Left 3

記事の先頭で書いたとおり、EitherはモナドなのでMonadのインスタンスとして定義があります。

instance Monad (Either e) where
    Left  l >>= _ = Left l
    Right r >>= k = k r

>>=にLeftの値を突っ込むとそのまま、Rightの値は関数が適用されて返ってくる、ということです。

Functor、Applicative、MonadそれぞれLeftなら値がスルーされ、Rightなら評価されるという認識でよいかと思います。

参考文献

注釈

  1. Functor、Applicative、Monadについては上の記事が非常にわかりやすいので読んでいただければと思います。

14
4
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
14
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?