在类型类中使用类型构造函数有什么优势? [英] Any advantage of using type constructors in type classes?
问题描述
例如,类 Functor
:
class Functor a
instance Functor Maybe
这里也许
是类型构造函数.
但是我们可以通过另外两种方式做到这一点:
But we can do this in two other ways:
首先,使用多参数类型类:
Firstly, using multi-parameter type classes:
class MultiFunctor a e
instance MultiFunctor (Maybe a) a
第二种使用类型族:
class MonoFunctor a
instance MonoFunctor (Maybe a)
type family Element
type instance Element (Maybe a) a
现在,后两种方法有一个明显的优势,那就是它允许我们执行以下操作:
Now there's one obvious advantage of the two latter methods, namely that it allows us to do things like this:
instance Text Char
或者:
instance Text
type instance Element Text Char
所以我们可以使用单态容器.
So we can work with monomorphic containers.
第二个优点是我们可以创建没有类型参数作为最终参数的类型的实例.可以说我们制作了 Ething
样式类型,但错误地放置了这些类型:
The second advantage is that we can make instances of types that don't have the type parameter as the final parameter. Lets say we make an Either
style type but put the types the wrong way around:
data Silly t errorT = Silly t errorT
instance Functor Silly -- oh no we can't do this without a newtype wrapper
而
instance MultiFunctor (Silly t errorT) t
工作正常,并且
instance MonoFunctor (Silly t errorT)
type instance Element (Silly t errorT) t
也很好.
鉴于在类型类定义中仅使用完整类型(而不是类型签名)的灵活性优势,假设您使用的是GHC,并且不介意使用扩展,是否有任何理由使用原始样式定义?也就是说,您可以做一些特殊的事情来放置类型构造函数,而不仅仅是在多类类型类或类型族中无法做到的类型类中的完整类型吗?
Given these flexibility advantages of only using complete types (not type signatures) in type class definitions, is there any reason to use the original style definition, assuming you're using GHC and don't mind using the extensions? That is, is there anything special you can do putting a type constructor, not just a full type in a type class that you can't do with multi-parameter type classes or type families?
推荐答案
您的提案忽略了有关现有 Functor
定义的一些非常重要的细节,因为您没有仔细研究将要写的内容的细节.发生在类的成员函数中.
Your proposals ignore some rather important details about the existing Functor
definition because you didn't work through the details of writing out what would happen with the class's member function.
class MultiFunctor a e where
mfmap :: (e -> ??) -> a -> ????
instance MultiFunctor (Maybe a) a where
mfmap = ???????
目前, fmap
的一个重要属性是它的第一个参数可以更改类型. fmap show ::(Functor f,Show a)=>f a->f字符串
.您不能只是丢掉它,否则就失去了 fmap
的大部分价值.所以说真的, MultiFunctor
应该看起来更像...
An important property of fmap
at the moment is that its first argument can change types. fmap show :: (Functor f, Show a) => f a -> f String
. You can't just throw that away, or you lose most of the value of fmap
. So really, MultiFunctor
would need to look more like...
class MultiFunctor s t a b | s -> a, t -> b, s b -> t, t a -> s where
mfmap :: (a -> b) -> s -> t
instance (a ~ c, b ~ d) => MultiFunctor (Maybe a) (Maybe b) c d where
mfmap _ Nothing = Nothing
mfmap f (Just a) = Just (f a)
请注意,尝试至少进行 close 推理变得非常复杂.所有功能依赖项均已就位,以允许实例选择而无需在各处注解类型.(我可能已经错过了几个可能的功能依赖项!)实例本身增加了一些疯狂的类型相等约束,以使实例选择更加可靠.最糟糕的是-与 fmap
相比,它的推理属性仍然较差.
Note just how incredibly complicated this has become to try to make inference at least close to possible. All the functional dependencies are in place to allow instance selection without annotating types all over the place. (I may have missed a couple possible functional dependencies in there!) The instance itself grew some crazy type equality constraints to allow instance selection to be more reliable. And the worst part is - this still has worse properties for reasoning than fmap
does.
假设我以前的实例不存在,我可以这样写一个实例:
Supposing my previous instance didn't exist, I could write an instance like this:
instance MultiFunctor (Maybe Int) (Maybe Int) Int Int where
mfmap _ Nothing = Nothing
mfmap f (Just a) = Just (if f a == a then a else f a * 2)
这当然被打破了-但是它以一种前所未有的新方式被打破了. Functor
定义的真正重要部分是 fmap <中的
a
和 b
类型/code>不会出现在实例定义中的任何位置.仅查看类就足以告诉程序员 fmap
不能的行为取决于类型 a
和 b 代码>.您可以免费获得该保证.您无需相信实例已正确编写.
This is broken, of course - but it's broken in a new way that wasn't even possible before. A really important part of the definition of Functor
is that the types a
and b
in fmap
don't appear anywhere in the instance definition. Just looking at the class is enough to tell the programmer that the behavior of fmap
cannot depend on the types a
and b
. You get that guarantee for free. You don't need to trust that instances were written correctly.
因为 fmap
为您提供了免费的保证,所以在定义实例时甚至不需要同时检查两个 Functor
律.检查法则 fmap id x == x
就足够了.当第一个定律被证明时,第二个定律是免费提供的.但是随着我刚刚提供的 mfmap
损坏,即使第二定律不是, mfmap id x == x
是真实的.
Because fmap
gives you that guarantee for free, you don't even need to check both Functor
laws when defining an instance. It's sufficient to check the law fmap id x == x
. The second law comes along for free when the first law is proven. But with that broken mfmap
I just provided, mfmap id x == x
is true, even though the second law is not.
作为 mfmap
的实现者,您需要做更多的工作来证明您的实现是正确的.作为用户,您必须对实现的正确性给予更多信任,因为类型系统不能保证那么多.
As the implementer of mfmap
, you have more work to do to prove your implementation is correct. As a user of it, you have to put more trust in the implementation's correctness, since the type system can't guarantee as much.
如果您为其他系统编写了更完整的示例,则会发现如果您想支持 fmap
的全部功能,它们同样会遇到很多问题.这就是为什么它们没有真正使用的原因.他们增加了很多的复杂性,只获得了很小的效用.
If you work out more complete examples for the other systems, you find that they have just as many issues if you want to support the full functionality of fmap
. And this is why they aren't really used. They add a lot of complexity for only a small gain in utility.
这篇关于在类型类中使用类型构造函数有什么优势?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!