【问题标题】:Heterogeneous Data.Map in HaskellHaskell中的异构Data.Map
【发布时间】:2016-04-01 12:35:37
【问题描述】:

是否可以在 Haskell 中使用 GADT 而不是 Dynamic 来实现异构 Data.Map?我尝试按照this answer 中的说明对异构集合进行建模:

{-# LANGUAGE GADTs #-}

class Contract a where
   toString :: a -> String

data Encapsulated where
   Encapsulate :: Contract a => a -> Encapsulated

getTypedObject :: Encapsulated -> a
getTypedObject (Encapsulate x) = x

这个想法是Encapsulated 可用于存储TypeClass a 的不同对象,然后在运行时提取特定类型。

我收到关于 x 类型与 Contract a 不匹配的错误。也许我需要指定某种类约束来告诉GHC Encapsulate x 中的x 类型与Contract a 中的a 相同?

T.hs:10:34:
    Couldn't match expected type ‘a’ with actual type ‘a1’
      ‘a1’ is a rigid type variable bound by
           a pattern with constructor
             Encapsulate :: forall a. Contract a => a -> Encapsulated,
           in an equation for ‘getTypedObject’
           at T.hs:10:17
      ‘a’ is a rigid type variable bound by
          the type signature for getTypedObject :: Encapsulated -> a
          at T.hs:9:19
    Relevant bindings include
      x :: a1 (bound at T.hs:10:29)
      getTypedObject :: Encapsulated -> a (bound at T.hs:10:1)
    In the expression: x
    In an equation for ‘getTypedObject’:
        getTypedObject (Encapsulate x) = x

我正在尝试这种方法,因为我有不同类型的 JSON 对象,并且根据在运行时通过网络解码的类型,我们希望从 Map 检索适当的特定类型 builder(已加载在运行时 IO 从配置文件中main,并传递给函数)并将其传递给解码的相同类型的 JSON 数据。

Dynamic 库可以在这里工作。但是,我有兴趣了解是否有其他可能的方法,例如 GADTsdatafamilies

【问题讨论】:

  • 也许值得注意的是,在这种情况下,您甚至不需要 GADT;让ExistentialQuantificationdata Encapsulated = forall a. Show a => Encapsulate a 之类的东西就足够了
  • 对于许多(大多数?)目的,几乎可以肯定的是,Dynamic 在概念上是多余的,Typeable 约束就足够了。 data Box where Box :: Typeable a => a -> Box。然后你可以使用从Data.TypeableMaybe 的函数来获取一个开箱即用的值。
  • 在 GHC 8.2 中,Typeable 预计将变得更加强大,超过包含 Dynamic 当前提供的功能。

标签: haskell gadt


【解决方案1】:

您的问题是您再次将a 推出(这不起作用)- 您可以做的是在内部使用合同,如下所示:

useEncapsulateContract :: Encapsulated -> String
useEncapsulateContract (Encapsulate x) = toString x

基本上,编译器会告诉你你需要知道的一切:在你里面有一个forall a. Contract a(所以基本上一个约束aContract

getTypedObject :: Encapsulated -> a 上,你没有这个约束——你是在告诉编译器:“看看这对我要求的每个a 都有效”

要实现它,您必须将 Encapsulated 参数化为 Encapsulated a,这显然是您不想要的。

第二个版本(我给出的内部版本)有效,因为您对数据构造函数有约束,因此您可以在那里使用它


稍微扩展一下:

这个

getTypedObject :: Contract a => Encapsulated -> a
getTypedObject (Encapsulate x) = x

也不会像现在那样工作,你会拥有Contract a,但它仍然可能是两个不同的类型,它们只是共享这个类。

为了向编译器提示两者应该相同,您必须再次参数化 Encapsulate ....

现在就这样做:

Encapsulate :: Contract a => a -> Encapsulated

你删除了这些信息

【讨论】:

  • 是的,我也有同样的想法,并且很好奇是否有任何方法可以使用 GADTs 保留这些信息,同时以相同的方式强制 Map 的类型是同质的(所以,GADTs 函数有检查类型)。 Dynamic 似乎是正确的方法。
【解决方案2】:

@Carsten 的回答显然是正确的,但我之前的两分钱帮助我理解了这一点。

当你写作时:

getTypedObject :: Encapsulated -> a

你“说”的是:

getTypedObject 是一个可以获取 Encapsulated 类型值的函数,并且可以在需要任何类型时使用其结果。

您显然无法满足这一点,编译器也不允许您尝试。您只能使用关于Encapsulated 内部值的知识来基于Contract 带出一些有意义的东西。换句话说,如果Contract 不存在,您将无法使用该值做任何有意义的事情。

这里的概念可以简洁地描述为类型擦除,并且在其他语言中也存在,C++ 是我所知道的一种。因此,价值在于删除关于类型的所有信息除了你想通过他们满足的合同保留的东西。缺点是恢复原始类型需要运行时检查。


另外,动态方法的工作原理如下:

{-# LANGUAGE GADTs #-}

import Unsafe.Coerce

data Encapsulated where
   Encapsulate :: Show a => a -> Encapsulated

getTypedObject :: Encapsulated -> a
getTypedObject (Encapsulate x) = unsafeCoerce x

printString :: String -> IO ()
printString = print

x = Encapsulate "xyz"
y = getTypedObject x

main = printString y

但是很容易看出它是如何破坏的,对吧? :)

【讨论】:

  • 是的,确实很容易破解。我正在考虑将类型 a 限制为类型类 Contract a 的类型。尽管如此,我们确实需要检查类型以确保它是正确的。所以,Dynamic 对我来说似乎是正确的做法。
猜你喜欢
  • 2023-03-20
  • 1970-01-01
  • 2014-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-08-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多