如何在 Dhall 中将动态命名的记录与静态记录合并?

How to merge a dynamically named record with a static one in Dhall?

我正在 Dhall 中创建 AWS Step Function 定义。但是,我不知道如何创建它们用于 Choice 状态的通用结构,例如下面的示例:

{
    "Not": {
        "Variable": "$.type",
        "StringEquals": "Private"
    },
    "Next": "Public"
}

Not 使用 mapKeymapValue 非常简单。如果我定义一个基本的比较:

{ Type =
    { Variable : Text
    , StringEquals : Optional Text
    }
, default = 
    { Variable = "foo" 
    , StringEquals = None Text
    }
}

以及类型:

let ComparisonType = < And | Or | Not >

并添加辅助函数以将 mapKey 的类型呈现为 Text:

let renderComparisonType = \(comparisonType : ComparisonType )
    -> merge
    { And = "And"
    , Or = "Or"
    , Not = "Not"
    }
    comparisonType

然后我可以在函数中使用它们来中途生成记录:

let renderRuleComparisons = 
  \( comparisonType : ComparisonType ) ->
  \( comparisons : List ComparisonOperator.Type ) ->
    let keyName = renderComparisonType comparisonType
    let compare = [ { mapKey = keyName, mapValue = comparisons } ]
    in compare

如果我 运行 使用:

let rando = ComparisonOperator::{ Variable = "$.name", StringEquals = Some "Cow" }
let comparisons = renderRuleComparisons ComparisonType.Not [ rando ]
in comparisons

使用dhall-to-json,她将输出第一部分:

{
    "Not": {
        "Variable": "$.name",
        "StringEquals": "Cow"
    }
}

...但我一直在努力将其与 "Next": "Sup" 合并。我已经使用了所有记录合并,如 /\// 等,但它一直给我各种我还不真正理解的类型错误。

首先,我将包括一种不进行类型检查的方法作为激发解决方案的起点:

let rando = ComparisonOperator::{ Variable = "$.name", StringEquals = Some "Cow" }

let comparisons = renderRuleComparisons ComparisonType.Not [ rando ]

in  comparisons # toMap { Next = "Public" }

toMap是将记录转换成键值列表的关键字,#是列表拼接运算符。 Dhall CheatSheet 提供了一些如何使用它们的示例。

上述解决方案不起作用,因为# 无法合并具有不同元素类型的列表。 # 运算符的左侧具有以下类型:

comparisons : List { mapKey : Text, mapValue : Comparison.Type }

... 而 # 运算符的右侧具有此类型:

toMap { Next = "Public" } : List { mapKey : Text, mapValue : Text }

... 所以两个 List 不能按原样合并,因为 mapValue 字段的类型不同。

有两种方法可以解决这个问题:

  • 方法 1:只要存在类型冲突就使用联合
  • 方法 2:使用可以容纳任意值的弱类型 JSON 表示

方法 1 是这个特定示例的更简单的解决方案,方法 2 是更通用的解决方案,可以处理非常奇怪的 JSON 模式。

对于方法 1,dhall-to-json 将在转换为 JSON 时自动剥离非空联合构造函数(留下它们包装的值)。这意味着您可以转换 # 运算符的两个参数以就此通用类型达成一致:

List { mapKey : Text, mapValue : < State : Text | Comparison : Comparison.Type > }

...然后您应该能够连接两个键值对列表,dhall-to-json 将正确呈现它们。

还有第二种处理弱类型 JSON 模式的解决方案,您可以在此处了解更多信息:

基本思想是所有 JSON/YAML 集成都识别并支持弱类型 JSON 表示,可以容纳任意 JSON 数据,包括具有不同形状键的字典(就像你的例子)。您甚至不需要将整个表达式转换为这种弱类型表示;您只需要将此表示用于您 运行 遇到架构问题的配置子集。

这对您的示例意味着,您将更改 # 运算符的两个参数以具有此类型:

let Prelude = https://prelude.dhall-lang.org/v12.0.0/package.dhall

in  List { mapKey : Text, mapValue : Prelude.JSON.Type }

documentation for Prelude.JSON.Type 也有更多关于如何使用此类型的详细信息。