我试图定义此函数以重新组合三个对列表:
{-# 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
,但是在查看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)]
,而是id
或fst
,实际返回不同类型snd
的函数。
答案 0 :(得分:8)
问题是fst
没有所需的类型
(forall x. x -> f x)
fst
的类型是
fst :: (a, b) -> a
且a
的格式不是f (a,b)
。 f
有一个必须使用类型构造函数实例化的变量,例如[]
,Maybe
,Either Bool
。 f
不能代表Λ (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不允许您仅仅通过类型构造函数来抽象类型级函数,例如Maybe
或IO
。所以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最糟糕的“可能修复”暗示......)更少隐式域之外的类型参数。