简体   繁体   English

键入类与代数数据类型?

[英]Type classes vs algebraic data types?

I frequently start thinking about a problem in terms of type classes to be defined, and realize when I start coding that I don't need type classes and can solve my problem with algebraic data types instead, which seems more straightforward. 我经常开始考虑要定义的类型类的问题,并且当我开始编码时意识到我不需要类型类并且可以用代数数据类型来解决我的问题,这似乎更直接。 As a result, I am wondering when type classes are necessary. 结果,我想知道何时需要类型类。

As I understand them, type classes are a way to say that certain functions exist for certain types. 据我了解,类型类是一种说某些类型存在某些函数的方法。 For example, when a type MyType is an instance of Monoid, then I can use the functions mempty :: MyType and mappend :: MyType -> MyType -> MyType , such that the monoid laws hold. 例如,当类型MyType是Monoid的一个实例时,我可以使用函数mempty :: MyTypemappend :: MyType -> MyType -> MyType ,这样monoid定律就成立了。

We could acheive the same with algebraic data types, by defining Monoid as a type rather than a typeclass: 通过将Monoid定义为类型而不是类型类,我们可以使用代数数据类型实现相同的效果:

data Monoid a = Monoid { mempty :: a
                       , mappend :: a -> a -> a}

and then say that a type MyType is a monoid by defining a new value of type Monoid MyType (which with typeclasses is done by declaring it an instance): 然后通过定义Monoid MyType类型的新值(类型类通过声明它是一个实例来完成)来说类型MyType是一个monoid:

monoidMyType :: Monoid MyType
monoidMyType = Monoid { mempty = ...
                      , mappend = \a b -> ... }

Then, we can write functions that operate on monoids like: 然后,我们可以编写像monoids一样运行的函数:

dummyFun :: Monoid a -> a -> a
dummyFun m x = mempty m x

And use those functions by explicitly passing the appropriate "monoid value": 并通过显式传递适当的“monoid值”来使用这些函数:

result = dummyFun monoidMyType valueOfMyType

The equivalent last two steps would happen very similarly with typeclasses: 与类型类相似的最后两个步骤将非常相似:

dummyFun :: (Monoid a) => a -> a
dummyFun x = mempty x

result = dummyFun valueOfMyType

The only substantial difference that I see is that with algebraic data types, the monoid value must be passed explicitly when we call the function dummyFun . 我看到的唯一重要区别是,对于代数数据类型,当我们调用函数dummyFun时,必须显式传递monoid值。 Although it is a bit more practical not to have to pass it explicitly, it doesn't look to me like a major obstacle. 虽然不必明确地传递它更实际,但它并不像我看来是一个主要的障碍。

In fact, I see an advantage that algebraic data types have over type classes: you can relate together types accross different functions: 事实上,我看到代数数据类型具有类型类的优势:您可以将不同函数的类型联系在一起:

data Bla a b = Bla {f1 :: a -> b, f2 :: b -> a, ...}

Doing this with typeclasses would (i believe) require using the multiple parameter type classes extension. 使用类型类来执行此操作(我相信)需要使用多参数类型类扩展。

Is there a reason to use type classes that I'm not seeing here? 有没有理由使用我在这里看不到的类型类?

When designing software, can you interchangeably chose to use type classes or algebraic data types, or are there situations where you can't do without type classes? 在设计软件时,您是否可以互换地选择使用类型类或代数数据类型,还是在没有类型类的情况下?

You just invented type classes! 你刚刚发明了类型类! A class is a dictionary of functions. 函数字典。 During compilation, code like 在编译期间,代码就像

class Monoid a where
    mempty :: a
    mappend :: a -> a -> a

instance Monoid [a] where
    mempty = []
    mappend = (++)

mconcat :: Monoid a => [a] -> a
mconcat = foldr mappend

main = print $ mconcat ["foo", "bar"]

is translated into explicit dictionary-passing style. 被翻译成显式字典传递风格。

data Monoid a = Monoid { mempty :: a, mappend :: a -> a -> a }

list_monoid = Monoid [] (++)

mconcat :: Monoid a -> [a] -> a
mconcat monoid = foldr (mappend monoid)

main = print $ mconcat list_monoid ["foo", "bar"]

That translation is exactly the most important difference between type classes and dictionaries: classes are implicit. 这种翻译正是类型类和字典之间最重要的区别:类是隐式的。 You don't have to explicitly pass the monoid variable around - the compiler takes care of the plumbing for you. 您不必显式传递monoid变量 - 编译器会为您处理管道。 It would be especially tedious to build composed instances like Ord a => Ord [a] by hand. 手工编写像Ord a => Ord [a]这样的组合实例会特别繁琐。

There's one other key difference between classes and dictionaries, and that's coherence . 类和字典之间还有另一个关键区别,那就是连贯性 Basically, there's always at most one "best" instance to satisfy a given constraint, that instance is global and unique, and you can't override it. 基本上,最多只有一个“最佳”实例来满足给定约束,该实例是全局且唯一的,并且您不能覆盖它。 With dictionary-passing style, on the other hand, the function will just use whatever dictionary you passed in and there are no guarantees of uniqueness. 另一方面,使用字典传递样式,该函数将使用您传入的任何字典,并且不保证唯一性。 This is sometimes good and sometimes bad . 这有时很好,有时也很糟糕

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM