user3594595
user3594595

Reputation:

FromRow instance for nested type with postgresql-simple

I am using the following types to represent a user:

data CredentialsRep uType pType = Credentials { username :: uType
                                              , password :: pType } deriving (Show)

data NameRep fType lType = Name { first :: fType
                                , last  :: lType } deriving (Show)

data UserRep cType nType = User { credentials :: cType
                                , name        :: nType} deriving (Show)

How can I write the FromRow instance for UserRep so that postgresql-simple can turn my queries into useful types? So far, I have this:

instance FromRow (UserRep a b) where
  fromRow = do
    username <- field
    password <- field
    fname <- field
    lname <- field
    return $ (User (Credentials username password) (Name fname lname))

Which results in this error:

src/PSQLTEST.hs:30:5:
    Couldn't match type ‘a’ with ‘CredentialsRep uType0 pType0’
      ‘a’ is a rigid type variable bound by
          the instance declaration at src/PSQLTEST.hs:24:10
    Expected type: RowParser (UserRep a b)
      Actual type: RowParser
                     (UserRep (CredentialsRep uType0 pType0) (NameRep fType0 lType0))
    Relevant bindings include
      password :: pType0 (bound at src/PSQLTEST.hs:27:5)
      username :: uType0 (bound at src/PSQLTEST.hs:26:5)
      fromRow :: RowParser (UserRep a b) (bound at src/PSQLTEST.hs:25:3)
    In a stmt of a 'do' block:
      return $ (User (Credentials username password) (Name fname lname))
    In the expression:
      do { username <- field;
           password <- field;
           fname <- field;
           lname <- field;
           .... }
    In an equation for ‘fromRow’:
        fromRow
          = do { username <- field;
                 password <- field;
                 fname <- field;
                 .... }

src/PSQLTEST.hs:30:5:
    Couldn't match type ‘b’ with ‘NameRep fType0 lType0’
      ‘b’ is a rigid type variable bound by
          the instance declaration at src/PSQLTEST.hs:24:10
    Expected type: RowParser (UserRep a b)
      Actual type: RowParser
                     (UserRep (CredentialsRep uType0 pType0) (NameRep fType0 lType0))
    Relevant bindings include
      lname :: lType0 (bound at src/PSQLTEST.hs:29:5)
      fname :: fType0 (bound at src/PSQLTEST.hs:28:5)
      fromRow :: RowParser (UserRep a b) (bound at src/PSQLTEST.hs:25:3)
    In a stmt of a 'do' block:
      return $ (User (Credentials username password) (Name fname lname))
    In the expression:
      do { username <- field;
           password <- field;
           fname <- field;
           lname <- field;
           .... }
    In an equation for ‘fromRow’:
        fromRow
          = do { username <- field;
                 password <- field;
                 fname <- field;
                 .... }

Upvotes: 3

Views: 552

Answers (1)

bheklilr
bheklilr

Reputation: 54078

(Disclaimer: I haven't used this library at all)

You're trying to define FromRow for all UserRep a b, but in the definition of fromRow you're explicitly constructing a UserRep (CredentialsRep u p) (NameRep f l), hence the error saying it can't match a with CredentialsRep u p and b with NameRep f l (modulo type variable names). Instead, I believe you can do something like

instance (FromField u, FromField p, FromField f, FromField l) => FromRow (UserRep (CredentialsRep u p) (NameRep f l)) where
    fromRow = do
        username  <- field
        password  <- field
        firstname <- field
        lastname  <- field
        return $ User (Credentials username password) (Name firstname lastname)

Or just

instance FromRow (UserRep (CredentialsRep String String) (NameRep String String)) where
    ...

with the same fromRow definition. With this approach you may have to enable the FlexibleInstances extension.

In addition, it looks like you may be making your types too polymorphic (but your situation might be unique, take this advice with a grain of salt), you could just get rid of the type parameters altogether and have

data CredentialsRep = Credentials
    { username :: String
    , password :: String
    } deriving (Show)

data NameRep = Name
    { first :: String
    , last :: String
    } deriving (Show)

data UserRep = User
    { credentials :: CredentialsRep
    , name :: NameRep
    } deriving (Show)

In which case you would just need

instance FromRow UserRep where
    ...

with the same fromRow definition.

Upvotes: 5

Related Questions