YS Currying in YAML Configs
In functional programming, currying is a technique where a function that takes multiple arguments is called with fewer arguments than it takes and returns a function that takes the remaining arguments.
In Clojure and YS, the partial
function is used to create a curried function.
Here's an example:
$ ys -e '
multiply-by-6 =: partial(mul 6)
multiply-by-7 =: mul.partial(7)
say: multiply-by-6(7)
say: multiply-by-7(6)
'
42
42
That's neat but how is it useful in YAML configs?
Curried merge
🔗
Imagine you have a big YAML config with a lot of mappings that often have the same data or almost the same data.
It would be nice if there was a simple way to use defaults, and just specify the overrides.
Here's part of an example YAML config I just found on the internet:
# From https://circleci.com/docs/sample-config/
jobs:
build:
docker:
- image: cimg/base:2023.03
steps:
- checkout
- run: echo "this is the build job"
test:
docker:
- image: cimg/base:2023.03
steps:
- checkout
- run: echo "this is the test job"
Let's refactor this with YS:
!YS-v0
defaults =::
docker:
docker:
- image: cimg/base:2023.03
--- !data
jobs:
build::
merge defaults.docker::
steps:
- checkout
- run: echo "this is the build job"
test::
merge defaults.docker::
steps:
- checkout
- run: echo "this is the test job"
Let's load this with ys
:
$ ys -Y config.yaml
jobs:
build:
docker:
- image: cimg/base:2023.03
steps:
- checkout
- run: echo "this is the build job"
test:
docker:
- image: cimg/base:2023.03
steps:
- checkout
- run: echo "this is the test job"
It works, but it's not very nice looking...
Yet!
Let's put the defaults in a separate file, and curry the defaults into the
merge
function.
# defaults.yaml
docker:
docker:
- image: cimg/base:2023.03
!YS-v0:
defaults =: load('defaults.yaml')
docker-defaults =: merge.partial(defaults.docker)
jobs:
build: !:docker-defaults
steps:
- checkout
- run: echo "this is the build job"
test: !:docker-defaults
steps:
- checkout
- run: echo "this is the test job"
And load:
$ ys -Y config.yaml
jobs:
build:
docker:
- image: cimg/base:2023.03
steps:
- checkout
- run: echo "this is the build job"
test:
docker:
- image: cimg/base:2023.03
steps:
- checkout
- run: echo "this is the test job"
It works, and it's a lot cleaner this time.
We used tag function calls to our curried merge
function.
If this was a real world config, it would have been much bigger.
We could have put lots of defaults in the defaults.yaml
file.
Then we could make functions to apply the defaults wherever we wanted to.
I hope you can see how powerful a refactoring strategy like this can be!