我们中的许多人没有函数式编程的背景知识,更不用说类别理论代数了。因此,我们假设我们需要并因此创建类似
的泛型类型data MySomething t = .......
然后我们继续编程,并使用MySomething
。哪些证据可以提醒我们MySomething
是一个monad,我们必须通过编写instance Monad MySomething ...
并为其定义return
和>>=
来使其成为一个?
感谢。
修改:另请参阅此问题:is chaining operations the only thing that the monad class solves?,此答案a monad is an array of functions with helper operations
答案 0 :(得分:17)
我对monad的理解向我迈出了一大步,就是帖子Monads are Trees with Grafting。如果您的类型看起来像一棵树,并且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(aka function monad或environment 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
看起来更像是一棵只有一种叶子的树 - 事实上,你可以把它变成一个monad
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
类型。
又一个例子 - 简化的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
操作视为获取具有一个值的简单树的方法,并将>>=
操作视为用树替换树叶处的元素的方式树木,可以成为观察单子的强大统一方式。
答案 1 :(得分:7)
值得一提的是,没有直接的方式来注意某些东西是Monad - 而是当你怀疑某些东西可能是Monad来证明你的怀疑是正确的。
也就是说,有一些方法可以提高你对Monads的敏感度。
对于任何类型的T
,遵纪守法instance Monad T
意味着遵守法律instance Applicative T
和守法instance Functor T
。 Functor
通常比Monad
更容易检测(或反驳)。有些计算可以通过Applicative
结构轻松检测到,然后才能看到它们也是Monad
。
具体而言,您可以通过以下方式证明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。我不建议你阅读它。
有一套非常标准的简单monad,任何中级Haskell程序员都应该立即熟悉它们。这些是Writer
,Reader
,State
,Identity
,Maybe
和Either
,Cont
和{{1} }。通常,您会发现您的类型只是对这些标准monad之一的一个小修改,因此可以以类似于标准的方式制作monad本身。
此外,一些[]
,称为变形金刚,&#34; stack&#34;形成其他Monad
。这具体意味着你可以组合Monad
monad和Reader
monad的一个(修改后的形式)组成Writer
monad。这些经过修改的表单在ReaderWriter
和transformers
包中公开,通常由附加的mtl
划分。具体而言,您可以使用T
中的标准变换器来定义ReaderWriter
,如此
transformers
一旦你学习了变形金刚,你就会发现更多的标准类型只是基本monad的堆栈,因此从变换器的monad实例继承了它们的monad实例。这是用于构建和检测monad的非常幂方法。
要了解这些内容,最好只研究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
和transformers
包中的模块。
通常会引入Monad以提供明确的操作顺序。如果你正在编写一个需要具体表示一系列动作的类型,你可能手上有一个monad - 但你也可能只有一个mtl
。
有时候你的数据类型显然不是monad,但显然取决于monad实例。一个常见的例子就是解析你可能需要进行多次搜索的搜索,但是不能立即明确你可以从中形成一个monad。
但如果您熟悉Monoid
或Applicative
,您就知道有Monad
和Alternative
类
MonadPlus
对于采用替代方案的结构计算很有用。这表明可能有找到你的类型monad结构的方法!
在仿函数上有 free monad的概念。这个术语非常类别理论,但它实际上是一个非常有用的概念,因为任何monad都可以被认为是解释相关的自由monad。此外,免费monad是相对简单的结构,因此更容易获得直觉。请注意,这些内容相当抽象,但需要花费一些精力才能消化。
免费monad定义如下
instance Monad m => MonadPlus m where ...
instance Applicative f => Alternative f where ...
这只是我们的仿函数data Free f a = Pure a
| Fix (f (Fix f a))
与f
值相邻的固定点。如果您研究类型修复点(请参阅recursion-schemes
包或Bartosz Milewski的Understanding F-algebras了解更多信息),您会发现Pure
位只定义了任何递归{{1}类型和Fix
位允许我们注入&#34;空洞&#34;进入由data
s填充的常规类型。
Pure
a
的{{1}}只是为了获取其中一个(>>=)
并填充新的Free
。
Monad
这个概念与Chris Taylor的答案非常相似--- Monads只是树状的类型,其中a
移植了新的树状部分。或者,正如我在上面所描述的那样,Monads只是具有Free f a
个洞的常规类型,可以在以后填充。
免费的monad在抽象方面有更多的深度,所以我推荐Gabriel Gonzalez的Purify your code with free monads文章,向你展示如何使用免费monad模拟复杂的计算。
我将建议的最后一个技巧结合了自由monad的概念和排序的概念,并且是extensible-effects
等新的通用monad包的基础。
想到monad的一种方法是按顺序执行一组指令。例如,(>>=) :: 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
monad可能是指令
(>>=)
我们可以用稍微不直观的方式具体表示为Functor
Pure
我们引入State
参数的原因是因为我们通过形成Get :: State s s
Put :: s -> State s ()
的定点来对序列data StateF s x = Get (s -> x) | Put s x deriving Functor
进行排序。直观地说,就好像我们将x
替换为StateF
本身,以便我们可以编写类似
StateF
其中x
是序列中的下一个操作。我们使用上面StateF
monad中的modify f = Get (\s -> Put (f s) (...))
构造函数,而不是永远地继续这样做。为此,我们还必须使用(...)
Pure
位
free
这种思维方式还有很长的路要走,我再次引导你去Gabriel's article。
但是你现在可以带走的是,有时候你有一种表明一系列事件的类型。这可以解释为表示Pure
的某种规范方式,您可以使用Fix
从规范表示中构建有问题的Monad。我经常使用这种方法来构建&#34;语义&#34;我的应用程序中的monad,例如&#34;数据库访问monad&#34;或者&#34;记录&#34;单子。
答案 2 :(得分:5)
根据我的经验,找出并同时为monad建立直觉的最简单方法是尝试为您的类型实施return
和(>>=)
,并验证它们是否符合monad法则。< / p>
答案 3 :(得分:3)
如果您最终编写任何具有任何类似签名的操作,您应该留意,或者同样重要的是,如果您有许多可以重构的函数来使用它们:
----- Functor -----
-- Apply a one-place function "inside" `MySomething`.
fmap :: (a -> b) -> MySomething a -> MySomething b
----- Applicative -----
-- Apply an n-place function to the appropriate number and types of
-- `MySomething`s:
lift :: (a -> ... -> z) -> MySomething a -> ... -> MySomething z
-- Combine multiple `MySomething`s into just one that contains the data of all
-- them, combined in some way. It doesn't need to be a pair—it could be any
-- record type.
pair :: MySomething a -> ... -> MySomething z -> MySomething (a, ..., z)
-- Some of your things act like functions, and others like their arguments.
apply :: MySomething (a -> b) -> MySomething a -> MySomething b
-- You can turn any unadorned value into a `MySomething` parametrized by
-- the type
pure :: a -> MySomething a
-- There is some "basic" constant MySomething out of which you can build
-- any other one using `fmap`.
unit :: MySomething ()
----- Monad -----
bind :: MySomething a -> (a -> MySomething b) -> MySomething b
join :: MySomething (MySomething a) -> MySomething a
----- Traversable -----
traverse :: Applicative f => (a -> f b) -> MySomething a -> f (MySomething b)
sequence :: Applicative f => MySomething (f a) -> f (MySomething a)
注意四件事:
Applicative
可能不如Monad
那么着名,但它是一个非常重要且有价值的类 - 可以说是API的核心内容!人们最初使用Monad
的许多内容实际上只需要Applicative
。如果Monad
可以使用Applicative
,最好不要使用Traversable
。Monad
进行类似的评论 - 最初为sequence
(mapM
,Traversable
编写的许多功能实际上只需要Applicative
} + Monad
。Applicative
通常会发现它是Monad
,然后询问它是否也是{{1}}。