12

(+)并且(++)只是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是否相同或几乎相同... Haskell 委员会是否对此有所计划?它会破坏一些代码,但我听说,虽然我不确定,有一个即将到来的版本会有很大的变化,这是一个很好的机会。太可惜了……至少,叉子买得起吗?map(.)liftMmapMforMfmap

编辑 在我读到的答案中,有一个事实是,对于数字,要么适合,(*)要么(+)适合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更接近数学;也许我们也可以在课堂上添加(-)(/)!如果这行得通,我认为它将解决SumandProduct以及函数重复的情况:mappend变得(+)和新mmultiply的只是(*). 基本上我建议用“上拉”重构代码。哦,我们还需要一个新mempty的 for (*)。我们可以将这些运算符抽象到一个类中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
    -...

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

4

4 回答 4

11

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

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

[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 []

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

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


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

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

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

于 2012-06-09T14:57:39.130 回答
9

将 mappend 重命名为(+)/(*)

虽然(+)(*)都是幺半群,但它们有额外的分配律将这两个操作联系起来,以及抵消律,例如 0*x = 0。本质上,(+)(*)形成一个。其他类型的两个幺半群可能不满足这些环(甚至更弱的半环)属性。(+)运算符的命名(*)暗示了它们的附加(相互关联)属性。因此,我会避免通过重命名mappend来颠覆传统的数学直觉,+或者*因为这些名称暗示了可能不具备的附加属性。有时过多的重载(即过多的概括)会导致直觉的丧失,从而导致可用性的丧失。

如果您确实有两个形成某种环的幺半群,那么您可能希望从中派生一个实例Num,因为名称“ +”和“ *”暗示了其他属性。

关于合并 (++) 和 mappend

重命名mappend(++)可能更合适,因为(++). 事实上,由于列表是自由幺半群(即没有附加属性的幺半群),那么使用传统的列表连接运算符(++)来表示幺半群的二元运算似乎不是一个糟糕的主意。

关于为一种类型定义多个幺半群

正如您所指出的,两者(+)都是(*)幺半群,但两者都不能成为Monoid相同类型的实例t。您提供一半的一种解决方案是在类中添加一个额外的类型参数Monoid来区分两个幺半群。请注意,类型类只能按类型参数化,而不是您在问题中显示的表达式。一个合适的定义是这样的:

class Monoid m variant where
 mappend :: variant -> m -> m -> m
 mempty :: variant -> m

data Plus = Plus
data Times = Times

instance Monoid Int Plus where
    mappend Plus x y = x `intPlus` y
    mempty = 0

instance Monoid Int Times where
    mappend Times x y = x `intTimes` y
    mempty = 1

(+) = mappend Plus
(*) = mappend Times 

为了将mappend/的应用程序mempty解析为特定的操作,每个都必须采用一个值来见证指示特定 monoid “变体”的类型。

旁白:你的问题的标题提到mconcat。这是一个完全不同的操作mappend-mconcat是从自由幺半群到一些其他幺半群的幺半群同态,即用 mappend 替换cons 用 mempty替换nil

于 2012-06-09T15:07:04.760 回答
4

如果您查看 Hackage,您会发现许多 旨在解决这些问题替代 Prelude 实现

于 2012-06-09T15:02:07.173 回答
3

那么有两个用于数字的 Monoids -你ProductSum如何处理呢?

对于一门坚持抽象和诸如 Haskell 之类的东西的美丽语言来说,为同一事物提供三个甚至更多的函数并不是一件好事。

抽象不是为了消除代码重复。算术和 Monoid 操作是两种不同的想法,尽管它们共享相同的语义,但合并它们并没有任何好处。

于 2012-06-09T13:48:57.147 回答