Haskell RankNTypes:将相同的函数应用于不同类型的对
我试图定义此函数以重新组合三个成对列表:Haskell RankNTypes:将相同的函数应用于不同类型的对,haskell,ghc,higher-rank-types,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
{-# 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)]
起初我没有包括RankNTypes
或forall
,但在seing之后,即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)'
显然,我对所有关键字的理解有限,但从我的理解来看,在这种情况下,应该允许f
接受任何类型。我不明白的是:一旦在给定的上下文中使用一次,该定义是否会在剩余的上下文中得到“修复”
看起来不是这样,因为如果我用int替换“chips”和“rice”,编译器仍然会抱怨,所以我猜我是在假设出了什么错误(当然,如果我删除mapAndZip3
的类型注释,在后一种情况下,一切都会解决,因为签名被简化为mapAndZip3::(a->t)->[a]->[a]->[a]->[(t,t,t)]
,但这不是我想要的)
我也发现了这一点,但无法确定这是否是同一个问题,因为我尝试应用的函数不是id
,而是fst
或snd
,实际上返回不同类型的函数(a->b)
签名中的问题是f
。让我们稍微扩展一下:
mapAndZip3 :: forall (a :: *) (b :: *) (c :: *) (f :: *->*)
=> (forall x. x -> f x) -> [a] -> [b] -> [c]
-> [(f a, f b, f c)]
f
在这里应该是“任何类型级别的函数”,在您的实例化中,它应该是类型f(a,b)=a
。但是Haskell不允许抽象类型级别的函数,只允许抽象类型构造函数,比如可能
或IO
。因此,mapAndZip3仅仅
实际上是可能的,但是fst
不构造而是解构元组类型
类型级别的函数在Haskell 98中甚至不存在,它们只在以后才可能存在。问题基本上是Haskell的kind语言不是类型1,但类型级函数必须是总函数2。但是,您不能真正定义任何在所有类型上定义的非平凡函数(即除了id
或类型构造函数之外的函数)。类型级别fst
当然不是total,它只适用于元组类型
因此,要使用这些函数,您需要明确地以其他方式指定它们的上下文。使用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)]
然而,这并不是您真正想要的:不可能在同一范围内定义与snd
相对应的TypeFunctionDomain
实例,这意味着mapAndZip3实际上根本不是多态的,而只能与单个函数一起工作
这些问题只能在依赖类型的语言中正确解决,例如,类型实际上只是类型的类型,您可以定义类型级函数以及值级函数。但这是有代价的:所有函数都必须是总函数!这并不是一件坏事,但这意味着这些语言通常不是真正的图灵完整的(这需要无限循环/递归的可能性;然而⟂代码>关于完整结果评估)
1.随着经济的发展,情况发生了一些变化
2不同于C++,它允许-尽管有非常可怕的语法-鸭型的类型级函数,通过模板。这可能是一个很好的特性,但其后果之一是您经常会收到完全无法读取的错误消息(与实际问题的关系甚至比GHC最糟糕的“可能修复”提示还要小……)尝试使用隐式域之外的类型参数实例化模板时。
问题在于fst
没有所需的类型
(forall x. x -> f x)
fst
的类型为
fst :: (a, b) -> a
而a
不是f(a,b)
的形式。f
有一个必须用类型构造函数实例化的变量,类似于[]
,可能
,或者Bool
f
不能代表任何像a(a,b)->a这样的“类型函数”,它必须是一个类型构造函数
如果我们为其提供所需类型的函数(抱歉,愚蠢的示例):
因为这里的fst0
的类型是a->((,)Bool)a
,它的形式是x->fx
,谢谢!我真的很难选择“一个”正确答案,因为两者都解释了这是不可能的。虽然我非常喜欢丹尼尔·菲舍尔的答案,因为一开始它是最容易理解的,但左撇子的答案确实给了我一些额外的上下文。为双方干杯!
{-# 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