假设我有一些类型类型的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选择器从容器中获取内容:
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表达式
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那么,,我如何存储一个分类的东西并取回它呢?
发布于 2016-07-03 17:16:43
通过以下方式:
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} -> ContainerRank2Types语言扩展。void示例将不再进行类型检查。foo (或任何其他带有签名Foo a => a -> ...的函数):
容器{content = a} -> foo a的事例发布于 2016-07-03 16:37:16
例如
main :: IO ()
main = do
case cont of
Container fi -> putStrLn $ foo fi您需要将存在类型字段fi的使用封装在一个表达式中,该表达式的类型不依赖于fi的类型;在这里,putStrLn $ foo fi具有IO ()类型。
这个示例非常无用,因为您对Container的Container字段所能做的唯一事情就是在它上调用foo,所以您最好在构造容器之前调用foo,并给出字段类型String。但是更有趣的是,如果Foo有像a -> a -> a这样的类型的操作,或者Container有多个包含相同存在量化变量的类型的字段,等等。
https://stackoverflow.com/questions/38171870
复制相似问题