haskellfunctional-programmingmonadsghciwriter-monad

How define instance Monad Writer with custom data type


I have module:

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

I tried to write batchLog function but compiler said:

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.

So, why I need to define any Monads else. I have already instance (Monoid w) => Monad (Writer w) and instance (Semigroup a, Num a) => Monoid (Foo a) and instance Semigroup Integer. Why it's not enough? Without batchLog function module compiles.

GHCi, version 8.6.5: http://www.haskell.org/ghc/

UPDATE: I tried to rewrite without do notation, and after a while I can do that and it compiles, but still can't understand, why another code, compiles with my own Monad and with do notation:

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

Correct example:

{-# 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

after adding RebindableSyntax and hiding (>>) operator and adding my own realization it compiles and works properly.


Solution

  • As per @freestyle's comment, do-notation by design always uses the Monad class from Prelude, even if you have your own Monad class defined. The RebindableSyntax extension can be enabled to make do-notation use whatever Monad class (or more specifically, whatever >>=, >>, and fail functions) are currently in scope.

    This also affects a bunch of other rebindable syntax. See the link above for a list, and make sure you aren't overriding additional syntax you don't intend to.

    Also, RebindableSyntax implies NoImplicitPrelude, but that should be fine since you already have an import Prelude statement.

    UPDATE: However, it's important to make sure you've hidden ALL the applicable Prelude syntax, or you may find yourself unintentionally using the Monad class from Prelude, even if you didn't want to. In your definition of:

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

    the do-block is desugared into:

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

    And, where is (>>) defined? In Prelude, of course. You will need to hide that as well, and provide your own definition, either in your custom Monad class or as a standalone function. After making the following modifications to your first code block, the do-block type checks correctly, and runWriter batchLog appears to work fine:

    import Prelude hiding (..., (>>), ...)
    
    (>>) :: (Monad m) => m a -> m b -> m b
    act1 >> act2 = act1 >>= \_ -> act2