I tend to do a lot of data-oriented programming, including at the meta-programming level, which blends with the problem of configuration. A pattern I found that works well this use case and covers the whole clock is the following:
- DSL as functions T, args* -> T, where T is your configuration type — or, as I like to call it, a plan. Since under function composition it's insensitive to composition order (associativity of the monoid), you can layer DSL functions on top of each other freely.
- Literal values as implicit functions. Once the plan is built and before it is run/compiled/interpreted, I cast any literal value in the datatype to a function returning that value. It's a design principle that allows me to hard-code behavior when a literal value is not enough by just swapping it with a lambda.
- Once this kind of homogeneity is ensured, and given the points above, I can extend my DSL and the behavior it describes with point-free function combinators. I get conditionals, advanced composition (parallelism for instance) , instrumentation (debugging), etc... without burdening my DSL with ad-hoc, invasive implementations. More importantly I can reuse these facilities across DSLs.
- DSL as functions T, args* -> T, where T is your configuration type — or, as I like to call it, a plan. Since under function composition it's insensitive to composition order (associativity of the monoid), you can layer DSL functions on top of each other freely.
- Literal values as implicit functions. Once the plan is built and before it is run/compiled/interpreted, I cast any literal value in the datatype to a function returning that value. It's a design principle that allows me to hard-code behavior when a literal value is not enough by just swapping it with a lambda.
- Once this kind of homogeneity is ensured, and given the points above, I can extend my DSL and the behavior it describes with point-free function combinators. I get conditionals, advanced composition (parallelism for instance) , instrumentation (debugging), etc... without burdening my DSL with ad-hoc, invasive implementations. More importantly I can reuse these facilities across DSLs.