6

私は現在、Haskell を学ぼうとしているところですが、Maybeモナドに関する奇妙な問題に遭遇しました。

実験として、私は現在、文字列を取得し、各文字を任意の数値に変換し、それらを乗算/結合しようとしています。これが私がこれまでに持っているものです:

lookupTable :: [(Char, Int)]
lookupTable = [('A', 1), ('B', 4), ('C', -6)]

strToInts :: String -> [Maybe Int]
strToInts = map lookupChar
    where 
        lookupChar :: Char -> Maybe Int
        lookupChar c = lookup c lookupTable

-- Currently fails
test :: (Num n, Ord n) => [Maybe n] -> [Maybe n]
test seq = [ x * y | (x, y) <- zip seq $ tail seq, x < y ]

main :: IO ()
main = do
    putStrLn $ show $ test $ strToInts "ABC"

これを実行しようとすると、次のエラーが返されます。

test.hs:13:16:
    Could not deduce (Num (Maybe n)) arising from a use of `*'
    from the context (Num n, Ord n)
      bound by the type signature for
                 test :: (Num n, Ord n) => [Maybe n] -> [Maybe n]
      at test.hs:12:9-48
    Possible fix: add an instance declaration for (Num (Maybe n))
    In the expression: x * y
    In the expression: [x * y | (x, y) <- zip seq $ tail seq]
    In an equation for `test':
        test seq = [x * y | (x, y) <- zip seq $ tail seq]

なぜこのエラーが発生するのか、またはそれが正確に何を意味するのかは 100% わかりませんが、2 つのMaybeモナドを乗算しようとしている可能性があると思われtestます。そしてうまく動作します:

test :: (Num n, Ord n) => [Maybe n] -> [Maybe n]
test seq = [ x | (x, y) <- zip seq $ tail seq, x < y ]

また、型宣言を以下に変更してみましたが、それもうまくいきませんでした。

test :: (Num n, Ord n) => [Maybe n] -> [Num (Maybe n)]

このエラーを修正する方法がよくわかりません。私はHaskellにかなり慣れていないので、本当に単純なものが欠けているか、すべてが完全に間違って構成されている可能性がありますが、これは私を困惑させています. 私は何を間違っていますか?

4

3 に答える 3

13

おそらく num インスタンスがないため、それらを直接乗算することはできません。どういうわけか、コンテキスト内の値に純粋な関数を適用する必要があります。これこそが、Applicative functor の目的です!

アプリカティブ ファンクターは Control.Applicative に存在します。

import Control.Applicative

したがって、この関数があり、コンテキスト内の 2 つの引数に適用したいとします。

(*) :: Num a => a -> a -> a

おそらく fmap について学習したことでしょう。関数を取り、それをコンテキスト内の値に適用します。<$>fmap のエイリアスです。純粋な関数を Maybe 値に fmap すると、次の結果が得られます。

(*) <$> Just 5 :: Num a => Maybe (a -> a)

これで多分関数ができて、それを多分値に適用する必要があります。これはまさにアプリカティブファンクターが行うことです。その主な演算子は<*>、署名を持つものです。

(<*>) :: f (a -> b) -> f a -> f b

それを特殊化すると、必要な機能が得られます。

(<*>) :: Maybe (a -> b) -> Maybe a -> Maybe b

それを適用すると、期待どおりの数値が出力されます。

(*) <$> Just 5 <*> Just 5 :: Num a => Maybe a

したがって、コードをコンパイルするには、テスト関数を変更して<$>andを使用する必要があり<*>ます。方法を理解できるかどうかを確認してください。

于 2014-03-08T11:11:11.057 に答える
6

Reite の答えは正しいです。一般的には、それを処理することをお勧めしますが、Maybe値の操作方法をよく理解していないようです。もしそうなら、今すぐアプリカティブファンクターを見る意味はほとんどありません。

の定義Maybeは基本的に

 data Maybe a = Nothing | Just a

これは基本的に、「型の値は、その型の値、またはフォームの値のMaybe aいずれかです」という平易な英語で読むと、まさにそのように聞こえることを意味します。NothingJust a

これで、パターン マッチングを使用して、リストの例を使用できます。

 maybeReverse :: Maybe [a] -> Maybe [a]
 maybeReverse Nothing = Nothing
 maybeReverse (Just xs) = Just $ reverse xs

これは基本的に「値が の場合、Nothing元に戻すものは何もないため、結果はNothing再び返されます。値が の場合は、再度ラップして値に変換できJust xsます。 」reverse xsJustMaybe [a]

もちろん、Maybe値で使用したいすべての関数に対してこのような関数を書くのは面倒です。したがって、高次関数が救助に役立ちます! ここでの観察は、ではmaybeReverseそれほど多くのことを行っておらずreverse、含まれている値にそれを適用し、その結果を でラップしただけであるということですJust

したがって、これを行う関数という名前を書くことができますliftToMaybe

 liftToMaybe :: (a->b) -> Maybe a -> Maybe b
 liftToMaybe f Nothing = Nothing
 liftToMaybe f (Just a) = Just $ f a

さらに、関数は値であるため、関数の値を持つこともできMaybeます。それらで何か役に立つことを行うには、それらを再びアンラップすることができます...または、最後の段落と同じ状況にあることに気づき、そのMaybe値に正確にどの関数が含まれているかはあまり気にしないことにすぐに気付きます。抽象化を直接:

 maybeApply :: Maybe (a->b) -> Maybe a -> Maybe b
 maybeApply Nothing _ = Nothing
 maybeApply _ Nothing = Nothing
 maybeApply (Just f) (Just a) = Just $ f a

上記の関数を使用してliftToMaybe、少し単純化できます。

 maybeApply :: Maybe (a->b) -> Maybe a -> Maybe b
 maybeApply Nothing _ = Nothing
 maybeApply (Just f) x = liftToMaybe f x

Reite の回答の<$>and演算子は、基本的に( としても知られている) およびそれぞれの中置名です。彼らはタイプを持っています<*>liftToMaybefmapmaybeApply

(<$>) :: Functor f => (a->b) -> f a -> f b
(<*>) :: Applicative f => f (a->b) -> f a -> f b

Functor現在、 andが何であるかを実際に知る必要はありませんApplicative(ただし、ある時点でそれらを調べる必要があります。基本的には、Maybe他の種類の「コンテキスト」に対する上記の関数の一般化です) - 基本的に、 and に置き換えるfだけMaybeですこれらは基本的に、以前に説明したのと同じ関数であることがわかります。

さて、これを元の掛け算の問題に適用したままにします(ただし、他の答えはそれを台無しにします)。

于 2014-03-08T11:33:15.110 に答える
3

正解です。問題は、2 つのMaybe値を掛け合わせようとしていることですが(*)、 のインスタンスでしか機能しませんNum

結局のところ、型クラスMaybeのインスタンスですApplicativeaこれは、 typeで機能する関数をtype で機能する関数に「持ち上げる」ことができることを意味しますMaybe a

import Control.Applicative

によって提供される 2 つの機能Applicativeは次のとおりです。

pure :: a -> f a「ニュートラルなコンテキスト」に純粋な値を置きます。の場合Maybe、これはJustです。

(<*>) :: f (a -> b) -> f a -> f b「コンテキスト内の関数」を 2 つの「コンテキスト内の値」に適用できます。

したがって、次の純粋な計算があるとします。

(*) 2 3

Maybeコンテキストでの類似の計算を次に示します。

Just (*) <*> Just 2 <*> Just 3
-- result is Just 6

pure (*) <*> pure 2 <*> pure 3
-- result is Just 6 -- equivalent to the above

pure (*) <*> pure 2 <*> Nothing
-- Nothing

Nothing <*> pure 2 <*> Just 3
-- Nothing

Nothing <*> Nothing <*> Nothing
-- Nothing

関数または引数の 1 つが「欠落」している場合は、 を返しNothingます。

(<*>)applicative を操作するときの明示的な適用演算子です (純粋な値を操作するときのように空白を使用する代わりに)。

次のような(<*>)の他のインスタンスで何が行われるかを調べることは有益です。Applicative[]

ghci> [succ,pred] <*> pure 3
[4,2]
ghci> [succ,pred] <*> [3]
[4,2]
ghci> pure succ <*> [2,5]
[3,6]
ghci> [succ] <*> [2,5]
[3,6]
ghci> [(+),(*)] <*> pure 2 <*> pure 3
[5,6]
ghci> [(+),(*)] <*> [2,1] <*> pure 3
[5,4,6,3]
ghci> [(+),(*)] <*> [2,1] <*> [3,7]
[5,9,4,8,6,14,3,7]
于 2014-03-08T11:14:34.750 に答える