我可以在类型构造函数中强制存在量化参数吗?

Can I coerce an existentially quantified argument in a type constructor?

我有一个数据类型,其(单个)构造函数包含一个存在量化类型变量:

data LogEvent = forall a . ToJSON a =>
            LogEvent { logTimestamp     :: Date
                     , logEventCategory :: Category
                     , logEventLevel    :: LogLevel
                     , logThreadId      :: ThreadId
                     , logPayload       :: a
                     }

当我最初编写该类型时,我隐藏了多态有效负载,因为当时我感兴趣的只是输出到某些 file/stream。但是现在我想做更多有趣的事情,为此我需要观察 a.

的实际类型

我从 this question 和其他阅读材料中了解到,存在量化类型变量在每次实例化时都是唯一的。但是,给定类型是 ToJSON a 我可以像下面这样(伪代码):

 let x :: Result Foo = fromJSON $ toJSON (logPayload event)

能够使用更精确的类型与 JSON 相互转换似乎很奇怪,尽管我可以理解其背后的基本原理。

那么,如果我知道它的类型,我该如何重写该类型以允许提取 logPayload?我

So how can I rewrite that type to allow to extract logPayload if I know its type?

如果你不想改变你的类型,你可以用unsafeCoerce替换fromJSON & toJSON——同样的想法,如果你是对的,结果是一样的,但如果你不对,可能会导致程序崩溃关于类型。

如果您希望类型检查器确保您是正确的,您将不得不在 LogEvent 中公开类型 a 而不是使用存在类型。

这类似于 existential typeclass(反)模式。这个存在的魔法相当于

data LogEvent = 
        LogEvent { logTimestamp     :: Date
                 , logEventCategory :: Category
                 , logEventLevel    :: LogLevel
                 , logThreadId      :: ThreadId
                 , logPayload       :: Aeson.Value
                 }

但这可以更清楚地传达您的结构所代表的含义。你不应该从你的存在结构中期望任何你不会从中期望的东西。

另一方面,如果您确实知道logPayload的类型,那么您应该通过移出类型变量在类型级别对该知识进行编码:

data LogEvent a = ...

此时 LogPayload Foo 类型的值代表您对负载类型的了解。然后,如果您愿意,可以定义

data ALogEvent = forall a. ToJSON a => ALogEvent (LogEvent a)

当你不知道的时候。在实践中,我很少看到这两者都存在的必要性,但也许你有它的用例。

如果您在 运行 时知道 logPayload 的类型,但由于某种原因无法在编译时跟踪有效负载,也许您可​​以添加一个 Typeable a 约束到您的existential 这样你就可以在不求助于 unsafeCoerce 的情况下进行转换......如果你犯了一个错误,你就不会奇怪地破坏你的整个程序。

您可以考虑 Data.Typeable 试一试;将 Typeable a 约束放入你的存在类型中,然后如果你能正确猜出隐藏类型,你就可以在该类型下取回值。有关玩具示例,请参阅 this Gist

请注意,此技术牺牲了一定程度的类型安全性——如果您开始在 LogEvent 中放入您之前没有放入的另一种类型,您可能会破坏假定他们成功的类型的用户处理每个子案例。与代数类型不同,动态和强制转换意味着编译器无法帮助您证明详尽无遗。