在Haskell中,有没有办法用多种方式表示类型应该是类型类的实例?

时间:2010-10-22 16:08:53

标签: haskell typeclass

(如果问题很愚蠢或明显,请提前抱歉 - 我对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指定的功能)?

5 个答案:

答案 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 preludethis论文。

答案 1 :(得分:7)

对于Monoid,具体来说,这是使用类型包装器完成的。如果您查看模块Data.Monoid,您会发现Bool值的两个不同的幺半群结构:AnyAll,以及实现类型的两种不同结构NumSumProduct以及Maybe类型的两种结构:FirstLast

然而,你的semiring示例会遇到问题,因为SumProduct的幺半群结构都提供了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 aliftMaybe :: Monoid a -> Monoid (Maybe a)

可以使用相同的技术来实现您的Semiring。例如(像以前一样使用Monoid数据类型):data Semiring a = Semiring { addition, multiplication :: Monoid a }