Haskell:重复函数(+)和(++),mappend

标签 haskell operator-overloading monads typeclass monoids

(+)(++)只是 mappend 的特化;我对吗?为什么需要它们?这是无用的重复,因为 Haskell 有这些强大的类型类和类型推断。
假设我们删除 (+)(++)并重命名 mappend (+)为了视觉方便和打字增益。
对于初学者来说,编码会更直观、更短、更容易理解:

--old and new
1 + 2
--result
3

--old
"Hello" ++ " " ++ "World"
--new
"Hello" + " " + "World"
--result
"Hello World"

--old
Just [1, 2, 3] `mappend` Just [4..6]
--new
Just [1, 2, 3] + Just [4..6]
--result
Just [1, 2, 3, 4, 5, 6]

(这让我做梦。)。对于一个坚持抽象和诸如 Haskell 之类的东西的美丽语言来说,为同一事物提供三个甚至更多的函数并不是一件好事。
我还看到了与 monad 相同的重复:fmapmap 相同或接近, (.) , liftM , mapM , forM , ...
我知道 fmap 的历史原因,但是幺半群呢? Haskell 委员会是否对此有所计划?它会破坏一些代码,但我听说,虽然我不确定,有一个即将到来的版本会有很大的变化,这是一个很好的机会。太可惜了……至少, fork 买得起吗?

编辑
在我阅读的答案中,有一个事实是,对于数字,(*)(+)适合mappend .其实我觉得(*)应该是 Monoid 的一部分!看:

目前,忘记函数memptymconcat , 我们只有 mappend .
class Monoid m where
    mappend :: m -> m -> m

但我们可以这样做:
class Monoid m where
    mappend :: m -> m -> m
    mmultiply :: m -> m -> m

它会(也许,我对此还不够了解)的行为如下:
3 * 3
mempty + 3 + 3 + 3
0 + 3 + 3 + 3
9

Just 3 * Just 4
Just (3 * 4)
Just (3 + 3 + 3 +3)
Just 12

[1, 2, 3] * [10, 20, 30]
[1 * 10, 2 * 10, 3 * 10, ...]
[10, 20, 30, 20, 40, 60, ...]

实际上,'mmultiply' 将仅根据 'mappend' 定义,因此对于 Monoid 的实例没有必要重新定义它!然后Monoid更接近数学;也许我们也可以添加(-)(/)上课!
如果这可行,我认为它将解决 Sum 的情况和 Product以及功能重复:mappend变成 (+)和新的 mmultiply只是 (*) .
基本上我建议用“上拉”重构代码。
哦,我们还需要一个新的 mempty对于 (*) .
我们可以将这些运算符抽象到一个类 MonoidOperator 中。并定义 Monoid如下:
class (Monoid m) => MonoidOperator mo m where
    mempty :: m
    mappend :: m -> m -> m

instance MonoidOperator (+) m where
    mempty = 0
    mappend = --definition of (+)

instance MonoidOperator (*) where
    --...

class Monoid m where
    -...

好吧,我还不知道该怎么做,但我认为所有这些都有一个很酷的解决方案。

最佳答案

您正在尝试在这里混合一些不同的概念。

算术和列表连接是非常实用的直接操作。如果你写:

[1, 2] ++ [3, 4]

...你知道你会得到[1, 2, 3, 4]作为结果。

一个 Monoid是一个更抽象的数学代数概念。这意味着 mappend不必是字面意思“将这个附加到那个;”它可以有许多其他含义。当你写:
[1, 2] `mappend` [3, 4]

...这些是该操作可能产生的一些有效结果:
[1, 2, 3, 4] -- concatenation, mempty is []

[4, 6]       -- vector addition with truncation, mempty is [0,0..]

[3, 6, 4, 8] -- some inner product, mempty is [1]

[3, 4, 6, 8] -- the cartesian product, mempty is [1]

[3, 4, 1, 2] -- flipped concatenation, mempty is []

[]           -- treating lists like `Maybe a`, and letting lists that
             -- begin with positive numbers be `Just`s and other lists
             -- be `Nothing`s, mempty is []

为什么mappend对于列表只是连接列表?因为这只是编写 Haskell 报告的人选择作为默认实现的 monoid 的定义,可能是因为它对列表的所有元素类型都有意义。实际上,您可以通过将列表包装在各种新类型中来为列表使用替代的 Monoid 实例;例如,对于列表执行笛卡尔积,有一个替代的 Monoid 实例。

“Monoid”的概念在数学中具有固定的含义和悠久的历史,在 Haskell 中更改其定义将意味着偏离数学概念,这是不应该发生的。 Monoid 不仅仅是对空元素和(字面)追加/连接操作的描述;它是遵循 Monoid 提供的接口(interface)的广泛概念的基础。

您正在寻找的概念是特定于数字的(因为您无法为 mmultiply 的所有实例定义类似 mproduce 或可能 mproduct/Maybe a 之类的东西),这个概念已经存在并被称为Semiring在数学中(好吧,您的问题中并没有真正涉及关联性,但是无论如何您都在示例中的不同概念之间跳跃-有时坚持关联性,有时不坚持-但总体思路是相同的)。

Haskell 中已经有 Semirings 的实现,例如在 algebra 中。包裹。

但是,Monoid 通常不是半环,特别是除了加法和乘法之外,实数的半环也有多种实现。不应该仅仅因为它“会很整洁”或“会节省一些击键次数”而向定义非常明确的类型类(如 Monoid)添加广泛的通用添加;我们有 (++) 是有原因的, (+)mappend作为单独的概念,因为它们代表完全不同的计算思想。

关于Haskell:重复函数(+)和(++),mappend,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10961483/

相关文章:

c++ - 在 cpp 中为 double 和 string 数据类型定义运算符 +

c++ - Operator [] 作为非静态函数

scala - 猫从 monad 堆栈中获取值(value)

haskell - 单独的 'App' 和 'BackgroundJobs' 包的布局

haskell - 在 Haskell 中,有没有办法在函数保护中进行 IO?

haskell - 图构建期间的连续传递风格 (CPS)

javascript - 仿函数或单子(monad)的名称和存在以链接和终止操作序列

haskell - 将列表转换为单独的参数

c++ - 如何将在重载 >> 运算符中输入的值插入 setter

macros - 在 Clojure 中,我们什么时候应该使用 monad 而不是宏,反之亦然?