9

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.

Doug McClean
  • 14,265
  • 6
  • 48
  • 70
  • 2
    You might be able to do this with GHC Generics, but I don't know if it'd be any simpler than TH. Since I have experience with TH, that'd be my choice as well. – bheklilr Jul 23 '14 at 23:01
  • 8
    What I would consider: don't use `VehicleState'` at all, and make a short alias for `Identity` and `runIdentity`. I know, not great. – luqui Jul 23 '14 at 23:21
  • Or use [newtype](http://hackage.haskell.org/package/newtype) and its helpers. – luqui Jul 23 '14 at 23:23
  • 1
    `Identity` is a newtype, so `cast :: VehicleState Identity -> VehicleState'; cast = unsafeCoerce` is, in this case, actually safe. (obviously the same for the inverse) – user2407038 Jul 24 '14 at 03:12
  • 6
    I agree with @luqui's suggestion; I've used it and it works well. Just derive lenses to access each field of `VehicleState`, and then you can access the `Identity`-wrapped variants by composing `wrapped` or `unwrapped` with the lens. – John L Jul 24 '14 at 03:17
  • 1
    @JohnL, if you have time could you elaborate? I don't understand lenses yet. – Doug McClean Jul 24 '14 at 13:11
  • @user2407038, Is it safe in this case? How do we know that the compiler decided to layout the two records in the same order? Is it guaranteed to happen in source order? – Doug McClean Jul 24 '14 at 15:18
  • @DougMcClean If by 'guaranteed' you mean the compiler promises to do it that way, then no, I don't believe so. But it is certainly done that way. We know because the `cast` function does not cause the universe to explode. – user2407038 Jul 24 '14 at 22:44

1 Answers1

4

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.

kosmikus
  • 19,549
  • 3
  • 51
  • 66