Block Manipulation
In this chapter you will learn:
- How to merge blocks with catenation and
merge - How to inspect, transform, and restructure blocks
- Key prelude functions for working with blocks
- Patterns for building and modifying configuration data
Block Merge by Catenation
When two blocks appear next to each other, the result is a shallow merge. Later values override earlier ones:
eu -e '{ a: 1 } { b: 2 }'
a: 1
b: 2
eu -e '{ a: 1 } { a: 2 }'
a: 2
This is the same as calling the merge function:
eu -e 'merge({ a: 1 }, { b: 2 })'
a: 1
b: 2
Deep Merge
Use deep-merge or the << operator for recursive merging of nested
blocks:
base: { server: { host: "localhost" port: 8080 } }
override: { server: { port: 9090 debug: true } }
config: base << override
base:
server:
host: localhost
port: 8080
override:
server:
port: 9090
debug: true
config:
server:
host: localhost
port: 9090
debug: true
Note that << merges nested blocks but replaces lists entirely.
Inspecting Blocks
elements
Break a block into its list of key-value pairs:
eu -e '{ a: 1 b: 2 } elements'
- - a
- 1
- - b
- 2
Each element is a two-element list: [key, value].
keys and values
eu -e '{ a: 1 b: 2 c: 3 } keys'
- a
- b
- c
eu -e '{ a: 1 b: 2 c: 3 } values'
- 1
- 2
- 3
has
Check whether a block contains a key:
eu -e '{ a: 1 b: 2 } has(:a)'
true
lookup and lookup-or
Look up a value by symbol key:
eu -e '{ a: 1 b: 2 } lookup(:b)'
2
With a default for missing keys:
eu -e '{ a: 1 } lookup-or(:z, 99)'
99
Reconstructing Blocks
block
Build a block from a list of key-value pairs:
eu -e '[[:a, 1], [:b, 2], [:c, 3]] block'
a: 1
b: 2
c: 3
zip-kv
Build a block from separate key and value lists:
eu -e 'zip-kv([:x, :y, :z], [1, 2, 3])'
x: 1
y: 2
z: 3
merge-all
Merge a list of blocks into one:
eu -e '[{a: 1}, {b: 2}, {c: 3}] merge-all'
a: 1
b: 2
c: 3
Transforming Blocks
map-values
Apply a function to every value, keeping keys:
eu -e '{ a: 1 b: 2 c: 3 } map-values(* 10)'
a: 10
b: 20
c: 30
map-keys
Transform the keys of a block:
eu -e '{ a: 1 b: 2 } map-keys(sym ∘ str.prefix("x-") ∘ str.of)'
x-a: 1
x-b: 2
filter-values
Return the list of values that satisfy a predicate (note: this returns a list, not a block):
eu -e '{ a: 1 b: 20 c: 3 d: 40 } filter-values(> 10)'
- 20
- 40
To keep matching entries as a block, use filter-items with
by-value:
eu -e '{ a: 1 b: 20 c: 3 d: 40 } filter-items(by-value(> 10)) block'
b: 20
d: 40
map-kv
Apply a function to each key-value pair. The function receives two
separate arguments (key, value) (it uses uncurry internally),
and returns a transformed result:
eu -e '{ a: 1 b: 2 } map-kv("{}: {}")'
- 'a: 1'
- 'b: 2'
To produce a new block, combine with block:
eu -e '{ a: 1 b: 2 } map-kv(pair) block'
a: 1
b: 2
Modifying Individual Values
alter-value
Replace the value at a specific key:
config: { host: "localhost" port: 8080 }
updated: config alter-value(:port, 9090)
config:
host: localhost
port: 8080
updated:
host: localhost
port: 9090
update-value
Apply a function to the value at a specific key:
counters: { hits: 10 errors: 3 }
result: counters update-value(:hits, inc)
counters:
hits: 10
errors: 3
result:
hits: 11
errors: 3
set-value
Set a value, creating the key if it does not exist:
eu -e '{} set-value(:x, 42)'
x: 42
alter and update (nested)
Modify values deep in nested blocks using a key path:
config: { server: { db: { port: 5432 } } }
changed: config alter([:server, :db, :port], 3306)
bumped: config update([:server, :db, :port], inc)
config:
server:
db:
port: 5432
changed:
server:
db:
port: 3306
bumped:
server:
db:
port: 5433
merge-at
Merge additional keys into a nested block:
config: { server: { db: { port: 5432 } } }
extended: config merge-at([:server, :db], { host: "10.0.0.1" })
config:
server:
db:
port: 5432
extended:
server:
db:
port: 5432
host: 10.0.0.1
Patterns: Configuration Templating
A common pattern is to define a base configuration and layer environment-specific overrides on top:
base: {
server: {
host: "0.0.0.0"
port: 8080
workers: 4
}
logging: {
level: "info"
format: "json"
}
}
production: base << {
server: { workers: 16 }
logging: { level: "warn" }
}
development: base << {
server: { host: "localhost" }
logging: { level: "debug" format: "text" }
}
Key Concepts
- Block catenation merges two blocks; later keys override earlier ones
- Deep merge (
<<) recursively merges nested blocks elements,keys,valuesdecompose blocks;blockandzip-kvreconstruct themmap-values,map-keys,filter-valuestransform blocksalter-value,update-value,set-valuemodify individual entriesalter,update,merge-atmodify deeply nested values