(+)
和 (++)
只是 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语言来说并不是好事情。
我也看到了关于单子的相同重复: fmap
与map
、(.)
、liftM
、mapM
、forM
几乎相同。
我知道fmap
有历史原因,但是对于幺半群呢? Haskell委员会是否计划对此进行一些改进? 这将破坏一些代码,但我听说(虽然不确定)即将推出一些变化很大的新版本,这是一个很好的机会。真是太可惜了... 至少,分叉是可以承担的吗?
编辑
在我读到的答案中,有一个事实是对于数字,(*)
或(+)
都可以放在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
-...
我不知道如何做到这一点,但我认为有一个很酷的解决方案可以解决所有这些问题。
Monoid
的一个实例 - 加法和乘法。确实存在一些重复的函数,应该将它们合并在一起(map
、fmap
、liftM
、liftA
、(<*>)
、ap
和许多其他函数),但我认为mappend
(或在较新版本中的(<>)
)不是需要合并的函数之一。 - VitusMonoid
类型类将是一件真正可惜的事情:有许多类型在两种不同(有用的)方式下并不是单子。 - Daniel Wagnermappend
和(++)
在其中一个实现中会更好,但在其他实现中放弃前者。此外,我不确定保留两者的教学用途是否合适:这使得解释类型类更加困难,因为初学者会问“我必须为每个实例创建重复函数吗?[...]那么为什么Haskell,这种超级干净的语言,要这样做?”从实际角度来看,这并不方便... - L01man