Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

instance FromRow a => FromRow (Maybe a) #83

Open
derrickbeining opened this issue Jan 24, 2022 · 6 comments
Open

instance FromRow a => FromRow (Maybe a) #83

derrickbeining opened this issue Jan 24, 2022 · 6 comments

Comments

@derrickbeining
Copy link

I'm kind of new to haskell, so forgive me if the answer to this is obvious, but I was attempting to do a left join in order to get rows :: [LeftEntity :. Maybe RightEntity] but then was informed by the compiler that there's no instance for instance FromRow a => FromRow (Maybe a). So I ended up having parse into an intermediate structure first like [LeftEntity :. IntermediateRightEntityResults] and then transform IntermediateRightEntityResult into Maybe RightEntity. I would really prefer to be able to parse directly into the Maybe, though. Is there reason there isn't an instance for FromRow (Maybe a)? And is there perhaps some other way to do what I want? Or is parsing into a temporary intermediate structure just how things are supposed to be done?

@phadej
Copy link
Collaborator

phadej commented Jan 24, 2022

If you select only one column, use Only wrapper.

@derrickbeining
Copy link
Author

derrickbeining commented Jan 24, 2022

The RightEntity that I'm trying to parse out of my left join query consists of more than one column, so I can't use the Only wrapper.

Does my question make sense? Here's some example code

data LeftEntity = LeftEntity
    { leId :: Id LeftEntity
    , leName :: Text
    , leAssociatedRightEntityId :: Id RightEntity
    }
    deriving (Show, Generic, ToRow, FromRow)

data RightEntity = RightEntity
    { reId :: Id RightEntity
    , reName :: Text
    , reDescription :: Text
    }
    deriving (Show, Generic, ToRow, FromRow)

getLeftEntityWithAssociatedRightEntity = do
    rows :: [ LeftEntity :. Maybe RightEntity ] <- runSelect [PGQ.sqlExp|
              select
                le.id,
                le.name,
                le.associated_right_entity_id,

                re.id,
                re.name,
                re.uuid

              from left_entity le
              left join right_entity re on re.id = le.associated_right_entity_id
        |]

  pure $
    flip fmap rows $ \(leftEntity :. mRightEntity) ->
      (leftEntity, mRightEntity)

This doesn't compile because there's no instance of FromRow a => FromRow (Maybe a)

So I have to change Maybe RightEntity to something that can collect all RightEntity fields (which are nullable in a left join) like

data RightEntityFields = RightEntityFields
    { reId :: Maybe (Id RightEntity)
    , reName :: Maybe Text
    , reDescription :: Maybe Text
    }
    deriving (Show, Generic, ToRow, FromRow)

And then make a function mkRightEntityFromFields :: RightEntityFields -> Maybe RightEntity and map the results of my query to that.

I'm wanting to know if there's a way to avoid all that boilerplate.

@derrickbeining
Copy link
Author

derrickbeining commented Jan 24, 2022

I see there's something close to what I want but it's for tuples:

(FromField a, FromField b) => FromRow (Maybe (a, b))

Someone pointed out to me that I could try uncurrying the constructor I was wanting to parse into and map that over the Maybe (a, b, ...z) to avoid having to define an intermediate structure.

So I could do something like

getLeftEntitiesWithAssociatedRightEntities = do
    rows :: [ LeftEntity :. Maybe RightEntity ] <-
        List.map go <$> runSelect [PGQ.sqlExp|
              select
                le.id,
                le.name,
                le.associated_right_entity_id,

                re.id,
                re.name,
                re.uuid

              from left_entity le
              left join right_entity re on re.id = le.associated_right_entity_id
        |]

  pure $
    flip fmap rows $ \(leftEntity :. mRightEntity) ->
      (leftEntity, mRightEntity)

  where
    go  (left :. maybeRight) = left :. fmap (uncurry3 RightEntity)  maybeRight

That does seem to work and I like that much better than having to define an ad-hoc type to collect intermediate results. But I still wish I could just parse directly into Maybe RightEntity without having to jump through extra hoops. Is there a way to achieve that? Perhaps a new feature to add to the library?

@phadej
Copy link
Collaborator

phadej commented Jan 24, 2022

There is nothing wrong in defining ad-hoc types for specific purposes. Types are cheap.

Now as I think of it, the FromRow a => Maybe (FromRow a) is impossible to define as FromRow class doesn't tell how many columns it will consume. RowParser consumes some columns, in LeftEntity :. Maybe RightEntity specific case you know that Maybe part should consume all the rest of columns. But in Maybe X .: Maybe Y that would be wrong.

@phadej
Copy link
Collaborator

phadej commented Jan 24, 2022

I see there's something close to what I want but it's for tuples:

(FromField a, FromField b) => FromRow (Maybe (a, b))

That's different. It has FromFields as context, not FromRow, so column count is known.

@cimmanon
Copy link

If you enable FlexibleInstances, you can define a Maybe instance for your type. Still a bit of boilerplate, but at least you don't need an intermediate type.

instance FromRow (Maybe RightEntity) where
    fromRow = maybeRightEntity <$> field <*> field <*> field

maybeRightEntity :: Maybe (Id RightEntity) -> Maybe Text -> Maybe Text -> Maybe RightEntity
maybeRightEntity (Just a) (Just b) (Just c) = Just $ RightEntity a b c
maybeRightEntity = Nothing

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants