Pattern: Higher-Order Config¶
The problem¶
Sometimes you do not want the config to build the final object directly.
You want it to build a configured callable:
- a loss function with stored kwargs
- a transform
- a callback factory
- a request wrapper
- a reusable stage that another pipeline can call later
That is one level more abstract than an ordinary template.
The pattern¶
Use !fn to return another callable, usually a !fn:path partial.
!define make_greeter: !fn
!require greeting: "greeting"
!fn : !fn:greet
greeting: ${greeting}
casual: !make_greeter { greeting: hey }
formal: !make_greeter { greeting: hello }
If greet is a Python callable in the loader context, then casual and formal become configured DraconPartial objects.
From Python:
Why this is interesting¶
This is config producing executable configured objects, not just plain data.
The first !fn is a template that runs during composition. The value it returns is another callable object that can be stored, serialized, passed around, and invoked later.
That is why this deserves its own pattern name. It is higher-order in the same sense that a Python function returning another function is higher-order.
Why use this instead of a Python factory¶
Because the factory itself becomes configurable.
You can:
- select which configured callable to build
- sweep over families of configured callables
- keep the configuration of those callables in YAML
- dump and inspect the resulting partials
This is especially useful when the "thing you want to build later" is still part of the experiment or app configuration.
Common cases¶
- configured loss functions
- data transforms
- notification or logging callbacks
- reusable service clients
- preconfigured stages for
!pipe
A practical guideline¶
If a config subtree should eventually be called like a function, consider whether the right output is a !fn:path partial rather than a fully constructed object.
That usually keeps the boundary cleaner.