(->)r的MonadReader实例的用例是什么

时间:2020-01-27 13:11:02

标签: haskell

我发现MonadReader的{​​{1}}实例很难理解。 irc的某人提到了一个用例,用于扩展其他人的软件包中的某些多态功能。我不记得他到底是什么意思。这是一个与他所说的话有关的例子,但我不明白这一点。任何人都可以针对(->) r

MonadReader用例给出另一个示例
(->) r

2 个答案:

答案 0 :(得分:2)

重点是使合并所有采用相同环境的功能更加容易。

考虑类型a -> Env -> b,其中Env是包含所有“全局”变量的某种数据类型。假设您要组合两个这样的函数。您不能只写h = f2 . f1,因为f1的返回类型Env -> bf2的参数类型b不匹配。

f1 :: a -> Env -> b  -- a -> ((->) Env b)
f2 :: b -> Env -> c  -- b -> ((->) Env c)

h :: a -> Env -> c
h x e = let v = f1 x e
        in f2 v e

由于单子MonadReader有一个适用的(->) Env实例,您可以将其写为

-- The class, ignoring default method implementations, is
-- class Monad m => MonadReader r m | m -> r where
--   ask :: m r
--   local :: (r -> r) -> m a -> m a
--   reader :: (r -> a) -> m a
--
-- The functional dependency means that if you try to use (->) Env
-- as the monad, Env is forced to be the type bound to r.
--
-- instance MonadReader r ((->) r) where 
--    ask = id
--    local f m = m . f
--    reader = id

h :: MonadReader Env m => a -> m c
h x = do
    v <- f1 x
    f2 v
-- h x = f1 x >>= f2

没有明确引用环境,h没有 关心;只有f1f2这样。

更简单地说,您可以使用Kleisli合成运算符定义相同的功能。

import Control.Monad

h :: MonadReader Env m => a -> m c
h = f1 >=> f2

在您的示例中,ask只是从函数主体内部访问环境的方式,而不是将其作为函数的预先存在的参数。没有MonadReader实例,您将编写类似

func :: Show a => Bool -> Int -> a  -- m ~ (->) Int
func b i = case b of
            True -> show i
            False -> error "nope"

main的定义保持不变。但是,(->) Int不是具有MonadReader实例的 only 类型;可能会有更复杂的monad堆栈 您在其他地方使用的类型,更通用的类型(Show a, MonadReader Int m) => Bool -> m a允许您使用它,而不是“只是” (->) Int

答案 1 :(得分:1)

我不确定是否打算将用例与Reader单例分开。

这是一些历史...

transformers库的灵感来自于一组Functional Programming with Overloading and Higher-Order Polymorphism的讲义(Mark P. Jones,1995年)。在这些注释中,讨论了几个命名的单子(StateIdListMaybeErrorWriter)。例如,Writer monad类型及其实例定义为:

data Writer a = Result String a
instance Monad Writer where
    result x            = Result "" x
    Result s x ‘bind‘ f = Result (s ++ s’) y
                          where Result s’ y = f x

还讨论了读者monad,但没有将其定义为单独的类型。而是将Read类型的别名与直接根据部分应用的函数类型Monad定义的(->) r实例一起使用:

type Read r = (r ->)
instance Monad (r->) where
    result x = \r -> x
    x ‘bind‘ f = \r -> f (x r) r

我实际上不知道这些类型级别的“节” (r ->)当时是否是有效的Haskell语法。无论如何,这在现代GHC版本中不是有效的语法,但这就是注释中出现的方式。

安迪·吉尔(Andy Gill)创作的transformers库的第一个版本-或至少是我能够找到的第一个版本,当时它实际上仍是base库的一部分- -在2001年6月被检入Git。它引入了MonadReader类和包装了Reader的新类型:

newtype Reader r a = Reader { runReader :: r -> a }

连同其FunctorMonadMonadFixMonadReader实例。 (没有Applicative-尚未发明。)它还包含 (->) r的一组实例,并带有注释:

部分应用的函数类型是一个简单的读取器monad

因此,我认为讲义中的原始表述使安迪(Andy)包括了(->) r的这些实例,即使他引入了专用的Reader新类型以保持与transformers库中的其他monad。

无论如何,这就是历史。对于用例,我只能想到一个严肃的用例,尽管它可能没有那么引人注目。 lens库旨在与MonadStateMonadReader良好地接口以访问复杂的状态/上下文。因为功能如下:

view :: MonadReader s m => Getting a s a -> m a 
preview :: MonadReader s m => Getting (First a) s a -> m (Maybe a) 
review :: MonadReader b m => AReview t b -> m t

是根据MonadReader实例定义的,它们都可以在传统的Reader上下文中使用:

do ...
   outdir <- view (config.directories.output)
   ...

并在普通函数上下文中:

map (view (links.parent.left)) treeStructure

同样,不一定是引人入胜的用例,但这是一个用例。