Can I type a partial record?
First way:
You can define Person
with Maybe UUID
:
data Person = Person { name :: String, id :: Maybe UUID }
Now you can define some useful functions:
partialPerson :: String -> Person
partialPerson n = Person { name = n, id = Nothing }
getId :: Person -> UUID
getId (Person _ (Just uuid)) = uuid
getId _ = error "person hasn't UUID"
But getId
is unsafe function, so you must be careful when you use it.
Second way:
You can define new data-type:
data PartialPerson = PartialPerson { name :: String }
... and define such functions for converting between this types:
withId :: PartialPerson -> UUID -> Person
withId (PartialPerson n) uuid = Person n uuid
withoutId :: Person -> PartialPerson
withoutId (Person n _) = PartialPerson n
One possible type for persons without id would be:
type PersonWithoutId = UUID -> Person
The problem is that we can't print values of this type, or inspect their other fields, because functions are opaque.
Another option is to parameterize the type:
data Person a = Person { name :: String, personId :: a }
type PersonWithId = Person UUID
type PersonWithoutId = Person ()
The nice thing about this is that you still can easily derive useful typeclasses.
A third option is to remove the id from the person and just use a pair when needed:
type PersonWithId = (UUID,Person)
or with a dedicated type:
data WithId a = WithId { theId :: UUID, theValue :: a } deriving Functor
The problem with this third option is that it becomes more cumbersome to have functions that work with both varieties of persons.
Also, auto-derived FromJSON
and ToJSON
instances will possibly have undesired nesting.
And it's not very extensible when there's more than one optional property.
A variation on the idea of making Person
parametric to support both persons with- and without id, whilst however keeping it rigid that the id
field is about UUIDs, not just some arbitrary parameter type:
{-# LANGUAGE DataKinds, KindSignatures, GADTs #-}
data IdRequirement
= NoIdNeeded | IdOptional | IdRequired
data IdField (reqId :: IdRequirement) where
IdLess :: IdField 'NoIdNeeded
IdOmitted :: IdField 'IdOptional
IdProvided :: UUID -> IdField 'IdOptional
RequiredId :: UUID -> IdField 'IdRequired
data Person (reqId :: IdRequirement)
= Person { personName :: String
, personId :: IdField reqId }