我有一个数据类型,其(单个)构造函数包含一个存在量化的类型变量:
data LogEvent = forall a . ToJSON a =>
LogEvent { logTimestamp :: Date
, logEventCategory :: Category
, logEventLevel :: LogLevel
, logThreadId :: ThreadId
, logPayload :: a
}
当我最初编写该类型时,我隐藏了多态有效负载,因为我当时感兴趣的是输出到某个文件/流。但现在我想做更多有趣的事情,我需要观察a
的实际类型。
我从this question和其他读物中了解到,存在量化的类型变量在每个实例化时都是唯一的。但是,给定的类型是ToJSON a
,我可以像下面这样(伪代码):
let x :: Result Foo = fromJSON $ toJSON (logPayload event)
能够以更精确的类型转换为JSON和从JSON转换似乎很奇怪,尽管我可以理解其背后的基本原理。
那么如果我知道它的类型,如何重写该类型以允许提取logPayload
?我
答案 0 :(得分:9)
这类似于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
约束,以便您可以执行没有诉诸unsafeCoerce
...如果你犯了一个错误,你就不会完全破坏整个程序。
答案 1 :(得分:2)
你可以考虑给Data.Typeable
一个机会;在您的存在类型中加入Typeable a
约束,然后如果您能正确猜出隐藏类型,则可以在该类型下获取值。有关玩具示例,请参阅this Gist。
请注意,这种技术会牺牲一些类型的安全性 - 如果你开始在LogEvent
内部放入另一种类型,而在你可能会破坏那些假设他们的类型的用户之前你没有这种类型。重新成功处理每个子案例。与代数类型不同,动态和强制转换意味着编译器无法帮助您证明详尽无遗。
答案 2 :(得分:1)
那么如果我知道它的类型,如何重写该类型以允许提取logPayload?
如果您不想更改类型,可以将fromJSON & toJSON
替换为unsafeCoerce
- 相同的想法,如果您是对的,结果相同,但如果您不对,可能会导致程序崩溃类型。
如果您希望类型检查器确保您是正确的,则必须在a
中公开类型LogEvent
,而不是使用存在类型。