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

时间:2014-12-23 07:56:04

标签: haskell types typeclass type-signature

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

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

我想知道为什么这些没有用

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

f :: Eq -> Num
f str = 4

我知道类型类的类型为* -> Constraint,而类型签名只接受*种。

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

2 个答案:

答案 0 :(得分:7)

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

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

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


无论如何......让我们探索一下这个可能的工作方式。有一件事是肯定的:它会允许你写任何像f str = 4这样简单的东西!

考虑

f1 :: forall a, b . Eq a -> Num b

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
                 }

显然,没有有意义的方法来定义结果中的所有方法。你可以用这样一个"类功能"是"项目"从较强的阶级到较弱的阶级。例如。你可以定义

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) }

事实上,具体的一点是有用的,但只是因为Monadstill, but not for long!)缺少Applicative作为超级类而应该只具有它。通常情况下,没有必要明确"降级"任何类,因为超类关系(或tuple-ConstraintKinds)会自动执行此操作。

答案 1 :(得分:4)

类型变量在类型签名中可能会出现多次:

f :: Eq a => a -> a -> a -> Bool

将上述内容写为

f :: Eq a -> Eq a -> Eq a -> Bool

看起来很不方便。更糟糕的是,a上可能有多个约束:

g :: (Show a, Eq a) => a -> Bool

我们如何用替代符号写这个?

g :: (Show & Eq) a -> Bool    -- ??

如您在上一个示例中所建议的那样完全忘记a会签名 含糊不清:考虑

h1 :: (Eq a)       => a -> a -> a -> a -> Bool
h2 :: (Eq a, Eq b) => a -> a -> b -> b -> Bool

这些是完全不同的签名:您可以拨打h2 1 2 [1] [2]但不能h1 1 2 [1] [2] 因为后者要求四个参数属于同一类型。 在使用提议的约定之后,它们被简化为相同的签名:

h12 :: Eq -> Eq -> Eq -> Eq -> Bool

通话h12 1 2 [1] [2]有效吗?上面的签名太模糊不清楚了。