如何在 dhall 中键入异构列表?

问题描述

我正在使用 Dhall 为 github 操作生成 Yaml 文件。在 GH Action 中,您可以指定一个矩阵来生成多个案例(例如 Scala 版本和项目名称的组合)。但您也可以指定要排除的组合。

例如:

runs-on: ${{ matrix.os }}
strategy:
  matrix:
    os: [macos-latest,windows-latest,ubuntu-18.04]
    node: [8,10,12,14]
    exclude:
      # excludes node 8 on macOS
      - os: macos-latest
      - node: 8

一个有效的例子:

runs-on: ${{ matrix.os }}
strategy:
  matrix:
    jvm: [8,11]
    scala: [2.12.12,2.13.5,3.0.0]
    exclude:
      - jvm: 8
      - scala: 2.12.12

最后一个

runs-on: ${{ matrix.os }}
strategy:
  matrix:
    ruby: [2.7.3,3.0.0]

(见:https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#example-excluding-configurations-from-a-matrix

特别是对于 matrix,我们有多个字段是 List Text,但我们也有 exclude,我认为它是 ListRecord

如果我只是从头开始编写代码,而不定义类型,我可以在 Dhall 中编写此代码(而且它似乎使用我拥有的特定键生成 Record

但是,如果我想构建一个带有类型的库,或者更具体地说是扩展这个库,我无法弄清楚如何编写正确的类型。通常,请参阅此定义:

{ matrix : List { mapKey : Text,mapValue : List Text },fail-fast : Optional Bool,max-parallel : Optional Natural
}

https://github.com/regadas/github-actions-dhall/blob/master/types/Strategy.dhall

matrix 被定义为 List { mapKey : Text,mapValue : List Text },但 exclude 不是 List Text 所以它不起作用。而且我不知道如何更改此定义以接受 exclude

请注意,在示例中它是 osnode,但它可以是任意键和任意数量的键。

那么知道如何定义该类型吗?

编辑:添加更多示例

解决方法

看起来这需要更改上游 github-actions-dhall 存储库。具体来说,matrix 可能应该是 Optional 字段的记录,而不是 Map。根据您的示例,它可能至少具有以下字段:

{ os : Optional (List Text),node : Optional (List Natural),exclude : { os : Optional Text,node : Optional Natural }
}

有关更多指导,请参阅 Dhall 手册的这些章节,其中讨论了如何在绑定到配置格式时对 Dhall 类型进行建模:

编辑:将示例 YAML 配置转换为 Dhall 值或相应的 Dhall 类型的一种简单方法是使用 yaml-to-dhall。例如,如果你把你给我的三个例子放在一个 YAML 数组中(每个例子一个元素),像这样:

- runs-on: ${{ matrix.os }}
  strategy:
    matrix:
      os: [macos-latest,windows-latest,ubuntu-18.04]
      node: [8,10,12,14]
      exclude:
        # excludes node 8 on macOS
        - os: macos-latest
        - node: 8
- runs-on: ${{ matrix.os }}
  strategy:
    matrix:
      jvm: [8,11]
      scala: [2.12.12,2.13.5,3.0.0]
      exclude:
        - jvm: 8
        - scala: 2.12.12
- runs-on: ${{ matrix.os }}
  strategy:
    matrix:
      ruby: [2.7.3,3.0.0]

然后您可以询问 yaml-to-dhall 您可以使用哪种类型来统一这三个示例,如下所示:

$ yaml-to-dhall type < examples.yaml  
List
  { runs-on : Text,strategy :
      { matrix :
          { exclude :
              Optional
                ( List
                    { jvm : Optional Natural,node : Optional Natural,os : Optional Text,scala : Optional Text
                    }
                ),jvm : Optional (List Natural),os : Optional (List Text),ruby : Optional (List Text),scala : Optional (List Text)
          }
      }
  }

然后,如果您只是从推断类型中删除 List,那么对于您的配置来说,这是一个可以处理这三个示例的有效类型:

  { runs-on : Text,scala : Optional (List Text)
          }
      }
  }

... 和 yaml-to-dhall 还可以显示该类型对应的 Dhall 值是什么:

$ ✓ yaml-to-dhall < examples.yaml     
[ { runs-on = "\${{ matrix.os }}",strategy.matrix
    =
    { exclude = Some
      [ { jvm = None Natural,node = None Natural,os = Some "macos-latest",scala = None Text
        },{ jvm = None Natural,node = Some 8,os = None Text,scala = None Text }
      ],jvm = None (List Natural),node = Some [ 8,14 ],os = Some [ "macos-latest","windows-latest","ubuntu-18.04" ],ruby = None (List Text),scala = None (List Text)
    }
  },{ runs-on = "\${{ matrix.os }}",strategy.matrix
    =
    { exclude = Some
      [ { jvm = Some 8,scala = None Text },scala = Some "2.12.12"
        }
      ],jvm = Some [ 8,11 ],node = None (List Natural),os = None (List Text),scala = Some [ "2.12.12","2.13.5","3.0.0" ]
    }
  },strategy.matrix
    =
    { exclude =
        None
          ( List
              { jvm : Optional Natural,scala : Optional Text
              }
          ),ruby = Some [ "2.7.3","3.0.0" ],scala = None (List Text)
    }
  }
]

...虽然您可能希望使用 Dhall 对模式的支持来缩短它。上面有关默认处理的 Dhall 手册的链接有更多详细信息,但简短的总结是您可以将以下三个模式保存到名为 ./schemas.dhall 的文件中:

-- ./schemas.dhall

let Exclusion =
      { Type =
          { jvm : Optional Natural,scala : Optional Text
          },default =
        { jvm = None Natural,os = None Natural,scala = None Text
        }
      }

let Matrix =
      { Type =
          { exclude : Optional (List Exclusion.Type),scala : Optional (List Text)
          },default =
        { exclude = None (List Exclusion.Type),scala = None (List Text)
        }
      }

let Config =
      { Type = { runs-on : Text,strategy : { matrix : Matrix.Type } },default = {=}
      }

in  { Exclusion,Matrix,Config }

...然后重写结果以使用这些模式,如下所示:

$ yaml-to-dhall < examples.yaml | dhall rewrite-with-schemas --schemas ./schemas.dhall
let schemas = ./schemas.dhall

in  [ schemas.Config::{,runs-on = "\${{ matrix.os }}",strategy.matrix
        = schemas.Matrix::{,exclude = Some
          [ schemas.Exclusion::{ os = Some "macos-latest" },schemas.Exclusion::{ node = Some 8,os = None Text }
          ],"ubuntu-18.04" ]
        }
      },schemas.Config::{,exclude = Some
          [ schemas.Exclusion::{ jvm = Some 8,os = None Text },schemas.Exclusion::{ os = None Text,scala = Some "2.12.12" }
          ],"3.0.0" ]
        }
      },strategy.matrix = schemas.Matrix::{ ruby = Some [ "2.7.3","3.0.0" ] }
      }
    ]