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

时间:2015-04-16 19:40:44

标签: haskell types gadt existential-type

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

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?我

3 个答案:

答案 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,而不是使用存在类型。