I have a Person
record with a name and an id, and a function createPerson
that returns a Person
without id
, leaving generating the UUID to the caller.:
-- Person.hs
import Data.UUID (UUID)
data Person = Person { name :: String, id :: UUID }
createPerson name = Person { name = name }
Is there a way type a Person
without an id
, to inform the Caller that id Person
will throw an exception? I've considered defining a PartialPerson
as follows:
data PartialPerson = { name :: String }
but this quickly gets cumbersome when I want to add or change fields.
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.
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
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