如何检测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)
从Bool
到a
函数相当于一对(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
通常Functor
比Monad
更容易检测(或反驳)。 在看到它们也是Monad
之前,一些计算可以通过它们的Applicative
结构轻松检测到。
具体来说,这是你如何证明任何Monad
是Functor
和Applicative
{-# 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。 这些是Writer
, Reader
, State
, Identity
, Maybe
,以及Either
, Cont
和[]
。 通常情况下,你会发现你的类型只是这些标准monad中的一个的小修改,因此可以通过类似于标准的方式将它自己制作成monad。
此外,一些叫做变形金刚的Monad
“叠加”来形成其他的Monad
。 具体而言,这意味着您可以将Reader
monad(修改后的形式)与Writer
monad结合在一起形成ReaderWriter
monad。 这些修改过的表单暴露在transformers
和mtl
包中,通常由附加的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实例。 这是一个非常强大的方法来建立和检测单子。
要了解这些,最好只研究transformers
和mtl
包中的模块。
注意排序
Monads通常是为了提供明确的操作顺序而引入的。 如果你正在写一个需要具体表示一系列动作的类型,你可能手上有一个monad,但你也可能只有一个Monoid
。
请参阅我以前的回答,以深入讨论如何将某个序列写成Monad
...但这样做并不会带来好处。有时,序列只是一个列表。
了解扩展
有时候你会得到一种不明显是monad的数据类型,但显然这取决于monad实例。 一个常见的例子是解析可能显而易见的是,您需要进行一个搜索,以寻找许多替代方案,但是不能立即清楚您可以从中形成monad。
但是,如果您熟悉Applicative
或Monad
您知道有Alternative
和MonadPlus
类
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法则。