Question: mapping over a Handler?
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.
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 .
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.
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.
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.
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 :)