如何检测Monad?

时间:2013-12-10 13:17:03

标签: haskell monads

我们中的许多人没有函数式编程的背景知识,更不用说类别理论代数了。因此,我们假设我们需要并因此创建类似

的泛型类型
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

4 个答案:

答案 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)

Boola的函数相当于一对(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 TFunctor通常比Monad更容易检测(或反驳)。有些计算可以通过Applicative结构轻松检测到,然后才能看到它们也是Monad

具体而言,您可以通过以下方式证明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。我不建议你阅读它。

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

有一套非常标准的简单monad,任何中级Haskell程序员都应该立即熟悉它们。这些是WriterReaderStateIdentityMaybeEitherCont和{{1} }。通常,您会发现您的类型只是对这些标准monad之一的一个小修改,因此可以以类似于标准的方式制作monad本身。

此外,一些[],称为变形金刚,&#34; stack&#34;形成其他Monad。这具体意味着你可以组合Monad monad和Reader monad的一个(修改后的形式)组成Writer monad。这些经过修改的表单在ReaderWritertransformers包中公开,通常由附加的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

See this previous answer of mine for a rather in-depth discussion of how a certain sequence could be written as a Monad... but derived no advantage from doing so.。有时序列只是一个列表。

了解扩展

有时候你的数据类型显然不是monad,但显然取决于monad实例。一个常见的例子就是解析你可能需要进行多次搜索的搜索,但是不能立即明确你可以从中形成一个monad。

但如果您熟悉MonoidApplicative,您就知道有MonadAlternative

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)

注意四件事:

  1. Applicative可能不如Monad那么着名,但它是一个非常重要且有价值的类 - 可以说是API的核心内容!人们最初使用Monad的许多内容实际上只需要Applicative。如果Monad可以使用Applicative,最好不要使用Traversable
  2. 可以对Monad进行类似的评论 - 最初为sequencemapMTraversable编写的许多功能实际上只需要Applicative } + Monad
  3. 由于上述原因,您Applicative通常会发现它是Monad,然后询问它是否也是{{1}}。
  4. 不要忘记法律 - 他们是什么使它成为什么,什么不成功的权威仲裁者。
相关问题