3

I have a User type that represents a user saved in the database. However, when displaying users, I only want to return a subset of these fields so I made a different type without the hash. When creating a user, a password will be provided instead of a hash, so I made another type for that.

This is clearly the worst, because there is tons of duplication between my types. Is there a better way to create several related 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
2
  • 1
    Why do you need UserPrintable at all? Just create a showPrintable ::User->String function. Commented Dec 18, 2014 at 18:21
  • I suspect you are trying to solve the wrong problem. What are you trying to achieve? If you are just interested in a subset of fields under different circumstances then just pass the whole type. Maybe you need to replace UserFromRequest with a function that computes the hash from the password. BTW, have you considered where salts will go? Commented Dec 18, 2014 at 18:40

1 Answer 1

2

In this case, I think you can replace your various User types with functions. So instead of UserFromRequest, have:

userFromRequest :: Email -> InstitutionId -> String -> User

Note how you can also make separate types for Email and InstitutionId, which will help you avoid a bunch of annoying mistakes. This serves the same purpose as taking a record with labelled fields as an argument, while also adding a bit of extra static safety. You can just implement these as newtypes:

newtype Email = Email String deriving (Show, Eq)

Similarly, we can replace UserPrintable with showUser.

UserCreatable might be a bit awkard however, depending on how you need to use it. If all you ever do with it is take it as an argument and create a database row, then you can refactor it into a function the same way. But if you actually need the type for a bunch of things, this isn't a good solution.

In this second case, you have a couple of decent options. One would be to just make id a Maybe and check it each time. A better one would be to create a generic type WithId a which just adds an id field to anything:

data WithId a = { id :: DatabaseId, content :: a }

Then have a User type with no id and have your database functions work with a WithId User.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.