【发布时间】:2022-12-02 21:38:32
【问题描述】:
标签: functional-programming variant purescript
标签: functional-programming variant purescript
变体是记录的对偶。虽然记录是一种可扩展的临时产品类型(考虑 data T = T Int String 与 type T = { i :: Int, s :: String }),但变体可以看作是可扩展的临时总和类型 - 例如data T = A Int | B String 与 Variant (a :: Int, b :: String)
例如,就像您可以编写处理部分记录的函数一样:
fullName :: forall r. { first :: String, last :: String | r } -> String
fullName r = r.first <> " " <> r.last
myFullName = fullName { first: "Fyodor", last: "Soikin", weight: "Too much" }
同样,您可以编写一个处理部分变体的函数:
weight :: forall r. Variant (kilos :: Int, vague :: String | r) -> String
weight =
default "Unknown"
# on _kilos (
-> show n <> " kg.")
# on _vague (s -> "Kind of a " <> s)
myWeight = weight (inj _kilos 100) -- "100 kg."
alsoMyWeight = weight (inj _vague "buttload") -- "Kind of a buttload"
但这些当然是玩具示例。对于一个不那么玩具的例子,我会想象一些处理替代品的东西,但需要是可扩展的。也许类似于文件解析器:
data FileType a = Json | Xml
basicParser :: forall a. FileType a -> String -> Maybe a
basicParser t contents = case t of
Json -> parseJson contents
Xml -> parseXml contents
假设在大多数情况下我可以使用这个解析器,但在某些情况下我也希望能够解析 YAML。我该怎么办?我不能“扩展”FileType sum 类型事后,我能做的最好的就是将它聚合成一个更大的类型:
data BetterFileType a = BasicType (FileType a) | Yaml
betterParser :: forall a. BetterFileType a -> String -> Maybe a
betterParser t contents = case t of
BasicType bt -> basicParser bt contents
Yaml -> parseYaml contents
现在每当我调用“更好的解析器”时,我都必须笨拙地包装文件类型:
result = betterParser (BasicType Json) "[1,2,3]"
更糟的是:现在每个消费者都必须知道BetterFileType -> FileType 的层次结构,他们不能只说“json”,他们必须知道将其包装在BasicType 中。尴尬的。
但是如果我为文件类型使用可扩展的变体,我可以很好地展平它们:
type FileType r = (json :: String, xml :: String | r)
basicParser :: forall a r. Variant (FileType r) -> Maybe a
basicParser = onMatch { json: parseJson, xml: parseXml } $ default Nothing
----
type BetterFileType r = (yaml :: String | FileType r)
betterParser :: forall a r. Variant (BetterFileType r) -> Maybe a
betterParser = onMatch { yaml: parseYaml } basicParser
现在我可以将裸变体名称与 basicParser 或 betterParser 一起使用,而不知道是否包装它们:
r1 = betterParser $ inj _json "[1,2,3]"
r2 = betterParser $ inj _yaml "foo: [1,2,3]"
【讨论】: