Transmogrify your data!

Ivan Lazar Miljenovic

25 January, 2017

Haskellers, we have a problem!

We think our language is all nice and theoretical…

  • “Look at me, I use category theory!”
  • “This is all based off of research, not just baseless pragmatism!”

We love our types!

data Foo = Foo Int Double Bool

-- See? So much clearer and safer and stuff!

But this can lead to a problem…

“I have a Foo… but this function requires a Bar which is identical to a Foo just with a different name…

… because we forgot some theory!

In type theory a product type of two types A and B is the type whose terms are ordered pairs (a,b) with a:A and b:B.

Data definitions complicate things!

The Solution

If people continue to complicate matters by writing new types everywhere…

We’ll just have to strip all that metadata out!

You can’t just say “Simples”…

GHC.Generics

A brief recap

Everything is either:

  • K1 – An individual field/constructor
  • V1 – Empty datatypes
  • U1 – A constructor without fields
  • :*: – Product types
  • :+: – Sum types
  • M1 – Pesky, pesky metadata…

So, what do we have to do?

  1. Get everything to derive Generic
  2. Convert it into it’s generic representation
  3. Recursively remove any M1 wrappers present
  4. Add in the M1 wrappers that the other type wants
  5. We have achieved…

TRANSMOGRIFI-CATION!!!

Example

λ> data Foo a c = Foo a Int c
     deriving (Show, Generic)
λ> data Bar b = Bar { a :: Word
                    , b :: b
                    , c :: Char
                    }
     deriving (Show, Generic)
λ> transmogrify (Foo (3 :: Word) 2 'a')
     :: Bar Int
Bar {a = 3, b = 2, c = 'a'}

But…

… it’s a tad boring.

What about nested examples?

  • Let’s pretend there’s no laziness.
  • Then the following are isomorphic:
    • ('t', 'u', 'p', 'l', 'e')
    • (('t', 'u'), 'p', ('l', 'e'))
    • ('t', ('u', ('p', ('l', ('e')))))

How to deal with this?

  • Recursively merge children product-types
  • Canonicalise the representation
  • Basically, convert everything into a lisp-style list: 't' :*: ('u' :*: ('p' :*: ('l' :*: ('e'))))

Witness the power!

λ> transmogrify
     ('H', 'a', 's', 'k', 'e', 'l', 'l')
     :: ((Char, Char)
        , Char
        , (Char, Char, (Char, Char)))
(('H','a'),'s',('k','e',('l','l')))

So, everyone should use this, right?

Let’s consider the types…

transmogrify :: (SameShape a b) => a -> b
λ> :type transmogrify
transmogrify
  :: (Raw' a (CanRecurse a)
        ~ Raw' b (CanRecurse b)
     , RawShape' b (CanRecurse b)
     , RawShape' a (CanRecurse a)) =>
     a -> b

There’s no type inference or safety…

How do you stop recursing?

type family CanRecurse a :: Bool where
  CanRecurse Int     = 'False
  CanRecurse Int8    = 'False
  CanRecurse Int16   = 'False
  CanRecurse Int32   = 'False
  CanRecurse Int64   = 'False
  CanRecurse Integer = 'False
  -- Snip other numeric types
  CanRecurse Char    = 'False
  CanRecurse a       = 'True

So, ummm….

  • If it was just the performance and type-inference, this would still be useful.
  • But without extensibility, this isn’t really viable.
  • As such, this isn’t on Hackage.
  • So all I can say is…

USE GENERICS