"Dependent optional" data in Haskell

algebraic-data-types, haskell

Solution

(This isn't an answer, but it's too long for a comment and will be clearer here.)

There is another way that you could handle it: have a single `DateTime` type that stores all fields always along with a parameter representing the precision, e.g.

data Precision = Days | Seconds | Milliseconds | Nanoseconds deriving (Ord, Eq {- etc -})
data DateTime = DateTime { prec :: Precision,
                           days :: Int, 
                           sec :: Int,
                           ms :: Int,
                           ns :: Int }

And use smart constructors that set the unused parameters to `0`. If you have `dateDifference` or whatever, you can propagate the precision through (the `Ord` instance would make this neat).

(I've got little idea about how good/Haskell-y this is, but the other solutions seem quite messy, maybe this is more elegant.)

Problem

Consider a DateTime type where the date must be present, but the time part in seconds is optional. If the time part is there, there might be an optional milliseconds part, too. If milliseconds are present, there might be a nanoseconds part, too. There are many ways to deal with this, e.g.: ``` --rely on smart constructors data DateTime = DateTime { days:: Int, sec :: Maybe Int, ms :: Maybe Int, ns :: Maybe Int } -- list all possibilities data DateTime = DateOnly Int | DateWithSec Int Int | DateWithMilliSec Int Int Int | DateWithNanoSec Int Int Int Int -- cascaded Maybe data DateTime = DateTime Int (Maybe (Int, Maybe (Int, Maybe Int))) -- cascaded data data Nano = NoNano | Nano Int data MilliSec = NoMilliSec | MilliSec Int Nano data Sec = NoSec | Sec Int MilliSec data Date = Date Int Sec ``` Which construct would you use (of course not limited to the examples above), and why? [Intentions] I'm exploring the possibilities for a date type in Frege ( http://code.google.com/p/frege/ ), using date4j's `DateTime` as a guide line (as Haskell's date and time lib is way too complicated, and `java.util.Date` too broken). In my current toy implementation all fields are mandatory, but of course it would be nice to free the user from unwanted precision (and the original implementation has optional fields). So the main goals are: - safety: Illegal states must be avoided at all costs - convenience: It should be easy to work with the type, e.g. pattern matching would be cool, calendar calculations should be easy... Not so important are: - performance: Of course working with the type shouldn't be too slow, but for the typical usage it doesn't have to sqeeze out the last clock cycle - memory: In cases where this really matters, it would be easy to derive a more compact storage format - terse implementation: It's a library, and I'm willing to add all the code needed to make things smooth That said, all of this is very tentative and shouldn't be taken too serious.

Original source