我经常开始考虑要定义的类型类方面的问题,并意识到当我开始编码时我不需要类型类,并且可以使用代数数据类型来解决我的问题,这看起来更简单。因此,我想知道什么时候需要类型类。
据我了解,类型类是表示某些类型存在某些函数的一种方式。例如,当类型 MyType 是 Monoid 的实例时,我可以使用以下函数mempty :: MyType
and mappend :: MyType -> MyType -> MyType
,使得幺半群定律成立。
通过将 Monoid 定义为类型而不是类型类,我们可以使用代数数据类型实现相同的效果:
data Monoid a = Monoid { mempty :: a
, mappend :: a -> a -> a}
然后通过定义类型的新值来说类型 MyType 是一个幺半群Monoid MyType
(对于类型类,这是通过将其声明为实例来完成的):
monoidMyType :: Monoid MyType
monoidMyType = Monoid { mempty = ...
, mappend = \a b -> ... }
然后,我们可以编写对幺半群进行操作的函数,例如:
dummyFun :: Monoid a -> a -> a
dummyFun m x = mempty m x
并通过显式传递适当的“幺半群值”来使用这些函数:
result = dummyFun monoidMyType valueOfMyType
对于类型类,等效的最后两个步骤将非常相似地发生:
dummyFun :: (Monoid a) => a -> a
dummyFun x = mempty x
result = dummyFun valueOfMyType
我看到的唯一实质性区别是,对于代数数据类型,当我们调用函数时必须显式传递幺半群值dummyFun
。虽然不必明确通过它更实际一些,但在我看来这并不是一个主要障碍。
事实上,我看到代数数据类型相对于类型类的一个优势:您可以将不同函数中的类型关联在一起:
data Bla a b = Bla {f1 :: a -> b, f2 :: b -> a, ...}
使用类型类执行此操作(我相信)需要使用多参数类型类扩展。
有没有理由使用我在这里没有看到的类型类?
在设计软件时,您是否可以交替选择使用类型类或代数数据类型,或者是否存在不能没有类型类的情况?