Hey everyone! I’m looking for a way to encode the concept of ‘default’ in datatypes, which can be resolved at runtime (as opposed to e.g. the Default
typeclass, which is static).
Here’s a contrived example. I want to describe a desktop theme:
data Theme = Theme { backgroundColor :: Color
, edgeColor :: Color
, transparency :: Double
}
There’s a default theme:
defaultTheme :: Theme
defaultTheme = ...
Now I want to modify this theme with a different background color. Easy:
myTheme :: Theme
myTheme = defaultTheme {backgroundColor = ...}
That’s all well and good if the default theme is static. What if the default theme could be dynamically set? I would like to describe myTheme
by showing what do I want to keep from the default theme (known at runtime), and what I want to override. Can I encode this at the type-level?
Here’s my idea:
-- | Marks that data can be inherited from the runtime default
data Inherit a = Default
| Override a
-- | Marks that the data is fully resolved and ready to be acted upon
data Resolved a = Resolved { resolved :: a }
data Theme m = Theme { backgroundColor :: m Color
, edgeColor :: m Color
, transparency :: m Double
}
myTheme :: Theme Inherit
myTheme = Theme { backgroundColor = Override "black"
-- Use the runtime default values for the following fields
, edgeColor = Default
, transparency = Default
}
-- | Resolve theme at runtime
resolve :: Theme Resolved -- ^ Runtime default theme
-> Theme Inherit -- ^ My modified theme
-> Theme Resolved -- ^ The combined
In practice, I’m going to be looking at multiple data types like Theme
which can go from a Inherit
to a Resolved
, so I would build a typeclass that formalizes this relationship:
class Resolvable a where
resolve :: a Resolved -> a Inherit -> a Resolved
This feels a bit clunky. Is there a better way to do something like this?