RankNTypes:将相同的函数应用于不同类型的对

时间:2013-06-05 10:21:02

标签: haskell ghc higher-rank-types

我试图定义此函数以重新组合三个对列表:

{-# LANGUAGE RankNTypes #-}

mapAndZip3 :: (forall x. x -> f x) -> [a] -> [b] -> [c] 
                                   -> [(f a, f b, f c)]
mapAndZip3 f la lb lc = zipWith3 (\a b c -> (f a, f b, f c)) la lb lc


main = do
    let x = mapAndZip3 (fst) [(1,"fruit"), (2,"martini")] 
                             [("chips","fish"),("rice","steak")]
                             [(5,"cake"),(4,"pudding")]
    print x -- was expecting [(1,"chips",5), (2,"rice",4)]

起初我没有包含RankNTypesforall,但是在查看this之后,即liftTup定义,我认为它应该足够了

但很明显,事实并非如此,因为我仍然会收到错误:

mapAndZip3.hs:8:25:
Couldn't match type `x' with `(f0 x, b0)'
  `x' is a rigid type variable bound by
      a type expected by the context: x -> f0 x at mapAndZip3.hs:8:13
Expected type: x -> f0 x
  Actual type: (f0 x, b0) -> f0 x
In the first argument of `mapAndZip3', namely `(fst)'

我对forall关键字的理解显然有限,但根据我的理解,在这种情况下,允许f接受任何类型。我不明白的是:一旦在给定的上下文中,并且使用过一次,那么定义是否会为剩余的上下文“固定”?

它似乎不是这样,因为如果我用Ints替换“chips”和“rice”,编译器仍会抱怨,所以我想我假设有些错误(当然,如果我删除了{的类型注释{1}}在后​​一种情况下,一切都解决了,因为签名被简化为mapAndZip3,但这不是我想要的。)

我也发现了这个question,但是如果这是同一个问题则无法真正做到,因为我尝试应用的功能不是mapAndZip3 :: (a -> t) -> [a] -> [a] -> [a] -> [(t, t, t)],而是idfst,实际返回不同类型snd的函数。

2 个答案:

答案 0 :(得分:8)

问题是fst没有所需的类型

(forall x. x -> f x)

fst的类型是

fst :: (a, b) -> a

a的格式不是f (a,b)f有一个必须使用类型构造函数实例化的变量,例如[]MaybeEither Boolf不能代表Λ (a,b) -> a之类的任何“类型函数”,它必须是类型构造函数。

如果我们为它提供所需类型的功能(对不起,愚蠢的例子),它就有效:

{-# LANGUAGE RankNTypes #-}

mapAndZip3 :: (forall x. x -> f x) -> [a] -> [b] -> [c]
                                   -> [(f a, f b, f c)]
mapAndZip3 f la lb lc = zipWith3 (\a b c -> (f a, f b, f c)) la lb lc

fst0 x = (True,x)

main = do
    let x = mapAndZip3 (fst0) [(1 :: Int,"fruit"), (2,"martini")]
                             [("chips","fish"),("rice","steak")]
                             [(5 :: Int,"cake"),(4,"pudding")]
    print x

因为此处fst0的类型为a -> ((,) Bool) a,其格式为x -> f x

答案 1 :(得分:6)

签名中的问题是f。让我们稍微扩展一下:

mapAndZip3 :: forall (a :: *) (b :: *) (c :: *) (f :: *->*)
           =>  (forall x. x -> f x) -> [a] -> [b] -> [c] 
                               -> [(f a, f b, f c)]

f在这里应该是“任何类型级别的函数”,在您的实例化中它将是type f (a,b) = a。但是Haskell不允许您仅仅通过类型构造函数来抽象类型级函数,例如MaybeIO。所以mapAndZip3 Just实际上是可能的,但fst不构造,但解构一个元组类型。

Haskell98中甚至不存在类型级函数,它们只能在TypeFamilies之后才能实现。问题基本上是Haskell的类型语言是无类型 1 ,但类型级函数需要是总函数 2 。但是,您无法真正定义任何在所有类型上定义的非平凡函数(即id或类型构造函数之外的函数)。类型级fst当然不是完全的,它只适用于元组类型。

因此,要使用此类函数,您需要以其他方式指定其上下文。使用TypeFamilies,它可以像这样工作:

class TypeFunctionDomain d where
  type TypeFunction d :: *

instance TypeFunctionDomain (a,b) where
  type TypeFunction (a,b) = a

mapAndZip3 :: (forall x. TypeFunctionDomain x => x -> TypeFunction x)
          -> [a] -> [b] -> [c] 
                   -> [(TypeFunction a, TypeFunction b, TypeFunction c)]

但是,这并不是你想要的:在同一范围内也不可能在TypeFunctionDomain实例中定义snd实例,这意味着mapAndZip3实际上不会是多态的完全,但只能使用单一功能。

这些问题只能在依赖类型语言中正确解决,例如Agda,其中种类实际上只是类型的类型,您可以定义类型级函数以及值级别的功能。但这需要付出代价:所有功能都必须是全部功能!这并不是一件坏事,但这意味着这些语言通常不是图灵完备的(这需要无限循环/递归的可能性;然而,对于完整的结果评估,这是)。


1 随着kind polymorphism etc.的出现,情况发生了一些变化。

2 这与例如C ++,它通过模板允许 - 尽管语法非常糟糕 - duck-typed 类型级函数。这可能是一个非常好的功能,但其中一个后果是,当您尝试使用实例化模板时,您经常会得到完全不可读的错误消息(与真实问题的关系甚至比GHC最糟糕的“可能修复”暗示......)更少隐式域之外的类型参数。