How can I create multiple related datatypes in haskell?
I have a type User
that represents the user stored in the database. However, when displaying users, I only want to return a subset of those fields, so I made another type without hash
. When creating a user, instead hash
will be provided password
, so I made a different type for it.
This is by far the worst, because there is a lot of duplication between my types. Is there a better way to create multiple sibling types that all share some fields, but add some fields and remove others?
{-# LANGUAGE DeriveGeneric #}
data User = User {
id :: String,
email :: String,
hash :: String,
institutionId :: String
} deriving (Show, Generic)
data UserPrintable = UserPrintable {
email :: String,
id :: String,
institutionId :: String
} deriving (Generic)
data UserCreatable = UserCreatable {
email :: String,
hash :: String,
institutionId :: String
} deriving (Generic)
data UserFromRequest = UserFromRequest {
email :: String,
institutionId :: String,
password :: String
} deriving (Generic)
-- UGHHHHHHHHHHH
source to share
In this case, I think you can replace the different types User
with functions. Therefore, instead of UserFromRequest
, we have:
userFromRequest :: Email -> InstitutionId -> String -> User
Note that you can also create separate types for Email
and InstitutionId
, which will help you avoid a bunch of annoying bugs. This serves the same purpose as writing a record with tagged fields as an argument, as well as adding a little extra static security. You can simply implement them as newtypes:
newtype Email = Email String deriving (Show, Eq)
Similarly, we can replace UserPrintable
with showUser
.
UserCreatable
can be a little weird depending on how you need to use it. If all you've ever done with it is taking it as an argument and creating a database string, then you can refactor it into a function in the same way. But if you really need a type for a bunch of things, this is not a good solution.
In this second case, you have a couple of decent options. One could just do id
a Maybe
and check it every time. Your best bet would be to create a generic type WithId a
that just adds a field id
to anything:
data WithId a = { id :: DatabaseId, content :: a }
Then enter the type User
without id
and your database functions will work with WithId User
.
source to share