Posts tagged "optics":
I started writing a small tool for work that consumes YAML files and combines
the data into a single YAML file. To be specific it consumes YAML files
containing snippets of service specification for Docker Compose and it produces
a YAML file for use with
docker-compose. Besides being useful to me, I thought
it'd also be a good way to get some experience with lens.
The first transformation I wanted to write was one that puts in the correct image name. So, only slightly simplified, it is transforming
panda: x-image: panda goat: x-image: goat tapir: image: incorrent x-image: tapir
panda: image: panda:latest x-image: panda goat: image: goat:latest x-image: goat tapir: image: tapir:latest x-image: tapir
That is, it creates a new key/value pair in each object based on the value of
x-image in the same object.
The first approach I came up with was to traverse the sub-objects and apply a
function that adds the
setImage :: Value -> Value setImage y = y & members %~ setImg where setImg o = o & _Object . at "image" ?~ String (o ^. key "x-image" . _String <> ":latest")
It did make me wonder if this kind of problem, setting a value based on another value, isn't so common that there's a nicer solution to it. Perhaps coded up in a combinator that isn't mentioned in Optics By Example (or mabye I've forgot it was mentioned). That lead me to ask around a bit, which leads to approach two.
Arguably there isn't much difference, it's still traversing the sub-objects and
applying a function. The function makes use of
view being run in a monad and
ASetter being defined with
Identity (a monad).
setImage' :: Value -> Value setImage' y = y & members . _Object %~ (set (at "image") . (_Just . _String %~ (<> ":latest")) =<< view (at "x-image"))
I haven't made up my mind on whether I like this better than the first. It's disappointingly similar to the first one.
Then I it might be nice to split the fetching of
x-image values from the
image key/value pairs. By extracting with an index it's possible
to keep track of what sub-object each
x-image value comes from. Then two steps
can be combined using
setImage'' :: Value -> Value setImage'' y = foldl setOne y vals where vals = y ^@.. members <. key "x-image" . _String setOne y' (objKey, value) = y' & key objKey . _Object . at "image" ?~ String (value <> ":latest")
I'm not convinced though. I guess I'm still holding out for a brilliant combinator that fits my problem perfectly.
Please point me to "the perfect solution" if you have one, or if you just have some general tips on optics that would make my code clearer, or shorter, or more elegant, or maybe just more lens-y.