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

      

+3


source to share


1 answer


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

.

+2


source







All Articles