假设我有一些类型类 Foo
和一些数据类型 FooInst
是 Foo
的实例:
class Foo a where
foo :: a -> String
data FooInst = FooInst String
instance Foo FooInst where
foo (FooInst s) = s
现在,我想定义一个数据类型来存储一个对象,该对象的类型在 Foo
类型类中,并且能够从该数据类型中提取该对象并使用它。
我发现的唯一方法是使用 GADTs 和 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-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
那么,我怎样才能存储一个类型分类的东西并取回它呢?
最佳答案
与:
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
关于haskell - 存储和检索某个类型类的对象 - 可能吗?如果是,如何?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38171870/