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:
Not so important are:
That said, all of this is very tentative and shouldn't be taken too serious.
(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.)
“Illegal states must be avoided at all costs” and “pattern matching would be cool” are fine principles that in this case are in direct conflict with each other.
In addition, dates and times are gnarly human cultural constructs with lots of edge cases and irregular corners. They are not the sort of rules we can easily encode in the type system.
So in this case I would go with an opaque data type, smart constructors, and smart deconstructors. There's always view patterns and pattern guards for occasions when we want to use pattern matching.
(And I haven't even discussed dependent optional data as a motivating factor.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With