Reputation: 6431
It is possible to rebind the (>>=) and return for a monad using explicit dictionary passing like this:
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RebindableSyntax #-}
module Lib where
import Prelude hiding ((>>=), return)
data MonadDict m = MonadDict {
bind :: forall a b. m a -> (a -> m b) -> m b ,
ret :: forall a. a -> m a }
(>>=) :: (MonadDict m -> m a) -> (a -> (MonadDict m -> m b)) -> (MonadDict m -> m b)
return :: a -> (MonadDict m -> m a)
monadDictIO :: MonadDict IO
usage = let
monadicCode = do
ln <- const getLine
const . putStrLn $ ln
in monadicCode monadDictIO
Is there a better way, how to represent the monad so one can avoid ignoring the MonadDict
monad instance argument (using const
) in every usage of the monadic action?
Upvotes: 2
Views: 156
Reputation: 3790
You can do like this:
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RebindableSyntax #-}
{-# LANGUAGE RecordWildCards #-}
module Lib where
import Prelude hiding(return, fail, (>>=), (>>))
data MonadDict m = MonadDict
{ (>>=) :: forall a b. m a -> (a -> m b) -> m b
, (>>) :: forall a b. m a -> m b -> m b
, return :: forall a. a -> m a
, fail :: forall a. String -> m a
}
monadDictIO :: MonadDict IO
monadDictIO = ...
foo :: MonadDict m -> String -> m ()
foo = ...
usage = let
monadicCode m@MonadDict{..} = do
ln <- getLine
putStrLn ln
foo m ln
in monadicCode monadDictIO
Upvotes: 6
Reputation: 24156
The short and incorrect answer is drop the MonadDict m
argument from the return type of the second argument to (>>=)
:
(>>=) :: (MonadDict m -> m a) -> (a -> m b) -> (MonadDict m -> m b)
But that doesn't really solve all of your syntax problems. If someone has an existing arrow with the type Monad m => a -> m b
, with explicit dictionary passing it'll have the type a -> (MonadDict m -> m b)
, and won't be usable as the second argument to (>>=)
. If there were a function drop :: (MonadDict m -> m b) -> m b
to make it compatible with the second argument then there'd be no reason to pass MonadDict
s around.
You're reinventing the ReaderT
transformer to read a MonadDict m
.
newtype ReaderT r m a = ReaderT { runReaderT :: r -> m a }
Every time you're using const
it's the equivalent of lift
ing an m a
into ReaderT (MonadDict m) m a
. Your example wouldn't look so unfamiliar if you wrote it with lift
instead of const
.
usage = let
monadicCode = do
ln <- lift getLine
lift . putStrLn $ ln
in monadicCode monadDictIO
Here's a complete example using ReaderT
; it'd probably be better to make a new type for ReaderT (MonadDict m) m
and a different name for lift
. The implementation of (>>=)
and return
is identical to ReaderT
s, except it uses the bind
or ret
from the MonadDict
.
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE RebindableSyntax #-}
module Lib
( usage
) where
import Prelude hiding ((>>=), return)
import qualified Prelude as P ((>>=), return)
import Control.Monad.Trans.Reader
data MonadDict m = MonadDict {
bind :: forall a b. m a -> (a -> m b) -> m b ,
ret :: forall a. a -> m a }
type ReadM m a = ReaderT (MonadDict m) m a
(>>=) :: ReadM m a -> (a -> ReadM m b) -> ReadM m b
m >>= k = ReaderT $ \d@MonadDict { bind = bind } -> bind (runReaderT m d) (\a -> runReaderT (k a) d)
return :: a -> ReadM m a
return a = ReaderT $ \d@MonadDict { ret = ret } -> ret a
lift :: m a -> ReadM m a
lift m = ReaderT $ \_ -> m
monadDict :: Monad m => MonadDict m
monadDict = MonadDict {
bind = (P.>>=),
ret = P.return
}
example1 :: String -> ReadM IO ()
example1 a = do
lift . putStrLn $ a
lift . putStrLn $ a
example2 :: ReadM IO ()
example2 = do
example1 "Hello"
ln <- lift getLine
lift . putStrLn $ ln
usage :: IO ()
usage = runReaderT example2 monadDict
If you give it its own type you can equip it with a Monad
instance independently of the underlying m
, and dispense with RebindableSyntax
.
newtype ReadMD m a = ReadMD {runReadMD :: MonadDict m -> m a}
instance Functor (ReadMD f) where
fmap = liftM
instance Applicative (ReadMD f) where
pure = return
(<*>) = ap
instance Monad (ReadMD m) where
m >>= k = ReadMD $ \d@MonadDict { bind = bind } -> bind (runReadMD m d) (\a -> runReadMD (k a) d)
return a = ReadMD $ \d@MonadDict { ret = ret } -> ret a
Upvotes: 1