0

Monads を s として扱うことができれば、よりきれいに記述できるコードが少しありますNum(もちろん、該当する場合)。簡単に完了:

{-# LANGUAGE FlexibleInstances #-}

import Control.Monad (liftM, liftM2)
import Data.Char (digitToInt)

instance (Monad m, Num a) => Num (m a) where
  (+) = liftM2 (+)
  (-) = liftM2 (-)
  (*) = liftM2 (*)
  abs = liftM abs
  signum = liftM signum
  fromInteger = return . fromInteger

square :: (Monad m, Num a) => m a -> m a
square x = x * x

-- Prints "Just 9", as expected
main = putStrLn $ show $ square $ Just 3

しかし、次の関数をファイルに追加すると…

digitToNum :: (Num a) => Char -> a
digitToNum = fromIntegral . digitToInt

… 次のエラーが表示されます。

monadNumTest.hs:15:14:
    Overlapping instances for Num (m a)
      arising from a use of `*'
    Matching instances:
      instance (Monad m, Num a) => Num (m a)
        -- Defined at monadNumTest.hs:6:10
      instance Integral a => Num (GHC.Real.Ratio a)
        -- Defined in `GHC.Real'
    (The choice depends on the instantiation of `m, a'
     To pick the first instance above, use -XIncoherentInstances
     when compiling the other instance declarations)
    In the expression: x * x
    In an equation for `square': square x = x * x

(1)digitToNumは決して呼び出されず、(2)Ratioではないため、これは私には意味がありませんMonad。したがって、それがどのように、またはなぜ問題なのかはわかりません。これに関するヒントをいただければ幸いです。

これは、Haskell Platform 2012.4.0.0 を使用する GHC 7.4.2 です。

4

2 に答える 2

8

ここで重要な問題は、追加のインスタンスを記述しても既存のコードの動作を変更してはならないというhaskellの原則です。これにより、haskellコードの堅牢性が向上します。これは、依存するモジュールが新しいインスタンスを追加した場合でも、コードが壊れたり、異なる動作をしたりしないためです。

このため、タイプに使用できるインスタンスを選択するとき、Haskellはインスタンスのコンテキストを考慮しません。たとえば、型がinstance (Monad m, Num a) => Num (m a)クラスのインスタンスと一致するかどうかの一致チェックでは、一致Numできるかどうかのみがチェックされますm a。これは、任意の型が後でクラスのインスタンスになる可能性があり、インスタンスの選択でコンテキスト情報を使用した場合、そのインスタンスを追加すると既存のプログラムの動作が変わるためです。

たとえば、次のコードがモジュールまたは依存するモジュールに追加されるのを止めるものは何もありません。

instance Monad Ratio where
   return = undefined
   (>>=) = undefined

確かに、そのようなインスタンスは役に立たないが、haskellにはそれを判断する方法がない。Monadforの有用な定義がある可能性もありますRatio(私はそれを調べていません)。

要約すると、あなたがやろうとしていることは良い考えではありません。OverlappingInstancesとを使用してこれらの制限を停止できますがIncoherentInstances、上記の理由により、これらのフラグはほとんどのhaskellプログラマーによる主流の使用には推奨されていません。

于 2012-12-16T06:02:13.220 に答える
2

@nanothief が説明するように、Haskell の型クラスは「オープン ワールドの仮定」で設計されています。この問題を回避する標準的な方法newtypeは、インスタンス ヘッドの汎用性を低くするラッパーを使用することです。例えば

newtype WrappedMonad m a = WrapMonad { unwrapMonad :: m a }

instance Monad m => Monad (WrappedMonad m) where
   return = WrapMonad . return
   (WrapMonad a) >>= f = WrapMonad (a >>= unwrapMonad . f)

instance (Monad m, Num a) => Num (WrappedMonad m a)
   (+) = liftM2 (+)
   fromInteger = return . fromInteger
   -- etc.

これで、最初にすべての入力をラップし、最後にラップを解除するだけで使用できるようになります。

unwrapMonad . sum . map WrapMonad $ [[1, 2], [10,20], [100,200]]
-- [111,211,121,221,112,212,122,222]

(明らかに、より長い算術チェーンには大きな利点があります。)

の任意の機能の正確な代役になるようにするには、派生などを行う価値があるかもしれEqませShowん。WrappedMonadm a

于 2012-12-16T10:16:37.033 に答える