(如果问题很愚蠢或明显,请提前抱歉 - 我对Haskell没有太多经验。)
有没有办法用多种方式表示类型应该是类型类的实例?这最好用一个例子来说明(这可能有些愚蠢):在数学中,我们可以说半环是一个在一个操作(我们称之为加法,身份0)和一个幺半群下的交换幺半群的集合。另一个(我们称之为乘法)以及乘法在加法上分配的要求,并且0在乘法下消灭所有元素。后面的部分在这里并不重要。
现在假设我有一个类型Monoid
(不要与Data.Monoid
混淆),
class Monoid m where
unit :: m
operation :: m -> m -> m
并且想要创建一个类型类Semiring
。根据上面给出的定义,我想说“如果类型r是两个(不同)方式中的幺半群,我们称之为半环”。所以我想要像
class (Monoid r, Monoid r) => Semiring r where ...
当然不起作用。不可否认,这个例子变得有点奇怪,因为我们不再需要semirings的函数,所以类型类是空的,但我希望它能说明我所要求的(或者只是假装我们需要一些函数) f:r->r
的{{1}}。
所以,在一般设置中,我问:给定类型类Semiring r
,有没有办法参数化类型类A
,要求B a
是一个实例a
有两种方式(意味着A
应该以两种方式实现a
指定的功能)?
答案 0 :(得分:11)
一种选择是为半环的两个操作定义自己的幺半群:
class AdditiveMonoid m where
zero :: m
(<+>) :: m -> m -> m
class MultiplicativeMonoid m where
one :: m
(<*>) :: m -> m -> m
然后合并它们:
class (MultiplicativeMonoid m, AdditiveMonoid m) => Semiring m
问题在于你无法表达幺半群定律或一个操作是可交换的这一事实。您可以获得的最佳结果是为法律定义快速检查属性。
对于某些灵感,请查看numeric prelude和this论文。
答案 1 :(得分:7)
对于Monoid,具体来说,这是使用类型包装器完成的。如果您查看模块Data.Monoid
,您会发现Bool
值的两个不同的幺半群结构:Any
和All
,以及实现类型的两种不同结构Num
:Sum
和Product
以及Maybe
类型的两种结构:First
和Last
。
然而,你的semiring示例会遇到问题,因为Sum
和Product
的幺半群结构都提供了mempty
的实现(unit
的Haskell版本})和mappend
(你的operation
的Haskell版本。)
答案 2 :(得分:4)
其他答案提到了newtype
包装器,但没有给出使用它们的明确解决方案:
-- export these newtypes from the module defining Semiring
newtype Add a = Add a
newtype Multiply a = Multiply a
class (Monoid (Add a), Monoid (Multiply a)) => Semiring a where
-- empty
instance Monoid (Add Integer) where
unit = Add 0
Add a `operation` Add b = Add (a + b)
-- etc.
您需要一些GHC扩展程序,例如FlexibleContexts
。
答案 3 :(得分:3)
另请参阅Conor McBride的“节奏部分”帖子:http://www.haskell.org/pipermail/libraries/2008-January/008917.html,虽然这是在价值水平,但对类型类没有帮助。
Kmett的Monoids库(在他剥离Ring之前)实现了类似于Daniel Velkov的方法:http://hackage.haskell.org/package/monoids-0.1.36
我应该补充一点,关于这种方法的好处是通过明确地在数据类型上定义加法和乘法,你可以捕获它们不相同 - 即,后者分布在前者上。
答案 4 :(得分:2)
正如其他答案所提到的,常见的技术是newtype包装器。在许多情况下,这似乎是对类型级概念的滥用。类型类是逻辑“公理”,表明某种类型的事实是正确的;例如,Maybe可能是Monad,或者Int是Num,或者列表是在它们的元素被排序时排序的。通常,如在Eq和Ord的情况下,还有其他合理的定义,但所选择的定义在某种程度上是“规范的”。其他时候,如Monoid一样,没有。
对于Monoid和其他高度抽象的结构,我认为data
声明会更有用。例如,data Monoid a = Monoid {mempty :: a ; mappend :: a -> a -> a}
。然后,我们有addition :: Num a => Monoid a
,liftMaybe :: Monoid a -> Monoid (Maybe a)
等
可以使用相同的技术来实现您的Semiring。例如(像以前一样使用Monoid数据类型):data Semiring a = Semiring { addition, multiplication :: Monoid a }
。