0

我有模块:

module Writer where

import Prelude hiding (Monad, (>>=), return, (=<<))
main = putStrLn "hello"

class Monad m where
  return :: a -> m a
  (>>=) :: m a ->  (a -> m b) -> m b
  (=<<) :: (a -> m b) -> m a -> m b
  (=<<) = flip (>>=)

data Writer w a = Writer { runWriter :: (a, w) } deriving Show

instance (Monoid w) => Monad (Writer w) where
  return x = Writer $ (x, mempty)
  m >>= k  = let
             (b, w1) = runWriter m
             Writer (a, w2) = k b
             in Writer (a, (w1 `mappend` w2))

writer :: (a, w) -> Writer w a
writer = Writer

instance (Semigroup a, Num a) => Monoid (Foo a) where
 mempty = Foo 0
 mappend (Foo v1) (Foo v2) = Foo (v1 + v2)

instance Semigroup a => Semigroup (Foo a) where
  (Foo v1) <> (Foo v2) = Foo (v1 <> v2)

instance Semigroup Integer where
  a1 <> a2 = a1 + a2


tell :: Monoid w => w -> Writer w ()
tell w = writer ((), w)

data Foo a = Foo { getFoo :: a } deriving Show

type LoggerFooInt = Writer (Foo Integer) ()

logLine :: String -> Integer -> LoggerFooInt
logLine _ = tell . Foo

batchLog :: Writer (Foo Integer) ()
batchLog = do
  logLine "line1"   19450
  logLine "line2"     760
  logLine "line3"     218

我试图编写batchLog函数,但编译器说:

Writer.hs:46:3: error:
    • No instance for (GHC.Base.Monad (Writer (Foo Integer)))
        arising from a do statement
    • In a stmt of a 'do' block: logLine "line1" 19450
      In the expression:
        do logLine "line1" 19450
           logLine "line2" 760
           logLine "line3" 218
      In an equation for ‘batchLog’:
          batchLog
            = do logLine "line1" 19450
                 logLine "line2" 760
                 logLine "line3" 218
   |
46 |   logLine "line1"   19450
   |   ^^^^^^^^^^^^^^^^^^^^^^^
Failed, no modules loaded.

那么,为什么我需要定义其他任何 Monad。我已经instance (Monoid w) => Monad (Writer w)instance (Semigroup a, Num a) => Monoid (Foo a)instance Semigroup Integer。为什么还不够?没有batchLog功能模块编译。

GHCi,版本 8.6.5:http ://www.haskell.org/ghc/

更新: 我尝试在不使用 do 表示法的情况下进行重写,过了一段时间我可以这样做并且它编译,但仍然无法理解,为什么另一个代码使用我自己的 Monad 和 do 表示法编译:

module MaybeMonad
import Prelude hiding (Monad, (>>=), return, (=<<))
import Control.Monad (ap, liftM)
import Data.Char

main = putStrLn "hello"

class Monad m where
  return :: a -> m a
  (>>=) :: m a ->  (a -> m b) -> m b
  (=<<) :: (a -> m b) -> m a -> m b


instance Monad Maybe where
    return = Just
    (>>=) Nothing _ = Nothing
    (>>=) (Just x) k = k x
    (=<<) = flip (>>=)

data Token = Number Int | Plus | Minus | LeftBrace | RightBrace
    deriving (Eq, Show)

asToken :: String -> Maybe Token
asToken "+" = Just(Plus)
asToken "-" = Just(Minus)
asToken "(" = Just(LeftBrace)
asToken ")" = Just(RightBrace)
asToken str | all isDigit str = Just $ Number $ read str
asToken _ = Nothing

tokenize :: String -> Maybe [Token]
tokenize x = foldr (\word maybeTokens -> do
  token <- asToken word
  tokens <- maybeTokens
  return $ token : tokens) (return []) $ words x

正确的例子:

{-# LANGUAGE RebindableSyntax #-}
module Writer where

import Prelude hiding (Monad, (>>=), return, (=<<), (>>))

class Monad m where
  return :: a -> m a
  (>>=) :: m a ->  (a -> m b) -> m b
  (=<<) :: (a -> m b) -> m a -> m b
  (=<<) = flip (>>=)
  (>>) :: m a -> m b -> m b

data Writer w a = Writer { runWriter :: (a, w) } deriving Show

instance (Monoid w) => Monad (Writer w) where
  return x = Writer $ (x, mempty)
  m >>= k  = let
             (b, w1) = runWriter m
             Writer (a, w2) = k b
             in Writer (a, (w1 `mappend` w2))
  ma >> mb = let
              (_, w1) = runWriter ma
              (vb, w2) = runWriter mb
             in Writer(vb, (w1 `mappend` w2))

writer :: (a, w) -> Writer w a
writer = Writer

instance (Semigroup a, Num a) => Monoid (Foo a) where
 mempty = Foo 0
 mappend (Foo v1) (Foo v2) = Foo (v1 + v2)

instance Semigroup a => Semigroup (Foo a) where
  (Foo v1) <> (Foo v2) = Foo (v1 <> v2)

instance Semigroup Integer where
  a1 <> a2 = a1 + a2


tell :: Monoid w => w -> Writer w ()
tell w = writer ((), w)

data Foo a = Foo { getFoo :: a } deriving Show

logLine :: String -> Integer -> Writer (Foo Integer) ()
logLine _ = tell . Foo

batchLog :: Writer (Foo Integer) ()
batchLog = do
  logLine "line1"   19450
  logLine "line2"     760
  logLine "line3"     218

添加 RebindableSyntax 和隐藏 (>>) 运算符并添加我自己的实现后,它可以编译并正常工作。

4

1 回答 1

2

根据@freestyle 的评论,do-notation 设计始终使用Monadfrom 类Prelude,即使您Monad定义了自己的类。RebindableSyntax可以启用扩展以使 do-notation 使用当前范围内的任何Monad类(或更具体地说,任何>>=>>和函数)。fail

这也会影响许多其他可重新绑定的语法。请参阅上面的链接以获取列表,并确保您没有覆盖您不打算使用的其他语法。

另外,RebindableSyntax暗示NoImplicitPrelude,但这应该没问题,因为您已经有一个import Prelude声明。

更新: 但是,确保您隐藏了所有适用的Prelude语法很重要,否则您可能会发现自己无意中使用了Monadfrom 类Prelude,即使您不想这样做。在您的定义中:

batchLog :: Writer (Foo Integer) ()
batchLog = do
  logLine "line1"   19450
  logLine "line2"     760
  logLine "line3"     218

do-block 被脱糖成:

batchLog = logLine "line1" 19450 >> logLine "line2" 760 >> ...

而且,在哪里(>>)定义?在Prelude,当然。您还需要隐藏它,并在您的自定义Monad类中或作为独立函数提供您自己的定义。在对您的第一个代码块进行以下修改后,do-block 类型检查正确,并且runWriter batchLog看起来工作正常:

import Prelude hiding (..., (>>), ...)

(>>) :: (Monad m) => m a -> m b -> m b
act1 >> act2 = act1 >>= \_ -> act2
于 2019-12-12T01:01:49.010 回答