存储和检索某个类型类的对象 - 这可能吗?如果有,怎么样?

时间:2016-07-03 16:17:52

标签: haskell typeclass

假设我有一些类型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

那么,如何存储一个类型化的东西并将其取回?

2 个答案:

答案 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
  1. 您不再需要Rank2Types语言扩展。
  2. 强制执行类型类约束;因此,void以上示例将不再进行类型检查。
  3. 此外,您可以使用模式匹配在内容上调用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具有涉及相同存在量化变量的类型的多个字段等,则会更有趣。