> 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. ![[nqthqn_procedural_generative_building_cubism_8087aeef-bd02-4865-8dcd-ee705925a26c.jpg]] ## Why? The builder pattern helps prevent misconfiguration of a data structure and offers a pipeline friendly interface. How does it accomplish this? - Hides internal module implementation using an opaque type - Exposes pipeline friendly `with` functions - Uses sane initial defaults ## Implementation ```elm -- 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 } ``` ## Usage ```elm x = init |> withF "custom" ``` or... ```elm x = init |> withG True |> withF "custom" ``` ## Use cases 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. ```elm card |> withTitle "Elm Trees" ``` ## Downsides 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.