假设我有一些类型Foo
和一些数据类型FooInst
,它是Foo
的实例:
class Foo a where
foo :: a -> String
data FooInst = FooInst String
instance Foo FooInst where
foo (FooInst s) = s
现在,我想定义一个数据类型,它存储一个类型在Foo
类型类中的对象,并且能够从该数据类型中提取该对象并使用它。
我找到的唯一方法是使用GADT和Rank2Types语言扩展并定义如下数据类型:
data Container where
Container :: { content :: Foo a => a } -> Container
然而,问题是,我无法使用content
选择器从Container中获取内容:
cont :: Container
cont = Container{content = FooInst "foo"}
main :: IO ()
main = do
let fi = content cont
putStrLn $ foo fi
导致编译错误
Cannot use record selector ‘content’ as a function due to escaped type variables
Probable fix: use pattern-matching syntax instead
但是当我将let ...
行修改为
let Conainer fi = cont
我得到一个相当有趣的错误
My brain just exploded
I can't handle pattern bindings for existential or GADT data constructors.
Instead, use a case-expression, or do-notation, to unpack the constructor.
如果我再次尝试修改let ...
行以使用case-expression
let fi = case cont of
Container x -> x
我得到了一个不同的错误
Couldn't match expected type ‘t’ with actual type ‘a’
because type variable ‘a’ would escape its scope
This (rigid, skolem) type variable is bound by
a pattern with constructor
Container :: forall a. (Foo a => a) -> Container,
in a case alternative
at test.hs:23:14-24
那么,如何存储一个类型化的东西并将其取回?
答案 0 :(得分:6)
使用:
data Container where
Container :: {content :: Foo a => a} -> Container
类型类约束甚至没有强制执行。那是
void :: Container
void = Container {content = 42 :: Int}
即使42 :: Int
不是 Foo
的实例,也会进行类型检查。
但如果你改为:
data Container where
Container :: Foo a => {content :: a} -> Container
Rank2Types
语言扩展。void
以上示例将不再进行类型检查。 此外,您可以使用模式匹配在内容上调用foo
(或带有签名Foo a => a -> ...
的任何其他函数):
case cont of Container {content = a} -> foo a
答案 1 :(得分:2)
例如
main :: IO ()
main = do
case cont of
Container fi -> putStrLn $ foo fi
您需要在表达式中使用存在类型字段fi
,其类型不依赖于fi
的类型;此处putStrLn $ foo fi
的类型为IO ()
。
此示例非常无用,因为您可以对content
的{{1}}字段执行的唯一操作就是调用Container
,因此您也可以调用{{1}在构造容器之前,给出字段类型foo
。但如果foo
具有类似String
类型的操作,或者Foo
具有涉及相同存在量化变量的类型的多个字段等,则会更有趣。