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

时间:2012-06-09 13:34:03

标签: 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(.)liftMmapM,{{相同或几乎相同1}},... 我知道有forM的历史原因,但是幺半群呢? Haskell委员会是否正在计划这方面的事情?它会破坏一些代码,但我听说,虽然我不确定,但是有一个传入的版本会有很大的变化,这是一个很好的场合。太可惜了......至少,叉子价格合理吗?

修改 在我读到的答案中,有一个事实是,对于数字,fmap(*)可能适合(+)。事实上,我认为mappend应该是(*)的一部分!看:

目前,关于功能Monoidmempty,我们只有mconcat

mappend

但我们可以这样做:

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

它(可能,我还没有足够的关于它)表现如下:

class Monoid m where
    mappend :: m -> m -> m
    mmultiply :: m -> m -> m

实际上'mmultiply'只能用'mappend'来定义,所以对于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, ...] 的实例,没有必要重新定义它!然后Monoid更接近数学;也许我们也可以在课堂上添加Monoid(-)! 如果这有效,我认为它将解决(/)Sum以及重复函数的情况:Product变为mappend而新的(+)是只是mmultiply。 基本上我建议用“拉起”来重构代码。 哦,我们还需要(*)的新mempty。 我们可以在类(*)中抽象这些运算符,并按如下方式定义MonoidOperator

Monoid

我还不知道该如何做到这一点,但我认为所有这一切都有一个很酷的解决方案。

4 个答案:

答案 0 :(得分:10)

你试图在这里混合一些独立的概念。

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

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

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


您正在寻找的概念特定于数字(因为您无法为mmultiply的所有实例定义mproducemproduct / Maybe a之类的内容例如),一个已经存在的概念,在数学中被称为Semiring(嗯,你并没有真正涵盖你的问题中的相关性,但是你在你的例子中跳过不同的概念,有时候会坚持相关性,有时不是,但总的想法是一样的。)

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

然而,Monoid通常不是半环形,并且除了加法和乘法之外,还存在多个实数的Semirings实现。为Monoid这样非常明确定义的类型类添加广泛的广义加法不应仅仅因为它“整齐”或“会节省一些键击”;我们之所以将(++)(+)mappend作为单独的概念,是因为它们代表了完全不同的计算方法。

答案 1 :(得分:8)

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

虽然(+)(*)都是幺半群,但他们还有其他分配法律,这两项操作相关,以及取消法则,例如0 * x = 0.基本上,(+)(*)形成ring。某些其他类型的两个幺半群可能不满足这些环(或甚至较弱的半环)属性。运算符(+)(*)的命名提示了它们的附加(相互关联)属性。因此,我会避免通过将mappend重命名为+*来颠覆传统的数学直觉,因为这些名称表明可能无法容纳的其他属性。有时过多的过载(即过多的泛化)会导致直觉的丧失,从而导致可用性的丧失。

如果您碰巧有两个形成某种环的幺半群,那么您可能希望从这些中获取Num的实例,名称为“+”和“{{1} }“建议其他属性。

关于conflating(++)和mappend

*重命名为mappend可能更合适,因为使用(++)的额外精神负担更少。实际上,因为列表是 free monoid (即没有附加属性的monoid),所以使用传统的list-concatentation运算符(++)来表示monoid的二进制运算似乎不太可能像一个可怕的想法。

为一种类型定义多个monoid

正如您所指出的,(++) (+) (*)都是幺半群,但对于同一类型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的应用程序解析为特定操作,每个应用程序必须采用一个值来表示指示特定幺​​半群“变体”的类型。

除此之外:您问题的标题提及mconcat。这是mappend完全不同的操作 - mconcat monoid homomorphism 从免费的monoid到其他一些monoid,即用mappend替换 cons nil 与mempty。

答案 2 :(得分:4)

如果你看一下Hackage,你会发现many alternative Prelude implementations aimed at fixing these problems

答案 3 :(得分:3)

对于数字有两个Monoids - ProductSum,你会如何处理?

  对于一个坚持抽象的美丽语言和Haskell之类的东西,三个,甚至更多,相同的功能并不是一件好事。

抽象不是要消除代码重复。算术和幺半群操作是两种不同的想法,尽管它们具有共享相同语义的情况,但你并没有通过合并它们获得任何东西。