Haskell在类型签名中使用类型类

CMC*_*kai 1 haskell types typeclass type-signature

假设一个简单的类型类约束签名:

f :: (Eq a, Num b) => a -> b
f str = 4
Run Code Online (Sandbox Code Playgroud)

我想知道为什么这些不起作用

f :: (Eq a) -> (Num b)
f str = 4

f :: Eq -> Num
f str = 4
Run Code Online (Sandbox Code Playgroud)

我知道类型类有类型* -> Constraint,而类型签名只接受各种类型*.

但我的问题是为什么会有这种限制?为什么不能像类型一样使用类型类?允许使用像类型这样的类型类的能力有哪些优点和缺点?

lef*_*out 7

有(如果我们忽略未装箱的类型)只有一种类型实际上有任何值:*.所有其他类型不包含类型,但只包含"类型级实体".编译器可以使用它们来确定如何处理实际类型及其值,但是在运行时永远不可能有某种类型的"类型"值* -> Constraint.

*一种对价值类型仅仅是一个游戏规则.这是一件好事,出于同样的原因,拥有一个强大的静态类型系统可以防止在运行时进行无意义的转换.笏?或者,让我们从字面上理解,出于同样的原因,你不能只是把你的王扯到你的棋子上,无论这个特征在你遇到的特定情况下看起来多么吸引人.

如果某些扩展确实允许从非*线程类型创建值,特别是* -> Constraint,我们需要一大堆非显而易见的定义来明确这些"类值"是如何实际应该使用的.可能,它将等于包含类'方法作为字典的记录类型.但是,究竟是什么......规范几乎是一场噩梦.这种方式使语言本身复杂化并不值得花费,因为1.使用类型类的标准方法对于所有应用程序的至少95%都是好的,并且2.当你需要具体类型类时,你可以使用GADT,ConstraintKinds甚至普通的旧手动定义的字典记录都很容易做到这一点.这些都不需要扭曲语言如何对待价值观的基本思想,就像非*类型一样.


反正...让我们探讨了一下这是如何可能的工作.有一件事是肯定的:它不会让你写任何简单的东西f str = 4!

考虑

f1 :: forall a, b . Eq a -> Num b
Run Code Online (Sandbox Code Playgroud)

两者都是Eq a, Num b :: Constraint,所以我们有类型的价值观Constraint.这基本上是给定实例的特定方法字典.所以执行f1必须看起来像

f1 (EqDict (d_eq :: a -> a -> Bool))
       = NumDict { dict_fromInteger = ??? :: Integer -> b
                 , dict_plus        = ??? :: b -> b -> b
                 , ...
                 , dict_signum      = ??? :: b -> b
                 }
Run Code Online (Sandbox Code Playgroud)

显然,没有有意义的方法来定义结果中的所有方法.你能用这种"类功能"做的就是从一个强大的阶级"投射"到一个较弱的阶级.你可以定义

monadApp :: forall m . Monad m -> Applicative m
monadApp (MonadDict {dict_return = d_ret, dict_bind = d_bd})
        = ApplicativeDict { dict_pure = d_ret
                          , dict_app = \fs vs -> d_bd fs (\f -> d_bd vs $ d_ret . f) }
Run Code Online (Sandbox Code Playgroud)

事实上,那个具体的实际上是有用的,但仅仅因为Monad(仍然,但不是很久!)缺乏Applicative它应该只具有的超类.通常,没有太多理由必须明确地"降级"任何类,因为超类关系(或元组 - ConstraintKinds)会自动执行此操作.