【发布时间】:2016-07-04 04:59:44
【问题描述】:
假设我有一些类型类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
那么,我怎样才能存储一个类型分类的东西并取回它?
【问题讨论】:
-
第 4 节 here 有一个很好的例子来存储具有可显示实例的多个项目
-
小心不要落入这个已知的反模式:lukepalmer.wordpress.com/2010/01/24/…
-
@pdexter 据我了解,我对
Container的定义实际上等同于Showable,除了记录语法。是记录语法,那么是什么让我与众不同?我觉得很奇怪,只是不同的语法也会改变语义...... -
您使用的是 GADT,而示例不是。这只是我乍一看发现的一个区别。所以这不仅仅是语法不同,而是理论不同。
-
@pdexter 该示例在最后表明它可以使用 GADT 等效地表示。但是 behzad.nouri 的回答或多或少地解释了差异。还是谢谢你。