Open upsiflu opened 8 months ago
In the following comments, I'll quote from John's article on dev.to and add my understanding underneath as my playing-around continues.
you can generalize and combine properties on record type aliases.
So the use case is: we have a set of dimensions modeled as sum types that constrain the color of an element.
Color
as a Product typeEach dimension of a Color
is a sum of its members: Mode = LightMode | DarkMode
Color is the product of its dimensions: (Background | Font) , (LightMode | DarkMode) , ...
You can model it as a Product type:
type Color = Color Target Mode
builder
This way, we can call a constructor with the convenient dot-accessor syntax in elm.
Plus, we give each constructor an unconstrained type builder
.
type alias AllTargets builder = { background : builder , font : builder }
builder
s per target, given a function that turns a Target
into a builder
type Target = Font | Background allTargets : (Target -> builder) -> AllTargets builder -- { background : builder, font : builder } allTargets toBuilder = { background = toBuilder Background , font = toBuilder Font }
type alias AllColors builder = AllTargets (AllModes (AllAccents (AllHues builder)))
Through alias substitution, any AllColors
instance will be a record { background, font }
where each field is itself a nested record of { mode₀..ₙ}
etc:
{ background:
{ lightMode :
{ accent₀ :
{ hue₀ : -- here we want the final color to be defined.
}
}
, darkMode : { ... }
}
, font :
{ lightMode : { ... }
, darkMode : { ... }
}
}
We can now compose dimensions to form those nested record types.
Color
is a constructor function that accepts values for each of its dimensions Target
, Mode
, etc.
AllColors Color
will be a record with all possible permutation of Color
.
We can simply assign the selected record field values to the constructor parameters like this:
colorBuilder = allTargets (\target -> allModes (\opacity -> allAccents (\accent -> allHues (\hue -> Color target mode accent hue ) ) ) )
Now that we have a builder for a "semantic" color, we can implement a single implementing function for a concrete value in CSS:
colorToAttr : Color -> Html.Attribute msg colorToAttr (Color target opacity accent hue) = [...]
type Button msg = Button { color : Color, onClick : Maybe msg, label : String }
Then we can implement create
, with.xy
or withXy
, view
as we do in any other builder-pattern module.
I'm so sorry - I don't have Github notifications "on" in any meaningful sense, and I just now saw this.
I'm so stoked that you found this interesting, and I hope that you find it useful - I've been playing with these myself, as time allows, and I hope to have more to share soon! ❤️
@wolfadex's elm-weekly newsletter brought this article to me, in which @jmpavlick showcases how a record parametrized over a builder-function can produce builder compositions in a stunningly beautiful fashion.
I want to document my process of understanding and reproducing this gem here.
Because I have a hunch that if can make the code of the
Ui
type (and perhaps also theLayout
type) much more self-explanatory. Plus, I had thought about adding builders in the Url codec as well as in the application module, and RTACs look like supercharged builders: a builder itself produces a record of chained builders.