Irakli Safareli

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 -> 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.

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 a
all = Data.NonEmpty.oneOf (Data.Enum.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 a
mkDict print = Data.Map.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 -> Data.Map.lookup s dict