【问题标题】:How define instance Monad Writer with custom data type如何使用自定义数据类型定义实例 Monad Writer
【发布时间】:2019-12-11 21:52:10
【问题描述】:

我有模块:

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) =&gt; Monad (Writer w)instance (Semigroup a, Num a) =&gt; 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 和隐藏 (>>) 运算符并添加我自己的实现之后,它可以编译并正常工作。

【问题讨论】:

  • 我认为这是因为您在 Prelude 中隐藏了标准的 Monad 类,并用您自己的替换了它。 do 表示法是一种特殊的语法糖,我认为它仅适用于“该”Monad 类,而不是另一个模块以相同名称调用的任意类,即使定义恰好重合。跨度>
  • 尝试启用RebindableSyntax
  • @RobinZigmond 也许你是对的,但我有案例,然后我在自己的 monad 类中使用 do 表示法,请参阅更新。

标签: haskell functional-programming monads ghci writer-monad


【解决方案1】:

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

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

另外,RebindableSyntax 暗示 NoImplicitPrelude,但这应该没问题,因为您已经有了 import Prelude 语句。

更新: 但是,重要的是要确保您已隐藏 ALL 适用的 Prelude 语法,否则您可能会发现自己无意中使用了 Monad 类来自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 >> ...

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-06-30
    • 2022-06-15
    • 2021-03-09
    • 1970-01-01
    • 2016-10-28
    • 1970-01-01
    • 2018-01-04
    • 2014-07-06
    相关资源
    最近更新 更多