(+)
和 (++)
只是 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 相同的重复:
fmap
与 map
相同或接近, (.)
, liftM
, mapM
, forM
, ...我知道
fmap
的历史原因,但是幺半群呢? Haskell 委员会是否对此有所计划?它会破坏一些代码,但我听说,虽然我不确定,有一个即将到来的版本会有很大的变化,这是一个很好的机会。太可惜了……至少, fork 买得起吗?编辑
在我阅读的答案中,有一个事实是,对于数字,
(*)
或 (+)
适合mappend
.其实我觉得(*)
应该是 Monoid
的一部分!看:目前,忘记函数
mempty
和 mconcat
, 我们只有 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/