首页 文章

Monads和抽象

提问于
浏览
6

我是haskell,功能语言和monad的新手 .
我已经搞乱了一个月左右;我已经阅读了learn you a haskell并且正在试图制作我的haskell网站 .

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

例如:

我们有列表monad,解压缩它将对其元素进行排序

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

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

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

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

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

谢谢,奥伦 .

7 回答

  • 2

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

    • 左侧身份: return a >>= f 等于 f a

    • 右侧身份: m >>= return 等于 m

    • 相关性: (m >>= f) >>= g 等于 m >>= (\x -> f x >>= g)

    例如,如果List monad的 return 被定义为 \x -> [x,x] 而不是 \x -> [x] ,则会破坏Left身份法 . return 5 >>= \x -> [x+1](\x -> [x+1]) 5 不同 .

    另外,not all monads can be intuitively understood as 'containers' of some kind . 容器类比适用于List和Maybe,但是读者呢? Reader值't really '包含' anything. Instead, it' s依赖于外部不变环境的计算描述 .

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

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

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

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

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

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

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

  • 1

    我描述了可用于了解特定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 . 法律是:

    • return a >>= k == k a

    • m >>= return == m

    • m >>= (\x -> k x >>= h) == (m >>= k) >>= h

    因此,无论某些特定monad的实现是什么,您都可以使用这些定律来推理您的代码 . 例如,如果您的代码包含法律左侧的代码,则可以通过法律的相应右侧替换该代码,并且行为不应更改 .

    这是一个如何使用monad定律的例子 . 假设我写了这段代码:

    foo = do
      x <- bar
      return x
    

    我们不喜欢一些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,请尝试查找一些相关的文档 .

  • 8

    monad不是“数据容器” . monad是更高阶的计算结构 . 如果您考虑<= <,则可以更好地理解>> =的含义 .

    f . g - 功能的简单组合

    mf <=< mg - 计算的组成 .

    在我看来,这更有说服力 . 但是,<= <可以通过>> =来定义,所以通常只需>> =需要定义 . 你也可以定义>> =通过<= <: m >>= f = (f <=< const m) ()

    "m a"不是数据容器 . 它只表示它实现了类似于"a"的行为 - 所以现在我们只能组合正确类型的部分 . (>>=) :: m a -> (a -> m b) -> m b 告诉我们,因为"m a"暴露了类似于"a"的行为,我们可以加入一个使用类似"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只能使用计算的代数结构 - 在"algebraic"的广义上 . 例如,列表[a]可以包含任何元素,但monad只能与列表的代数结构一起运行 - 枚举元素,拆分列表,折叠等,但不能说,加起来所有元素 - 加起来就是"a" . 其他monad也将具有monad特定功能;如果没有它们,它们可能会毫无用处 - 例如 askatomicallyretry 等 .

  • 3

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

    类似地, bindreturn 和其他monad特定函数(如状态monad中的 putget )只能为您提供 what 使用这些函数可以完成从一件事到另一件事的所有映射 . 如果您需要了解monad实际工作的 how 的基础逻辑,您可以浏览文档(如果提供)或源代码 .

  • 4

    我可以告诉应用功能......在幕后做什么

    对于你没有问过的问题,有很多完全准确的答案 . 你能告诉monad在使用它的背景下做了什么吗?不,不是一般的 .

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

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

  • 2

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

  • 1

    你不能通过仅仅查看它们的类型签名来确切地告诉 return>>= 将对给定的monad做什么 . 那是因为他们的类型签名将是 always

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

    Très通用 . 这在类型级别是很棒的,因为它是函数执行的精确规范 .

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

相关问题