Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Map identity functor over record

I have a record type like this one:

data VehicleState f = VehicleState
                      {
                        orientation :: f (Quaternion Double),
                        orientationRate :: f (Quaternion Double),
                        acceleration :: f (V3 (Acceleration Double)),
                        velocity :: f (V3 (Velocity Double)),
                        location :: f (Coordinate),
                        elapsedTime :: f (Time Double)
                      }
                    deriving (Show)

This is cool, because I can have a VehicleState Signal where I have all sorts of metadata, I can have a VehicleState (Wire s e m ()) where I have the netwire semantics of each signal, or I can have a VehicleState Identity where I have actual values observed at a certain time.

Is there a good way to map back and forth between VehicleState Identity and VehicleState', defined by mapping runIdentity over each field?

data VehicleState' = VehicleState'
                      {
                        orientation :: Quaternion Double,
                        orientationRate :: Quaternion Double,
                        acceleration :: V3 (Acceleration Double),
                        velocity :: V3 (Velocity Double),
                        location :: Coordinate,
                        elapsedTime :: Time Double
                      }
                    deriving (Show)

Obviously it's trivial to write one, but I actually have several types like this in my real application and I keep adding or removing fields, so it is tedious.

I am writing some Template Haskell that does it, just wondering if I am reinventing the wheel.

like image 297
Doug McClean Avatar asked Jul 23 '14 22:07

Doug McClean


1 Answers

If you're not opposed to type families and don't need too much type inference, you can actually get away with using a single datatype:

import Data.Singletons.Prelude

data Record f = Record
  { x :: Apply f Int
  , y :: Apply f Bool
  , z :: Apply f String
  }

type Record' = Record IdSym0

test1 :: Record (TyCon1 Maybe)
test1 = Record (Just 3) Nothing (Just "foo")

test2 :: Record'
test2 = Record 2 False "bar"

The Apply type family is defined in the singletons package. It can be applied to various type functions also defined in that package (and of course, you can define your own). The IdSym0 has the property that Apply IdSym0 x reduces to plain x. And TyCon1 has the property that Apply (TyCon1 f) x reduces to f x.

As demonstrated by test1 and test2, this allows both versions of your datatype. However, you need type annotations for most records now.

like image 55
kosmikus Avatar answered Sep 20 '22 18:09

kosmikus