如何检测Monad?

我们中的许多人没有功能编程的背景,更不用说类别理论代数。 所以我们假设我们需要并因此创建一个通用类型

data MySomething t = ....... 

然后我们继续编程,并使用MySomething 。 有什么证据应该提醒我们MySomething是一个monad,我们必须通过编写instance Monad MySomething ...来实现它,并且定义return>>=它是什么?

谢谢。

编辑:另请参阅此问题:链接操作是monad类解决的唯一事情吗?,而这个答案monad是一个带有辅助操作的函数数组


在我了解单子之前,我迈出的一大步是Monads是嫁接树。 如果你的类型看起来像一棵树,并且t值出现在树叶上,那么你的手上可能会有一个monad。

简单的例子

一些数据类型显然是树,例如Maybe类型

data Maybe a = Nothing | Just a

要么有一个空叶子,要么是一个具有单个值的叶子。 该列表是另一种明显的树型

data List a = Nil | Cons a (List a)

这是一个空叶,或者是一个具有单个值和另一个列表的叶。 更明显的树是二叉树

data Tree a = Leaf a | Bin (Tree a) (Tree a)

价值在叶子上。

更难的例子

但是,有些类型乍一看并不像树木。 例如,'reader'monad(又名函数monad或环境monad)看起来像

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

目前看起来不像一棵树。 但让我们专注于一个具体的类型r ,例如Bool -

data ReaderBool a = ReaderBool (Bool -> a)

Boola函数相当于一对(a,a) ,其中左边的元素是True上的函数的值,右边的参数是False上的值 -

data ReaderBool a = ReaderBool a a

看起来更像一棵只有一种叶子的树 - 事实上,你可以把它变成一个单子

instance Monad ReaderBool where
    return a = ReaderBool a a
    ReaderBool a b >>= f = ReaderBool a' b'
      where
        ReaderBool a' _ = f a
        ReaderBool _ b' = f b

道德是一个函数r -> a可以被看作是一个包含许多a类型值的大长元组,每个可能的输入都有一个 - 并且该元组可以被视为一棵特别简单的树的叶子。

国家monad是这种类型的另一个例子

data State s a = State { runState :: s -> (a, s) }

您可以在其中查看s -> (a, s)作为类型(a, s)值的大元组 - 一个用于s类型s可能输入。

再举一个例子 - 一个简化的IO动作monad

data Action a = Put String (Action a)
              | Get (String -> Action a)
              | Return a

这是一种有三种类型的叶子的树 - Put叶子,它携带另一个动作, Get叶子,它可以被看作是一个无限的动作元组(一个用于每个可能的String输入)和一个简单的Return叶子类型a的单个值。 所以看起来它可能是一个monad,实际上它是

instance Monad Action where
  return = Return

  Put s a  >>= f = Put s (a >>= f)
  Get g    >>= f = Get (s -> g s >>= f)
  Return a >>= f = f a

希望这给了你一点直觉。

将monad看作树, return操作是一种获得具有一个值的简单树的方式,并且用>>=操作作为用新树替换树叶上元素的方式,可以成为一种强大的统一方法看看monads。


值得一提的是,没有一种直接的方式可以注意到Monad是一种Monad,相反,当您怀疑Monad可能会证明您的怀疑是正确的时候,这是一个过程。

也就是说,有办法提高你对Monad的敏感度。

知道依赖关系

对于任何类型T ,守法instance Monad T意味着有一个守法instance Applicative T和一个守法instance Functor T 通常FunctorMonad更容易检测(或反驳)。 在看到它们也是Monad之前,一些计算可以通过它们的Applicative结构轻松检测到。

具体来说,这是你如何证明任何MonadFunctorApplicative

{-# LANGUAGE GeneralizedNewtypeDeriving #-}

newtype Wrapped m a = W { unW :: m a } -- newtype lets us write new instances
  deriving ( Monad )

instance Monad m => Functor (Wrapped m) where
  fmap f (W ma) = W (ma >>= return . f)

instance Monad m => Applicative (Wrapped m) where
  pure = W . return
  W mf <*> W mx = W $ do
    f <- mf
    x <- mx
    return (f x)

通常,理解这种类型层次结构的最佳资源是Typeclassopedia。 我不能推荐阅读它。

了解你的标准单子和变形金刚

任何中间Haskell程序员都应该立即熟悉一组非常简单的monads。 这些是WriterReaderStateIdentityMaybe ,以及EitherCont[] 。 通常情况下,你会发现你的类型只是这些标准monad中的一个的小修改,因此可以通过类似于标准的方式将它自己制作成monad。

此外,一些叫做变形金刚的Monad “叠加”来形成其他的Monad 。 具体而言,这意味着您可以将Reader monad(修改后的形式)与Writer monad结合在一起形成ReaderWriter monad。 这些修改过的表单暴露在transformersmtl包中,通常由附加的T mtl 。 具体而言,您可以使用像这样的transformers标准变压器来定义ReaderWriter

import Control.Monad.Trans.Reader
import Control.Monad.Writer

newtype ReaderWriter r w a = RW { unRW :: ReaderT r (Writer w) a }
  deriving Monad

-- Control.Monad.Trans.Reader defines ReaderT as follows
--
--     newtype ReaderT r m a = ReaderT { runReaderT :: r -> m a }
--
-- the `m` is the "next" monad on the transformer stack

一旦你学习了变形器,你会发现更多的标准类型只是基本monads的堆栈,因此从变形器的monad实例继承它们的monad实例。 这是一个非常强大的方法来建立和检测单子。

要了解这些,最好只研究transformersmtl包中的模块。

注意排序

Monads通常是为了提供明确的操作顺序而引入的。 如果你正在写一个需要具体表示一系列动作的类型,你可能手上有一个monad,但你也可能只有一个Monoid

请参阅我以前的回答,以深入讨论如何将某个序列写成Monad ...但这样做并不会带来好处。有时,序列只是一个列表。

了解扩展

有时候你会得到一种不明显是monad的数据类型,但显然这取决于monad实例。 一个常见的例子是解析可能显而易见的是,您需要进行一个搜索,以寻找许多替代方案,但是不能立即清楚您可以从中形成monad。

但是,如果您熟悉ApplicativeMonad您知道有AlternativeMonadPlus

instance Monad m => MonadPlus m where ...
instance Applicative f => Alternative f where ...

这对于采取替代方案的结构计算是有用的。 这表明也许有办法在你的类型中找到monad结构!

了解自由结构

有一个关于函数的自由monad的概念。 这个术语是非常类别论的,但它实际上是一个非常有用的概念,因为任何monad可以被认为是解释一个相关的免费monad。 而且,自由单体是相对简单的结构,因此更容易为他们获得直觉。 请注意,这些东西是相当抽象的,尽管如此,它可能需要一些努力来消化。

免费的monad定义如下

data Free f a = Pure a
              | Fix (f (Fix f a))

这仅仅是我们的仿函数的定点f邻接到一个Pure价值。 如果你研究类型固定点(参见recursion-schemes包或者Bartosz Milewski的了解F-代数),你会发现Fix位只是定义了任何递归data类型,而Pure位允许我们在这个常规中注入“洞”键入其通过填充a秒。

(>>=)Free Monad只是把其中的一个a S和填补其与新的孔Free fa

(>>=) :: Free f a -> (a -> Free f a) -> Free f a
Pure a >>= g = g a
Fix fx >>= g = Fix (fmap (>>= g) fx) -- push the bind down the type

这个概念与克里斯泰勒的答案非常相似--- Monads只是树状类型,其中(>>=)移植了叶子以前的新树状部分。 或者,正如我在上面所描述的那样,Monads只是普通类型,后面可以填充Pure孔。

免费monads在抽象方面有更多的深度,所以我建议Gabriel Gonzalez的免费monads文章中介绍了如何使用免费monads对复杂计算进行建模。

知道规范分解

我将要提出的最后一个技巧将免费monad的概念和序列的概念结合起来,并且是新的通用monad软件包(如extensible-effects

思考单子的一种方法是按顺序执行的一组指令。 例如, State monad可能是指示

Get :: State s s
Put :: s -> State s ()

我们可以用一种稍微不直观的方式将其具体表示为一个函子

data StateF s x = Get (s -> x) | Put s x deriving Functor

我们引入x参数的原因是因为我们要通过形成StateF的定点来对StateF操作进行StateF 。 直观地说,这就好像我们用StateF自己替换了x ,这样我们就可以写出类似的类型

modify f = Get (s -> Put (f s) (...))

(...)是序列中的下一个动作。 我们不是永远持续下去,而是从上面的free monad中使用Pure构造函数。 为此,我们还必须使用Fix标记非Pure

-- real Haskell now
modify f = Fix (Get $ s -> Fix (Put (f s) (Pure ()))

这种思维模式进一步发展,我会再次引导你加百列的文章。

但是你现在可以拿走的是有时候你有一个类型来表示一系列的事件。 这可以被解释为表示Monad的某种规范方式,并且您可以使用free从您的规范表示形式构建Monad。 我经常使用这种方法在我的应用程序中构建“语义”monad,比如“数据库访问monad”或“logging”monad。


根据我的经验,找出并同时为monad创建直觉的最简单方法就是尝试为您的类型实现return(>>=) ,并验证它们是否满足monad法则。

链接地址: http://www.djcxy.com/p/47671.html

上一篇: How to detect a Monad?

下一篇: Monad with no wrapped value?