web-dev-qa-db-ja.com

HaskellでMaybeの値を取得する方法

私はHaskellに比較的慣れていないため、「Real World Haskell」を読み始めました。

私はたぶんタイプを偶然見つけ、たとえばJust 1から実際の値を受け取る方法について質問があります。

私は次のコードを書きました:

combine a b c = (eliminate a, eliminate b, eliminate c)
                where eliminate (Just a) = a
                      eliminate Nothing = 0

これは私が使用するとうまくいきます:

combine (Just 1) Nothing (Just 2)

しかし、たとえば1を文字列に変更しても機能しません。

理由はわかっていると思います:eliminateは1つの型(この場合はInt)を返す必要があるためです。しかし、少なくとも文字列(または多分あらゆる種類のタイプ)を処理するようにeliminateをどのように変更できますか?

34
Moe

[著者から編集、6年後]これは不必要に長い回答であり、なぜそれが受け入れられたのかわかりません。最高の賛成投票で提案されているようにmaybeまたはData.Maybe.fromMaybeを使用してください答え。以下は、実際的なアドバイスというよりは、思考実験の詳細です。

それで、あなたはたくさんの異なるタイプのために機能する関数を作成しようとしています。これはクラスを作る良い機会です。 JavaまたはC++でプログラミングした場合、Haskellのクラスは、これらの言語のインターフェースのようなものです。

class Nothingish a where
    nada :: a

このクラスは値nadaを定義します。これは、クラスのNothingに相当するものと見なされます。楽しい部分です。このクラスのインスタンスを作成します!

instance Nothingish (Maybe a) where
    nada = Nothing

タイプMaybe aの値の場合、Nothingに似た値はNothing!です。これはすぐに奇妙な例になります。しかしその前に、このクラスのインスタンスもリストにしてみましょう。

instance Nothingish [a] where
    nada = []

空のリストはNothingのようなものですよね?したがって、文字列(Charのリスト)の場合、空の文字列""が返されます。

数値も簡単に実装できます。 0は明らかに数値の「無」を表すことをすでに示しました。

instance (Num a) => Nothingish a where
    nada = 0

ファイルの先頭に特別な行を配置しない限り、これは実際には機能しません

{-# LANGUAGE FlexibleInstances, UndecidableInstances, OverlappingInstances #-}

または、コンパイルするときに、これらの言語プラグマのフラグを設定できます。それらについて心配しないでください。それらは、より多くのものを機能させる魔法にすぎません。

これで、このクラスとそのインスタンスができました...今度は、それらを使用するように関数を書き直しましょう!

eliminate :: (Nothingish a) => Maybe a -> a
eliminate (Just a) = a
eliminate Nothing  = nada

0nadaに変更しただけで、残りは同じです。スピンしてみよう!

ghci> eliminate (Just 2)
2
ghci> eliminate (Just "foo")
"foo"
ghci> eliminate (Just (Just 3))
Just 3
ghci> eliminate (Just Nothing)
Nothing
ghci> :t eliminate
eliminate :: (Nothingish t) => Maybe t -> t
ghci> eliminate Nothing
error! blah blah blah...**Ambiguous type variable**

価値観などに最適です。 (Just Nothing)がNothingに変わることに注意してください。それは奇妙な例でした。とにかくeliminate Nothingはどうですか?まあ、結果の型があいまいです。私たちが何を期待しているのかわかりません。だから私たちはそれが私たちが望むタイプを伝える必要があります。

ghci> eliminate Nothing :: Int
0

他のタイプで試してみてください。それぞれがnadaを取得することがわかります。したがって、この関数をcombine関数で使用すると、次のようになります。

ghci> let combine a b c = (eliminate a, eliminate b, eliminate c)
ghci> combine (Just 2) (Just "foo") (Just (Just 3))
(2,"foo",Just 3)
ghci> combine (Just 2) Nothing (Just 4)
error! blah blah Ambiguous Type blah blah

「Nothing」のタイプを示すか、期待する戻りのタイプを示す必要があることに注意してください。

ghci> combine (Just 2) (Nothing :: Maybe Int) (Just 4)
(2,0,4)
ghci> combine (Just 2) Nothing (Just 4) :: (Int, Int, Int)
(2,0,4)

または、型シグネチャをソースに明示的に置くことにより、関数が許可する型を制限することもできます。これは、関数の論理的な使用が、同じタイプのパラメーターでのみ使用される場合に意味があります。

combine :: (Nothingish a) => Maybe a -> Maybe a -> Maybe a -> (a,a,a)
combine a b c = (eliminate a, eliminate b, eliminate c)

今では、たぶん3つすべてが同じタイプである場合にのみ機能します。そうすれば、Nothingが他の型と同じ型であると推測されます。

ghci> combine (Just 2) Nothing (Just 4)
(2,0,4)

あいまいさはありません。しかし、今は前と同じように、組み合わせるとエラーになります。

ghci> combine (Just 2) (Just "foo") (Just (Just 3))
error! blah blah  Couldn't match expected type  blah blah
blah blah blah    against inferred type         blah blah

ええと、それは十分に長く、誇張された答えでした。楽しい。

23
Dan Burton

標準から Prelude

maybe :: b -> (a -> b) -> Maybe a -> b
maybe n _ Nothing = n
maybe _ f (Just x) = f x

デフォルト値と関数を指定して、Maybeの値に関数を適用するか、デフォルト値を返します。

あなたのeliminatemaybe 0 id、例:識別関数を適用するか、0を返します。

標準から Data.Maybe

fromJust :: Maybe a -> a
fromJust Nothing = error "Maybe.fromJust: Nothing"
fromJust (Just x) = x

これはpartial関数です(total関数ではなく、すべての入力に対して値を返します)。ただし、可能な場合は値を抽出します。

44
ephemient

私もHaskellを初めて使用しているので、これがまだプラットフォームに存在するかどうかはわかりません(確かに存在します)が、存在する場合に値を取得する「取得またはその他」関数についてはどうですか?デフォルト?

getOrElse::Maybe a -> a -> a
getOrElse (Just v) d = v
getOrElse Nothing d  = d
3
Roberto

これは私がこの質問に来たときに私が探していた答えです:

https://hackage.haskell.org/package/base-4.9.0.0/docs/Data-Maybe.html#v:fromJust

...そして同様に、Eitherの場合:

https://hackage.haskell.org/package/one-unwrap-1.1/docs/Data-Either-Unwrap.html

それらは、コンテキストから値をアンラップする、私が自分で書いたであろう機能を提供します。

2
Ben

eliminate関数の型シグネチャは次のとおりです。

eliminate :: Maybe Int -> Int

これは、Nothingに対して0を返し、コンパイラにa :: Inteliminate関数内。したがって、コンパイラはcombine関数の型シグネチャを次のように推定します。

combine :: Maybe Int -> Maybe Int -> Maybe Int -> (Int, Int, Int)

文字列を渡しても機能しないのはそのためです。

次のように書いた場合:

combine a b c = (eliminate a, eliminate b, eliminate c)
                where eliminate (Just a) = a
                      eliminate Nothing = undefined

次に、それは文字列または他のタイプで機能します。その理由は、undefined :: aは、eliminateをポリモーフィックにし、Int以外の型に適用できるようにします。

もちろん、それはコードの目的ではありません。つまり、結合関数を合計することです。

実際、いくつかのNothing引数へのcombineの適用が成功しても(これはHaskellがデフォルトで遅延しているためです)、結果を評価しようとするとすぐに、undefinedは有用なものとして評価できないため、ランタイムエラーが発生します(簡単に言えば)。

1
Cristiano Paris