haskell-graphql / graphql-api

Write type-safe GraphQL services in Haskell
BSD 3-Clause "New" or "Revised" License
406 stars 35 forks source link

Correctly propagate Maybe handlers. Closes #102 and #119. #120

Closed teh closed 7 years ago

teh commented 7 years ago

I'm not happy with this solution because of the extra monad level introduced.

pure (Just (pure value))

is not a good way to return a Maybe. Not sure how to fix better ATM.

jml commented 7 years ago

I don't like the extra monad level either. I'm not sure how to fix and I don't think I fully understand what's going on. OK to hold off a little to give us both time to think of something better?

teh commented 7 years ago

The extra level happens because every "type translation" step in form of an instance HasResolver introduces a new monad level ATM.

  type Handler m (Maybe hg) = m (Maybe (Handler m hg))
                                        ^^^^^ this part expands to e.g. `m Text`

We can have arbitrary type computations, so we could e.g. add another type function to HasResolver:

class HasResolver (m :: Type -> Type) (a :: Type) where
  type Handler m a
  type NonMonadicHandler a
  type instance NonMonadicHandler a = a
  resolve :: Handler m a -> Maybe (SelectionSetByType Value) -> m (Result Value)

and then use that to avoid introducing the extra m. This makes it slightly harder to reason about types but practically it might be more intuitive.

teh commented 7 years ago

Ah, no, we still need a way of patching through m, and also NonMonadicHandler doesn't work when we expect Handler as input to resolve. I'll ponder.

teh commented 7 years ago

UX-wise it'd be nicest if we could return maybes directly, e.g. pure (Just "hello") but then we can't use Handler type expansion because that introduces the extra m (and it's needed in the case that we return an object, not just a single Text field).

So just to keep composition I don't think we'll get around Just (pure "hello"), no matter how ugly.

A 2nd option is trying to peel off the outer m, so instead of pure (Just (pure "hello")) we'd have just Just (pure "hello"), but then we need to figure out a way of running the handler in m, i.e. what is resolve in the following?:

instance forall m hg. (HasResolver m hg, Monad m) => HasResolver m (Maybe hg) where
  type Handler m (Maybe hg) = Maybe (Handler m hg)
  resolve handler selectionSet = ???

Slight tangent: We discussed a while back having a PlainField type that doesn't run in m, maybe that'd be a way of cushioning the API UX issues.