如何使用自定义数据类型定义实例 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
我有模块:
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