Haskell 中的异构 Data.Map

Heterogeneous Data.Map in Haskell

是否可以在 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 不匹配的错误。也许我需要指定某种 class 约束来告诉 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(在 运行 时从配置文件中加载 main 的 IO,并传递给函数)并将其解码的 JSON 数据传递给它同一类型。

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

你的问题是你再次将 a 推出(这将不起作用) - 你可以做的是在内部使用 合同 像这样:

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

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

getTypedObject :: Encapsulated -> a 你没有这个约束 - 你告诉编译器:"look this works for every a I'll demand"

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

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


稍微扩展一下:

这个

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

也不会工作,因为现在你会有 Contract a,但它仍然可以是两种 不同类型 ,它们只是共享这个 class。

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

现在就这样做:

Encapsulate :: Contract a => a -> Encapsulated

您删除该信息

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

当你写:

getTypedObject :: Encapsulated -> a

你是"saying"是:

getTypedObject is a function that can take a value of the Encapsulated type and its result can be used whenever any type whatsoever is needed.

你显然不能满足,而且编译器不允许你尝试。你只能利用关于Encapsulated里面的值的知识,在Contract的基础上带出一些有意义的东西。换句话说,如果 Contract 不存在,您将无法对该值做任何有意义的事情。

这里的概念可以简洁地描述为type erasure并且也存在于其他语言中,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

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