haskell-servant / servant

Main repository for the servant libraries — DSL for describing, serving, querying, mocking, documenting web applications and more!
https://docs.servant.dev/
1.8k stars 407 forks source link

monadic `hoistServerM`? #1700

Closed goertzenator closed 11 months ago

goertzenator commented 11 months ago

I have been experimenting MonadUnliftIO handlers under Servant with some success. Example. An essential component is unliftio's "toIO" function which looks like this:

toIO :: MonadUnliftIO m => m a -> m (IO a)

This is an effective way to get a IO a which with a little further massaging can be turned into Handler. But, this function has a monadic context so it does not work with hoistServer which has this signature:

hoistServer :: HasServer api '[] => Proxy api -> (forall x. m x -> n x) -> ServerT api m -> ServerT api n

What is needed in this situation is a monadic version of hoistServer that looks like this:

hoistServerM :: HasServer api '[] => Proxy api -> (forall x. m x -> m (n x)) -> ServerT api m -> m (ServerT api n)

I would like to experiment with an implementation that looks like this:

  1. Convert the existing implementations of hoistServerWithContext into hoistServerWithContextM.
  2. Add a hoistServerWithContext that wraps hoistServerWithContextM using Identity.

My example above demonstrates ReaderT, but my real motive is to enable ReaderT-based effect systems like cleff and effectful.

I am looking for some feedback on this idea. If I did a PR like this would it have any hope of being merged? Are there some obvious shortcomings here that I am not seeing?

As a reference, wai supports unliftio through the wai-control package.

Thank you for your time.

goertzenator commented 11 months ago

The proposed solution has some serious problems and I would like to retract it.

Also, I've found a solution that involves no modification to Servant.

The main line is this one which hoists a ReaderT _ IO to Handler

  server <- withRunInIO $ \toIo -> pure $ hoistServer counterAPI (Handler . ExceptT . try . toIo) server_rio
  -- `toIO` converts from (ReaderT (IORef Int) IO) to IO
  -- `Handler. ExceptT . try` converts Servant exceptions in IO back to ExceptT

That of course will work with anything that is MonadUnliftIO such as the cleff and effectful Eff monad.

Closing this issue.