Haskell 存储和检索某个类型的对象-可能吗?如果是,如何进行?
假设我有一些类型类Haskell 存储和检索某个类型的对象-可能吗?如果是,如何进行?,haskell,typeclass,Haskell,Typeclass,假设我有一些类型类Foo和一些数据类型FooInst,它们是Foo的实例: class Foo a where foo :: a -> String data FooInst = FooInst String instance Foo FooInst where foo (FooInst s) = s 现在,我想定义一个数据类型,它存储类型在Footypeclass中的对象,并且能够从该数据类型中提取该对象并使用它 我找到的唯一方法是使用GADTs和Rank2Types语
Foo
和一些数据类型FooInst
,它们是Foo
的实例:
class Foo a where
foo :: a -> String
data FooInst = FooInst String
instance Foo FooInst where
foo (FooInst s) = s
现在,我想定义一个数据类型,它存储类型在Foo
typeclass中的对象,并且能够从该数据类型中提取该对象并使用它
我找到的唯一方法是使用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 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…
行以使用用例表达式
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
那么,如何存储类型化的对象并将其检索回来呢?例如
main :: IO ()
main = do
case cont of
Container fi -> putStrLn $ foo fi
您需要将存在类型字段fi
的使用封装在一个表达式中,该表达式的类型不依赖于fi
的类型;这里的putStrLn$foo-fi
类型为IO()
这个示例非常无用,因为对容器的内容字段所能做的唯一一件事就是调用foo
,所以在构建容器之前,您最好只调用foo
,并给出字段类型字符串。但是更有趣的是,如果Foo
具有类似a->a->a
的类型的操作,或者Container
具有包含相同存在量化变量的类型的多个字段,等等
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
(或任何其他具有签名的函数fooa=>a->…
):
case cont of Container {content = a} -> foo a
第4节有一个很好的例子,存储了多个具有可显示实例的项。注意不要落入这种已知的反模式:@pdexter据我所知,我对容器
的定义实际上等同于可显示
,除了记录语法。这是记录语法吗?那么是什么让我觉得不同呢?我觉得奇怪的是,只是一个不同的语法也改变了语义……您使用的是GADTs,而示例没有。这只是我第一眼看到的一个区别。因此,这不仅仅是不同的语法,而是不同的理论。@pdexter该示例在最后显示,它可以使用GADT等价地表示。但贝扎德·努里的回答或多或少地解释了这种差异。无论如何,谢谢你。你的代码对我来说不起作用,因为使用“Foo”可能的修复:将(Foo a)添加到数据构造函数“Container”的上下文中,我没有注意到你也定义了错误的Container
。贝扎德,努里的回答和你的意思一样。