【问题标题】:Can I coerce an existentially quantified argument in a type constructor?我可以在类型构造函数中强制存在量化参数吗?
【发布时间】:2015-04-16 19:40:43
【问题描述】:

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

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 相互转换似乎很奇怪,尽管我可以理解其背后的基本原理。

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

【问题讨论】:

    标签: haskell types gadt existential-type


    【解决方案1】:

    这类似于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 约束,这样您就可以在不诉诸 @ 的情况下进行转换987654329@... 万一你犯了错误,你不会奇怪地破坏你的整个程序。

    【讨论】:

    • 我在这里使用了一个存在包装器,因为事件被传递到 Chan LogEvent 以异步记录它们:一个单独的线程读取事件并处理它。我当然最初尝试使用其有效负载类型参数化LogEvent 类型,但后来我没有得到任何东西,因为通道另一端的消费者仍然无法观察到事件的类型。
    • 如果消费者需要观察事件的类型,我会推荐代数类型。也就是说,除非它统一处理所有事件,除非一两个特殊情况,在这种情况下,Typeable 可能是正确的
    • 是的,但这意味着将 LogEvent 类型与记录的事件联系起来,这是我想避免的,但显然不能再推迟了 :-) 这不是库代码,所以我不太在意了,尽管这意味着使日志模块依赖于系统的每种记录类型,这有点糟糕......
    • @insitu,如果记录器需要观察事件类型,记录器已经有这个依赖不是吗?
    • 当然 :-) 但不是所有记录的类型。对此您可能会回答:为您需要观察的那些类型提供类型化构造函数,您可能是对的!让我们试试……
    【解决方案2】:

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

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

    【讨论】:

    • 按照 luqui 的评论,我已经开始走这条路,但后来碰壁了:无法 eta-reduce 到表单实例的实例 (...) => 可键入命令在数据实例声明中“命令” 我要记录的类型实际上是在类型类中定义的类型族的实例,似乎DeriveDataTypeable 对此感到窒息。或者我可能会误解编译器错误
    【解决方案3】:

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

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

    如果您希望类型检查器确保您是正确的,则必须在 LogEvent 中公开类型 a 并且不使用存在类型。

    【讨论】:

      猜你喜欢
      • 2011-05-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-06-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多