(+)
和(++)
只是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
,{{相同或几乎相同1}},...
我知道有forM
的历史原因,但是幺半群呢? Haskell委员会是否正在计划这方面的事情?它会破坏一些代码,但我听说,虽然我不确定,但是有一个传入的版本会有很大的变化,这是一个很好的场合。太可惜了......至少,叉子价格合理吗?
修改
在我读到的答案中,有一个事实是,对于数字,fmap
或(*)
可能适合(+)
。事实上,我认为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
实际上'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
我还不知道该如何做到这一点,但我认为所有这一切都有一个很酷的解决方案。
答案 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
的所有实例定义mproduce
或mproduct
/ 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 - Product
和Sum
,你会如何处理?
对于一个坚持抽象的美丽语言和Haskell之类的东西,三个,甚至更多,相同的功能并不是一件好事。
抽象不是要消除代码重复。算术和幺半群操作是两种不同的想法,尽管它们具有共享相同语义的情况,但你并没有通过合并它们获得任何东西。