我可以在类型构造函数中强制存在量化参数吗?
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
中放入您之前没有放入的另一种类型,您可能会破坏假定他们成功的类型的用户处理每个子案例。与代数类型不同,动态和强制转换意味着编译器无法帮助您证明详尽无遗。
我有一个数据类型,其(单个)构造函数包含一个存在量化类型变量:
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
中放入您之前没有放入的另一种类型,您可能会破坏假定他们成功的类型的用户处理每个子案例。与代数类型不同,动态和强制转换意味着编译器无法帮助您证明详尽无遗。