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 `Ord s`.

Motivation

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 -> Stringprint = case _ of  A -> "a"  B -> "b"  C -> "c"`
`parse :: String -> Maybe MyTypeparse = 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.

Solution

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 aall = oneOf (upFromIncluding bottom)`

Once we have `all` values of a type we can apply `print` to each and every one of them and use results as a key for some lookup dictionary:

`mkDict ∷ ∀ s a. Ord s ⇒ BoundedEnum a ⇒ (a → s) → Map s amkDict print = fromFoldable \$ all <#> \x → print x `Tuple` x`

Now we can now write a function which takes `print` and gives back `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`

Don’t miss the next article 🦄