Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can I type a partial record?

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.

like image 262
Niek Avatar asked Feb 17 '21 12:02

Niek


2 Answers

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.

like image 146
danidiaz Avatar answered Oct 19 '22 08:10

danidiaz


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
like image 26
SergeyKuz1001 Avatar answered Oct 19 '22 08:10

SergeyKuz1001