Monads和抽象

时间:2013-09-10 03:25:48

标签: haskell monads

我是haskell,功能语言和monad的新手 我一直在乱搞这个约一个月;我读了 learn you a haskell 并且正在试图制作我的haskell网站。

但是有些东西困扰着我:monads抽象。 如果我理解正确,monad是可以排序的“数据容器”。 我可以用“>> =”解压缩它,例如“幕后”将完成更多的工作 对我来说,如果我没有monad定义,我必须猜测它将如何被解压缩。

例如:

我们有一个列表monad,解包它会对其元素进行排序

[1,2,3] >>= return . (+1) -- gives back [2,3,4]

或者像这些例子中的作家那样更复杂的monad: Log Writer Monad

或者我可能有一个webWriter monad,对于每个'解包'它的值,它会向一些远程服务器发送请求(我不确定这个,但我试图给出一个极端的情况)< / p>

我的问题是:我是否可以通过查看monad用户界面(我猜是类型定义)告诉应用函数('&gt;&gt; =','applyLog')在幕后做什么?

希望我能很好地解释自己。

谢谢,奥伦。

7 个答案:

答案 0 :(得分:8)

虽然你只是通过查看界面无法知道(>>=)对特定monad的作用,但是每个monad都必须遵守法律才能构成“适当的”monad。这限制了return(>>=)的可能实现。 monad laws如下:

  • 左侧身份:return a >>= f等于f a
  • 正确的身份:m >>= return等于m
  • 相关性:(m >>= f) >>= g等于m >>= (\x -> f x >>= g)

例如,如果将列表monad的return定义为\x -> [x,x]而不是\x -> [x],则会破坏左侧身份法。 return 5 >>= \x -> [x+1](\x -> [x+1]) 5不同。

此外,not all monads can be intuitively understood as 'containers' of some kind。容器类比适用于List和Maybe,但是读者呢? Reader值并不真正“包含”任何内容。相反,它是对依赖于外部不变环境的计算的描述。

Monad是实现monad接口并尊重monad法则的任何东西。

编辑:作为如何直观了解monad实例对给定类型执行操作的示例,请考虑Data.Stream.Infinite.Stream包中的streams。流就像列表,只有它们总是无限的。

Stream有一个Monad实例。在这种情况下,return(>>=)会做什么?

return的类型为a -> Stream a。这种类型唯一可能的函数是返回作为参数传递的值的无限重复的函数。

(>>=)更棘手。它的类型为Stream a -> (a -> Stream b) -> Stream b。这种类型的一个可能的函数是获取第一个参数的头部并将其应用于第二个参数的函数,返回结果流。 s >>= f = f $ head s

(>>=)的另一种可能实现是将类型a -> Stream b的函数应用于原始流的每个元素,获得类型{{1}的中间结果然后以某种方式将流的流折叠为单个Stream (Stream b)值。怎么做?你可以简单地采用无限广场的对角线!

哪个版本的Stream b与monad法律兼容?第一个肯定没有,因为它打破了正确的认同。 (>>=)的结果为1,2,3,4... >>= return。第二个实现尊重正确的身份(你能看出原因吗?),这使我们更加确信它可能是为流实现1,1,1,1...的正确方法。当然,您需要确保所有法律的实际证据!

答案 1 :(得分:4)

我描述了四种信息来源,您可以用它们来了解>>=对特定monad的行为。

>>=

的类型

>>=的类型始终相同。它在Monad类型类中指定。见documentation。类型是:

(>>=) :: forall a b. m a -> (a -> m b) -> m b

其中m是您感兴趣的特定monad的占位符。例如,对于列表monad,>>=的类型为:

(>>=) :: forall a b. [a] -> (a -> [b]) -> [b]

请注意,我刚刚将m ...替换为[...]

monad法律

>>=的实现对于每个monad都是不同的,但是所有monad都应该遵守monad定律。这些法律在Monad类型类的文档中指定。见documentation again。法律是:

  1. return a >>= k == k a
  2. m >>= return == m
  3. m >>= (\x -> k x >>= h) == (m >>= k) >>= h
  4. 因此,无论某些特定monad的实现如何,您都可以使用这些定律来推理您的代码。例如,如果您的代码包含法律左侧的代码,则可以通过法律的相应右侧替换该代码,并且行为不应更改。

    以下是如何使用monad法的示例。假设我写了这段代码:

    foo = do
      x <- bar
      return x
    

    我们甚至不知道monad在这里使用了什么,但我们知道有一些monad,因为我们看到了符号。为了应用monad法律,我们不得不将这些符号用于>>=的调用:

    foo = bar >>= (\x -> return x)
    

    请注意,\x -> return xreturn相同(通过η-reduction。

    foo = bar >>= return
    

    根据第二个monad法则,此代码与调用bar完全相同。

    foo = bar
    

    因此看起来原始>>=函数中的foo根本无法做任何有趣的事情,因为monad法则允许我们将其排除在外。我们甚至不知道具体的monad在这里为>>=运营商提供了什么。

    特定monad的文档

    如果您需要了解>>=对特定monad的行为的更多信息,特定monad的文档应该告诉您。您可以使用hoogle来搜索文档。例如,documentation of StateT告诉您:

      

    return函数保持状态不变,而>>=使用第一次计算的最终状态作为第二次计算的初始状态。

    特定monad

    的实现

    如果您想了解有关特定monad实现的更多详细信息,您可能需要查看实际实现。搜索instance Monad ...声明。例如,查看implementation of StateT。列表monad的实现位于this file中,搜索instance Monad []或查看此内容除外:

    instance  Monad []  where
        m >>= k             = foldr ((++) . k) [] m
        m >> k              = foldr ((++) . (\ _ -> k)) [] m
        return x            = [x]
        fail _              = []
    

    也许不是最明显的定义,但如果您为列表monad调用>>=,则会发生这种情况。

    摘要

    所有monad都共享>>=return的类型签名以及monad法律。根据这些约束条件,每个monad都提供>>=return的不同实现,如果您想了解所有细节,则必须研究instance Monad ...的源代码。宣言。如果您只是想学习如何使用特定的monad,请尝试查找一些相关的文档。

答案 2 :(得分:3)

monad不是“数据容器”。 monad是更高阶的计算结构。如果您考虑&lt; =&lt;。

,则可以更好地理解&gt;&gt; =的含义

f . g - 功能的简单组合

mf <=< mg - 计算的组合。

在我看来,这更有说服力。但是,&lt; =&lt;可以通过&gt;&gt; =定义,因此通常只有&gt;&gt; =需要定义。您还可以定义&gt;&gt; =通过&lt; =&lt;:m >>= f = (f <=< const m) ()

“m a”不是数据容器。它只表示它实现了“类似”行为 - 所以现在我们只能组合正确类型的部分。 (>>=) :: m a -> (a -> m b) -> m b告诉我们,因为“m a”暴露了“类似”行为,我们可以加入一个使用“类似”行为转变为“b”类行为的函数。

如何为任何类型实现“类似”行为?好吧,这就是为什么我们说它是一个仿函数:它将任何函数a-> b映射到m a-> mb-对于每个函数a-> b它以这样的方式找到(或构建)函数,如果f和g组成,然后mf和mg组成。这是关于保留类型a和b的代数属性的强烈声明:如果f加1,g取1,则得到相同的数字;那么用m g组成的m也会让你回到你开始的地方 - 它可能没有存储在任何地方的数字,但行为将是“相同的数字”一样。

另外,作为monad,意味着它只涉及高阶结构,而不是实际类型:因为“m a”可以有任何类型a,这意味着实现不能依赖于类型的细节。 monad只能使用计算的代数结构 - 在“代数”的广义上。例如,列表[a]可以包含任何元素,但monad只能使用列表的代数结构 - 枚举元素,拆分列表,foldr等,但不能,比如说,加起来所有元素 - 加起来是“像”一样。其他monad也将具有monad特定功能;如果没有它们,它们可能会毫无用处 - 例如,askatomicallyretry等。

答案 3 :(得分:2)

仅查看monad API的类型签名与查看函数的类型签名相同,例如:a -> b -> c,它只告诉您给定一些函数可以为您提供其他内容。函数如何执行此操作是函数的实现细节。

同样地,bindreturn和其他monad特定函数(例如状态monad中的putget)仅为您提供内容使用这些函数可以完成从一件事到另一件事的所有映射。如果您需要了解monad实际工作的如何的基础逻辑,您可以查看文档(如果提供)或源代码。

答案 4 :(得分:2)

  

我可以告诉应用函数......在幕后做什么

对于您没有提出的问题,有很多非常准确的答案。你能告诉monad在使用它的背景下做了什么吗?不,不是一般的。

你必须希望monad的名称及其文档(我经常非常害怕)帮助,并且周围的代码(通常非常简洁)为您提供上下文。

当然,如果您熟悉的是您的代码或代码库,那么它很简单。但是,大多数Haskell代码似乎没有被优化为“可skimmable”。

答案 5 :(得分:1)

这是一个有趣的问题。我会说你对monad可以做的事情有一个很好的理解。我会说,如果不阅读文档,就不可能知道任何函数的具体行为。每个monad的绑定和返回都是专门为类型的结构和目的而实现的。

答案 6 :(得分:1)

通过仅查看其类型签名,您无法确切地知道return>>=将对给定的monad做什么。那是因为他们的类型签名将始终

return :: Monad m => a -> m a
(>>=)  :: Monad m => m a -> (a -> m b) -> m b

Très通用。这在类型级别是很棒的,因为它是函数的确切说明。

在更精细的分辨率下,您将不得不查看monad实例声明。