如何使用自定义数据类型定义实例 Monad Writer

How define instance Monad Writer with custom data type

我有模块:

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, version 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 和隐藏 (>>) 运算符并添加我自己的实现后,它可以正常编译和工作。

根据@freestyle 的评论,do-notation 设计总是使用 Prelude 中的 Monad class,即使您有自己的 Monad [=48] =] 定义。可以启用 RebindableSyntax 扩展来使 do-notation 使用任何 Monad class(或者更具体地说,任何 >>=>>fail 功能)目前在范围内。

这也影响了一堆其他可重新绑定的语法。请参阅上面的 link 以获取列表,并确保您没有覆盖您不打算覆盖的其他语法。

此外,RebindableSyntax 意味着 NoImplicitPrelude,但这应该没问题,因为您已经有一个 import Prelude 语句。

更新: 然而,重要的是要确保你已经隐藏了 ALL 适用的 Prelude 语法,或者你可能会发现自己无意中使用了 Prelude 中的 Monad class,即使您不想这样做。在您的定义中:

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

do 块被脱糖为:

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

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

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

(>>) :: (Monad m) => m a -> m b -> m b
act1 >> act2 = act1 >>= \_ -> act2