Haskell中的非法多态或限定类型

时间:2018-06-17 23:31:08

标签: haskell higher-rank-types

为什么Haskell在尝试解析此类型签名时会抱怨?

f :: (a,s) -> (forall r.(r -> (a,r)),s)

2 个答案:

答案 0 :(得分:6)

Haskell不支持impredicative类型,特别是不允许forall出现在任何类型构造函数下(->除外)。

例如,Maybe (forall a. a), [forall a. a->a], ((forall a. a), Bool)被禁止。

如果您想要的话,请使用newtype包装。

newtype T = T (forall a. a->a)
foo :: [T] -- OK
foo = [T id]

答案 1 :(得分:4)

出于好奇,你想用这种类型做什么?它看起来像quote这样的连接组合类型,[X] quote == [[X]](有时称为unit)。换句话说,它在堆栈顶部采用一个值并将其包装在一个函数中,该函数在应用时将该值推送到堆栈。

这是我过去用于此类功能的一种表示。 Tupled类型系列将类型列表转换为嵌套元组以表示堆栈。

-- Tupled '[t1, t2, ..., tn] s == (t1, (t2, (... (tn, s))))
type family Tupled ts t where
  Tupled '[] t' = t'
  Tupled (t ': ts) t' = (t, Tupled ts t')

使用newtype包装器,我们可以创建一个函数(某个输入和输出arity),它在堆栈的“rest”中是多态的。

newtype as :-> bs = Fun (forall s. Tupled as s -> Tupled bs s)

这是隐藏 impredicative polymorphism 的标准方法,即在函数箭头forall以外的类型构造函数下使用(->) - 量化类型,就像你做的那样当你试着写(forall r. (r -> (a, r)), s)时。 Haskell不直接支持这个,但是如果你使用newtype包装器,那么编译器就知道何时引入并消除forall

通过展开此newtype并将其应用于堆栈类型,我们可以将包装函数应用于堆栈。

apply :: forall z as bs. (as :-> bs) -> Tupled as z -> Tupled bs z
apply (Fun f) as = f @z as

quote组合器在函数中包装堆栈的顶部元素:

quote :: forall a s. (a, s) -> ([] :-> '[a], s)
quote (a, s) = (Fun $ \s' -> (a, s'), s)

unquote将堆栈上的函数应用于堆栈的其余部分。

unquote
  :: forall z as bs s
  .  (Tupled as z ~ s)
  => (as :-> bs, s)
  -> Tupled bs z
unquote (f, s) = apply @z f s

(注意等式约束Tupled as z ~ s,这意味着“输入堆栈类型s必须以一系列类型as开头,剩下的任何类型称为z” 。)

add是加法运算符(+)被提升到堆栈;它只是添加了堆栈的前两个元素。

add :: forall a. (Num a) => '[a, a] :-> '[a]
add = Fun $ \ (x, (y, s)) -> (x + y, s)

现在,引用和取消引用元素会使其保持不变:

unquote (quote ("hello", ()) == ("hello", ())

可以直接应用添加功能......

apply add (1, (2, ())) == (3, ())

...或者放在堆叠上然后应用。

unquote (add, (1, (2, ()))) == (3, ())

这需要以下扩展名:

  • DataKinds允许类型级别的类型列表

  • RankNTypesScopedTypeVariables允许显式forall并将类型变量纳入范围,以便我们可以使用TypeApplications引用它们,因为我们需要{{ 1}}延迟指定“堆栈”类型,直到调用网站,如AllowAmbiguousTypes

  • apply @z f as启用TypeFamilies类型系列

  • Tupled为包装的函数类型提供漂亮的符号名TypeOperators