I'm collecting a handful of useful patterns mostly for my own reference when building safe and maintainable software. I also have a note on the spooky Phantom Type pattern.
The builder pattern helps prevent misconfiguration of a data structure and offers a pipeline friendly interface. How does it accomplish this?
with
functions-- opaque
type X = X C
-- hidden internal configuration
type alias C = { f : String, g : Bool }
-- sane configuration defaults
init : X
init =
X { f = "default", g = False }
-- create variants
withF : String -> X -> X
withF f (X c) =
X { c | f = f }
withG : Bool -> X -> X
withG g (X c) =
X { c | g = g }
x = init |> withF "custom"
or...
x =
init
|> withG True
|> withF "custom"
This pattern is great for design systems. A view can have a nice default and as business needs change and pain points emerge variants can be easily introduced and internal configuration can be updated smoothly.
card |> withTitle "Elm Trees"
This can yield lots of boilerplate for larger configurations. A with
function is needed for updating each part of the configuration — it is intentionally not exposed by the module.
Who decides the sanity or sensibility for the defaults? You might run into a case where everyplace the module is used it has a with function tagging along. Maybe a great reason to refactor.
And of course — there is always a tradeoff between flexibility and rigidity when it comes to selecting a pattern that suites your need.