【问题标题】:Rebind do notation with typeclass-free monad用无类型的 monad 重新绑定 do 表示法
【发布时间】:2016-12-07 16:41:09
【问题描述】:

可以像这样使用显式字典传递重新绑定 (>>=) 并返回一个 monad:

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

有没有更好的方法,如何表示 monad,以便避免在每次使用 monadic 动作时忽略 MonadDict monad 实例参数(使用 const)?

【问题讨论】:

  • 你肯定会想要更像(&gt;&gt;=) :: MonadDict m -&gt; m a -&gt; (a -&gt; m b) -&gt; m b 的东西,或者如果你得到三个不同的字典你打算怎么做?
  • @DanielWagner 我只用过一次看到 usage 部分,我在最后通过字典
  • 通常简单的不变量,如“我只通过一个”,要求类型检查器为您检查是件好事。我提出的类型是要求它这样做的一种方式。
  • @DanielWagner 你如何将多个MonadDict ms 传递给&gt;&gt;=;只暴露了一个,最后一个MonadDict m 参数;只要&gt;&gt;= 将该读者的参数传递给其他两个读者,相同的字典将用于所有3。(&gt;&gt;=) 的类型与ReaderT (MonadDict m) m 的前奏曲(&gt;&gt;=) 相同,但有不同实施。
  • @Cirdec 啊,你是对的:我完全误读了类型签名!

标签: haskell monads typeclass do-notation


【解决方案1】:

你可以这样做:

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

【讨论】:

  • 有趣。事实证明这甚至适用于-XRecordWildCards:如果你分别调用bind 字段&gt;&gt;=&gt;&gt;,你可以只使用monadicCode MonadDict{..} = do ...。这是否是一个好主意我不太确定,但这一切肯定很有趣。
【解决方案2】:

简短且不正确的答案是将MonadDict m 参数从第二个参数的返回类型中删除到(&gt;&gt;=)

(>>=) :: (MonadDict m -> m a) -> (a -> m b) -> (MonadDict m -> m b)

但这并不能真正解决您的所有语法问题。如果某人有一个类型为Monad m =&gt; a -&gt; m b 的现有箭头,并通过显式字典传递,它将具有a -&gt; (MonadDict m -&gt; m b) 类型,并且不能用作(&gt;&gt;=) 的第二个参数。如果有一个函数drop :: (MonadDict m -&gt; m b) -&gt; m b 使其与第二个参数兼容,那么就没有理由传递MonadDicts。


您正在重新发明ReaderT 转换器以读取MonadDict m

newtype ReaderT r m a = ReaderT { runReaderT :: r -> m a }

每次您使用const 时,它都相当于liftm a 转换为ReaderT (MonadDict m) m a。如果您使用lift 而不是const 编写,您的示例看起来不会那么陌生。

usage = let
  monadicCode = do
    ln <- lift getLine 
    lift . putStrLn $ ln
  in monadicCode monadDictIO

这是一个使用ReaderT的完整示例;为ReaderT (MonadDict m) m 创建一个新类型并为lift 创建一个不同的名称可能会更好。 (&gt;&gt;=)return 的实现与 ReaderTs 相同,只是它使用来自 MonadDictbindret

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

如果你给它自己的类型,你可以为它配备一个独立于底层mMonad实例,并省去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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-04-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多