【问题标题】:Store and retrieve an object of some typeclass - is it possible? If yes, how?存储和检索某个类型类的对象 - 可能吗?如果是,如何?
【发布时间】:2016-07-04 04:59:44
【问题描述】:

假设我有一些类型类Foo 和一些数据类型FooInstFoo 的实例:

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 的回答或多或少地解释了差异。还是谢谢你。

标签: haskell typeclass


【解决方案1】:

与:

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
    

【讨论】:

    【解决方案2】:

    例如

    main :: IO ()
    main = do
      case cont of
        Container fi -> putStrLn $ foo fi
    

    您需要将您对存在类型字段fi 的使用封装在一个类型不依赖于fi 类型的表达式中;这里是putStrLn $ foo fi,其类型为IO ()

    这个例子没什么用,因为你唯一能对Containercontent字段做的就是调用foo,所以你最好在构造容器之前调用foo并给出字段类型String。但更有趣的是,Foo 的操作具有 a -> a -> a 之类的类型,或者 Container 具有多个字段,其类型涉及相同的存在量化变量等。

    【讨论】:

    • 你的代码对我不起作用,我收到错误No instance for (Foo a) arising from a use of ‘foo’ Possible fix: add (Foo a) to the context of the data constructor ‘Container’
    • 我没有注意到你还定义了Container 错误。 behzad.nouri 的回答就是你的意思。
    猜你喜欢
    • 2017-07-25
    • 1970-01-01
    • 1970-01-01
    • 2019-10-21
    • 2011-09-28
    • 2011-10-13
    • 2011-11-14
    • 2018-05-22
    • 1970-01-01
    相关资源
    最近更新 更多