graphql-api icon indicating copy to clipboard operation
graphql-api copied to clipboard

Question: mapping over a Handler?

Open lexi-lambda opened this issue 8 years ago • 5 comments

This is a reproduction of an open Stack Overflow question, which I think might get more visibility here.

I have created a simple API for testing purposes:

type Query = Object "Query" '[]
  '[ Argument "id" Text :> Field "test" (Maybe Foo) ]

type Foo = Object "Foo" '[]
  '[ Field "name" Text ]

I also have a Haskell type that represents the Foo resource, as well as a function for retrieving it from the database:

data ServerFoo = ServerFoo
  { name :: Text
  } deriving (Eq, Show)

lookupFoo :: Text -> IO (Maybe ServerFoo)

Now, I want to implement a Handler for Query. To start, I implemented a Handler for Foo:

viewFoo :: ServerFoo -> Handler IO Foo
viewFoo ServerFoo { name } = pure $ pure name

Then I went to implement the root handler, but I realized I am not sure how to use my viewFoo function when I end up with with a Maybe ServerFoo. I tried this:

handler :: Handler IO Query
handler = pure $ \fooId -> do
  foo <- lookupFoo fooId
  sequence $ fmap viewFoo foo

However, this does not work. It produces the following type error:

• Couldn't match type ‘IO Text’
                 with ‘Object "Foo" '[] '[Field "name" Text]’
  Expected type: ServerFoo
                 -> IO (Object "Foo" '[] '[Field "name" Text])
    Actual type: ServerFoo -> Handler IO Foo
• In the first argument of ‘fmap’, namely ‘viewFoo’
  In the second argument of ‘($)’, namely ‘fmap viewFoo foo’
  In a stmt of a 'do' block: (sequence $ fmap viewFoo foo)

This seems a bit odd to me, given that the examples I’ve read would seem to imply that Handlers are designed to be compositional. Indeed, they appear to be, since making a few tweaks to remove the Maybe makes this typecheck:

type Query = Object "Query" '[]
  '[ Argument "id" Text :> Field "test" Foo ]

handler :: Handler IO Query
handler = pure $ \fooId -> do
  Just foo <- lookupFoo fooId
  viewFoo foo

Obviously, however, this involves a partial pattern match, so it’s not an okay solution.

My conclusion is that either Handler IO is not a functor or Handler m (Maybe a) is stranger than I’m anticipating. Whatever the reason, this seems like a fairly straightforward use case, so I would imagine there has to be some solution.

lexi-lambda avatar Apr 11 '17 19:04 lexi-lambda

Hi Alexis,

We'll look into this as soon as we can, but this is a best-effort, spare time thing for both @teh and I.

jml

On Tue, 11 Apr 2017 at 20:47 Alexis King [email protected] wrote:

This is a reproduction of an open Stack Overflow question http://stackoverflow.com/q/43333486/465378, which I think might get more visibility here.

I have created a simple API for testing purposes:

type Query = Object "Query" '[] '[ Argument "id" Text :> Field "test" (Maybe Foo) ] type Foo = Object "Foo" '[] '[ Field "name" Text ]

I also have a Haskell type that represents the Foo resource, as well as a function for retrieving it from the database:

data ServerFoo = ServerFoo { name :: Text } deriving (Eq, Show) lookupFoo :: Text -> IO (Maybe ServerFoo)

Now, I want to implement a Handler for Query. To start, I implemented a Handler for Foo:

viewFoo :: ServerFoo -> Handler IO Foo viewFoo ServerFoo { name } = pure $ pure name

Then I went to implement the root handler, but I realized I am not sure how to use my viewFoo function when I end up with with a Maybe ServerFoo. I tried this:

handler :: Handler IO Query handler = pure $ \fooId -> do foo <- lookupFoo fooId sequence $ fmap viewFoo foo

However, this does not work. It produces the following type error:

• Couldn't match type ‘IO Text’ with ‘Object "Foo" '[] '[Field "name" Text]’ Expected type: ServerFoo -> IO (Object "Foo" '[] '[Field "name" Text]) Actual type: ServerFoo -> Handler IO Foo • In the first argument of ‘fmap’, namely ‘viewFoo’ In the second argument of ‘($)’, namely ‘fmap viewFoo foo’ In a stmt of a 'do' block: (sequence $ fmap viewFoo foo)

This seems a bit odd to me, given that the examples I’ve read would seem to imply that Handlers are designed to be compositional. Indeed, they appear to be, since making a few tweaks to remove the Maybe makes this typecheck:

type Query = Object "Query" '[] '[ Argument "id" Text :> Field "test" Foo ] handler :: Handler IO Query handler = pure $ \fooId -> do Just foo <- lookupFoo fooId viewFoo foo

Obviously, however, this involves a partial pattern match, so it’s not an okay solution.

My conclusion is that either Handler IO is not a functor or Handler m (Maybe a) is stranger than I’m anticipating. Whatever the reason, this seems like a fairly straightforward use case, so I would imagine there has to be some solution.

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub https://github.com/jml/graphql-api/issues/119, or mute the thread https://github.com/notifications/unsubscribe-auth/AAHq6oNYcFODIKCkByc_gO9AlKzcm6Vsks5ru9jQgaJpZM4M6kCc .

jml avatar Apr 13 '17 17:04 jml

No worries, I certainly understand. I think this project is really exciting, for what it’s worth, and it seems to work pretty well considering how young it is.

lexi-lambda avatar Apr 14 '17 01:04 lexi-lambda

Thanks for the detailed report!

looking at the expansion:

λ  :kind! Handler IO Query
Handler IO Query :: *
= IO (Text -> IO (Maybe (Object "Foo" '[] '[Field "name" Text])))

The Object "Foo" '[] '[Field "name" Text] part should be reduced to another type but it isn't. I.e. this is a bug.

teh avatar Apr 18 '17 20:04 teh

Yes, I looked through the source about a week ago and found this line in GraphQL.Resolver:

instance forall m hg. (HasResolver m hg, Functor m, ToValue (Maybe hg)) => HasResolver m (Maybe hg) where
  type Handler m (Maybe hg) = m (Maybe hg)
  resolve handler _ =  map (ok . toValue) handler

I thought it was weird that the HasResolver m hg instance doesn’t seem to be used at all. The associated type is just m (Maybe hg) instead of m (Maybe (Handler m hg)), as I would have expected. However, I’m not familiar enough with the library to know what the right thing to do is.

lexi-lambda avatar Apr 18 '17 20:04 lexi-lambda

Related: https://github.com/jml/graphql-api/issues/102

I'm checking but might take a while because I haven't touched this part of the code in a while :)

teh avatar Apr 18 '17 21:04 teh