Description
Currently, the types supported are string
, int
, bool
, fs
, option
, and pipeline
.
This proposal introduces composite types, allowing for []string
, []int
, []bool
, []fs
, []option
, and []pipeline
, as well as for loops
.
Proposal
- Statements in array blocks append to return register instead of modify.
- Arrays are splatted automatically.
For arrays to work well in practice, we want to ensure we have data flow analysis to return compile errors for dead code
, so that will be a prerequisite.
string regions() {
"us-east-1"
^^^^^^^^^^^
dead code
"us-west-2"
}
Here is a function returning an array. Instead of overriding the value of the return register, they are appended similar to declaring the elements of an array. (1. Statements in array blocks append to return register instead of modify)
[]string regions() {
"us-east-1"
"us-west-2"
}
You can invoke functions that also return the same type array and they are splatted automatically. (2. Arrays are splatted automatically)
[]string westRegions() {
"us-west-1"
"us-west-2"
}
[]string allRegions() {
"us-east-1"
westRegions
^^^^^^^^^^^
splatted
}
This is useful when combined with for loop
, here's the proposed structure.
for (<type> <ident> in <expr>) {
# statements affect register as usual
}
For example, when you need to publish an image to multiple registries:
fs publish() {
build
for (string region in regions) {
dockerPush "registry-${region}.com/my-image"
}
}
Since the for loop
expects an expression, you can also provide a block literal as an expression.
fs publish() {
build
for (string region in []string {
"us-east-1"
westRegions
}) {
dockerPush "registry-${region}.com/my-image"
}
}
Note you can still do this inline, but follows regular block syntax (i.e. separate statements by ;
).
fs publish() {
build
for (string region in []string { "us-east-1"; "us-west-2" }) {
^
odd compared to other languages
but consistent within HLB
dockerPush "registry-${region}.com/my-image"
}
}
Elements or slices of an array can be accessed through array indexing. For example, if we need to bind the digest of one of the registries we pushed to.
fs registryPush(fs ref) {
for (string region in regions[1:]) {
dockerPush "registry-${region}.com/${ref}"
}
dockerPush "registry-${region[0]}.com/${ref}" as registryDigest
}
You can also pass arrays to functions that expect variadic arguments because they are splatted automatically.
# Builtin `stage` function that runs pipelines in parallel.
pipeline stage(variadic pipeline pipelines)
[]fs tests() {
lint
unitTest
}
pipeline testAll() {
stage tests
}
This proposal also addresses the special handling of option blocks, where they behaved like arrays when arrays didn't exist. By changing with option
-> with []option
and function declarations for option
-> []option
in the linter.
fs npmInstall() {
image "node:alpine"
run "npm install" with []option {
^^^^^^^^
not special anymore
behaves as an array type
dir "/in"
mount manifest "/in"
mount scratch "/in/node_modules" as nodeModules
}
}
Suppose we have a builitn localWalk
, we can combine this with for loop
to produce a list of options for mounting decrypted secrets.
# Return filenames walking the directory at `localPath`.
[]string localWalk(string localPath)
[]option::run mountSecrets() {
for (string filename in localWalk("./decrypted")) {
secret "./decrypted/${filename}" "/secrets/${filename}"
}
}
Activity