Derive parse from print with reasonable constraints
or how to invert some functions
I will introduce simple motivation behind this whole story and shortly we will know how to derive
parse :: s → Maybe a from
print :: a → s, with reasonable constraints —
BoundedEnum a and
Type system is a friend who’s got your back by making sure all inputs are encountered when writing functions. Usually
print :: a → s takes some type which is smaller (has smaller number of values) than its resulting type. But when writing
parse :: s -> Maybe a our friend can’t cover us anymore, as input type is bigger and we need to use
_ → in our case expression (it might be impossible to write down all the cases, take String for example).
data MyType = A | B | C
print :: MyType -> String
print = case _ of
A -> "a"
B -> "b"
C -> "c"
parse :: String -> Maybe MyType
parse = case _ of
"a" -> Just A
"b" -> Just B
"c" -> Just C
_ -> Nothing
Here we also have a decent amount of repetition. Also this code can get out of sync in many ways like incorrect change during merge, typo, etc.
If you are unfamiliar with BoundedEnum you should definitely check it out. How it helps us is that it gives us a way to generate all values of a type implementing this type class:
all ∷ ∀ a. BoundedEnum a ⇒ Array a
all = oneOf (upFromIncluding bottom)
Once we have
all values of a type we can apply
mkDict ∷ ∀ s a. Ord s ⇒ BoundedEnum a ⇒ (a → s) → Map s a
mkDict print = fromFoldable $ all <#> \x → print x `Tuple` x
Now we can now write a function which takes
parse (note we use let binding and lambda in the end, so that dictionary is generated only once)
mkParse ∷ ∀ s a. Ord s ⇒ BoundedEnum a ⇒ (a → s) → (s → Maybe a)
mkParse print = let dict = mkDict print in \s -> lookup s dict