【问题标题】:What is practical use of monoids?幺半群的实际用途是什么?
【发布时间】:2014-12-01 13:21:51
【问题描述】:

我正在阅读 Learn You a Haskell 并且我已经介绍了应用程序,现在我正在学习幺半群。我对两者的理解都没有问题,尽管我发现 applicative 在实践中很有用,而 monoid 并非如此。所以我想我对 Haskell 有一些了解。

首先,说到Applicative,它创建了类似统一语法的东西来对“容器”执行各种操作。所以我们可以使用普通函数对Maybe、列表、IO(我应该说monads吗?我还不知道monads)执行操作,函数:

λ> :m + Control.Applicative
λ> (+) <$> (Just 10) <*> (Just 13)
Just 23
λ> (+) <$> [1..5] <*> [1..5]
[2,3,4,5,6,3,4,5,6,7,4,5,6,7,8,5,6,7,8,9,6,7,8,9,10]
λ> (++) <$> getLine <*> getLine
one line
 and another one
"one line and another one"
λ> (+) <$> (* 7) <*> (+ 7) $ 10
87

所以 applicative 是一种抽象。我认为我们可以没有它,但它有助于清楚地表达一些想法模式,这很好。

现在,让我们看看Monoid。它也是抽象且非常简单的一种。但它对我们有帮助吗?对于书中的每个示例,似乎都有更清晰的方法来做事:

λ> :m + Data.Monoid
λ> mempty :: [a]
[]
λ> [1..3] `mappend` [4..6]
[1,2,3,4,5,6]
λ> [1..3] ++ [4..6]
[1,2,3,4,5,6]
λ> mconcat [[1,2],[3,6],[9]]
[1,2,3,6,9]
λ> concat [[1,2],[3,6],[9]]
[1,2,3,6,9]
λ> getProduct $ Product 3 `mappend` Product 9
27
λ> 3 * 9
27
λ> getProduct $ Product 3 `mappend` Product 4 `mappend` Product 2
24
λ> product [3,4,2]
24
λ> getSum . mconcat . map Sum $ [1,2,3]
6
λ> sum [1..3]
6
λ> getAny . mconcat . map Any $ [False, False, False, True]
True
λ> or [False, False, False, True]
True
λ> getAll . mconcat . map All $ [True, True, True]
True
λ> and [True, True, True]
True

所以我们注意到了一些模式并创建了新的类型类......好吧,我喜欢数学。但从实际的角度来看,Monoid 的意义何在?它如何帮助我们更好地表达想法?

【问题讨论】:

标签: haskell monoids


【解决方案1】:

加布里埃尔·冈萨雷斯 (Gabriel Gonzalez) 在他的博客中写了很多关于为什么你应该关心的信息,而且你真的应该关心。您可以阅读它here(另请参阅this)。

这是关于 API 的可扩展性、架构和设计。这个想法是“传统架构”说:

将多个 A 类型的组件组合在一起,生成一个 B类型的“网络”或“拓扑”

这种设计的问题在于,随着您的程序扩展,重构时您的地狱也会如此。

所以你想改变模块 A 来改进你的设计或领域,所以你这样做了。哦,但是现在依赖于 A 的模块 B 和 C 坏了。你修复了B,太好了。现在你修复了 C。现在 B 又坏了,因为 B 也使用了 C 的一些功能。我可以永远继续下去,如果你曾经使用过 OOP - 你也可以。

然后就是 Gabriel 所说的“Haskell 架构”:

将类型 A 的多个组件组合在一起以生成一个新的 相同类型 A 的组分,与其取代部分在性质上没有区别

这也优雅地解决了问题。基本上:不要对模块进行分层或扩展以制作专门的模块。
相反,结合。

所以现在,鼓励的是,不要说“我有多个 X,所以让我们创建一个类型来表示它们的联合”,而是说“我有多个 X,所以让我们将它们组合成一个 X”。或者用简单的英语:“让我们首先制作可组合类型。” (你感觉到幺半群的潜伏了吗?)。

假设您想为您的网页或应用程序制作一个表单,并且您拥有创建的模块“个人信息表单”,因为您需要个人信息。后来你发现你也需要“更改图片格式”所以就这么快写了。现在你说我想把它们结合起来,所以我们来做一个“个人信息和图片表格”模块。在现实生活中的可扩展应用程序中,这可能而且确实会失控。可能不是表格,而是演示,您需要撰写和撰写,因此您最终会得到“个人信息 & 更改图片 & 更改密码 & 更改状态 & 管理朋友 & 管理愿望清单 & 更改视图设置 & 请不要再扩展我&请&请停止!&停止!!!!“模块。这并不漂亮,您将不得不在 API 中管理这种复杂性。哦,如果你想改变任何东西 - 它可能有依赖关系。所以..是的..欢迎来到地狱。

现在让我们看看另一个选项,但首先让我们看看它的好处,因为它会引导我们:

这些抽象可以无限扩展,因为它们始终保留 可组合性,因此我们永远不需要分层进一步的抽象 在上面。这是你应该学习 Haskell 的原因之一:你学会了如何 构建扁平化架构。

听起来不错,因此,与其制作“个人信息表单”/“更改图片表单”模块,不如停下来想想我们是否可以让这里的任何东西可组合。好吧,我们可以做一个“表格”,对吧?也会更抽象。
然后为您想要的所有内容构建一个,将它们组合在一起并获得一种形式,就像任何其他形式一样。

因此,您不会再得到一棵杂乱无章的复杂树,因为您采用两种形式并获得一种形式的关键。所以Form -&gt; Form -&gt; Form。正如您已经清楚地看到的那样,这个签名是mappend 的一个实例。

替代方案和传统架构可能看起来像a -&gt; b -&gt; c,然后是c -&gt; d -&gt; e,然后......

现在,有了表格就没有那么具有挑战性了;挑战在于在现实世界的应用程序中使用它。要做到这一点,只需尽可能多地问自己(因为它得到了回报,正如你所见):我怎样才能使这个概念可组合?既然幺半群是实现这一目标的一种简单方法(我们想要简单),请先问自己:这个概念怎么是幺半群?

旁注:幸运的是,Haskell 非常不鼓励您扩展类型,因为它是一种函数式语言(没有继承)。但是仍然可以为某事创建一个类型,为某事创建另一种类型,并在第三种类型中将两种类型都作为字段。如果这是为了作曲 - 看看你是否可以避免它。

【讨论】:

  • 您可能还想提及this post,它专门使用Monoid 作为运行示例。
【解决方案2】:

好吧,我喜欢数学。但从实际的角度来看,Monoid 的意义何在?它如何帮助我们更好地表达想法?

这是一个 API。一个简单的。对于支持的类型:

  • 元素为零
  • 有追加操作

很多类型都支持这些操作。因此,为操作和 API 命名有助于我们更清楚地捕捉事实。

API 很好,因为它们让我们重用代码和重用概念。意味着更好、更易维护的代码。

【讨论】:

    【解决方案3】:

    重点是,当您将Int 标记为Product 时,您表达了整数相乘的意图。并通过将它们标记为Sum,将它们添加在一起。

    然后您可以在两者上使用相同的mconcat。这用于例如在Foldable 中,其中一个foldMap 表达了折叠包含结构的想法,同时以特定的幺半群方式组合元素。

    【讨论】:

      【解决方案4】:

      一个非常简单的例子是foldMap。只需将不同的幺半群插入这个单一函数,您就可以计算:

      • firstlast 元素,
      • 元素的sumproduct(从这也是它们的平均值等),
      • 检查all 元素或any 是否具有给定属性,
      • 计算最大或最小元素,
      • 将元素映射到集合(如列表、sets、字符串、TextByteString 或 ByteString Builder)并将它们连接在一起 - 它们都是幺半群。

      此外,幺半群是可组合的:如果 ab 是幺半群,那么 (a, b) 也是。因此,您可以轻松地一次计算多个不同的幺半群值(例如计算元素平均值时的总和和乘积等)。

      虽然你可以在没有幺半群的情况下完成所有这些工作,但使用 foldrfoldl 会更麻烦,而且通常效率也更低:例如,如果你有一个平衡二叉树并且你想找到它的最小值和最大元素,你不能同时使用foldr(或两者都使用foldl),对于其中一种情况,一个总是O(n),而使用foldMap使用适当的幺半群,这两种情况都是 O(log n)

      这只是一个函数foldMap!还有许多其他有趣的应用程序。举个例子,exponentiation by squaring 是一种有效的计算能力方式。但它实际上与计算能力无关。你可以为任何幺半群实现它,如果它的&lt;&gt;O(1),你就有一种有效的方法来计算 n-times x &lt;&gt; ... &lt;&gt; x。突然间,您可以进行有效的矩阵求幂运算并计算 n-th Fibonacci number 只需 O(log n) 乘法。请参阅 semigroup 中的times1p

      另见Monoids and Finger Trees

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-04-02
        • 2019-05-17
        • 2015-04-22
        • 2021-07-22
        • 1970-01-01
        相关资源
        最近更新 更多