Open gdeest opened 4 years ago
I think this is a really great idea!
(it looks like we would need additional constraints on the monad type constructors for this to work),
If you mean a RunClient
constraint, I don't think we can have it - if we have it, we don't need the method at all! I'm not really sure what to do about that method though.
If you mean a RunClient constraint, I don't think we can have it - if we have it, we don't need the method at all!
That makes sense.
I'm not really sure what to do about that method though.
Do you mean that removing hoistClientMonad
is under consideration ?
Alternatively, we might be able to derive this function automatically with datatype-generic programming. I haven't given it much thought yet.
Do you mean that removing hoistClientMonad is under consideration ? I'm no longer sufficiently involved with servant to be making such big decisions!
But what about changing the type of hoistClientMonad to have the constraints?
hoistClientMonad :: RunClient mon' => Proxy m -> Proxy api -> (forall x. mon x -> mon' x) -> Client mon api -> Client mon' api
(I don't think we need the constraint on RunClient mon
for anything?).
Hmmm, I'll check if that works.
It seems to work just fine, I'll prepare a PR. Thanks for the suggestion !
One thing that seems like it'll be trouble is the forall context
here:
dict :: forall context m. Dict (GServerConstraints routes context m)
Some combinators (such as servant-multipart
and, soon most combinators), require specific contexts. I think just changing the class to:
class GServer (routes :: Type -> Type) context where
dict :: forall m. Dict (GServerConstraints routes context m)
default dict ::
GServerConstraints routes context m =>
Dict (GServerConstraints routes context m)
dict = Dict
works
@jkarni Would you consider giving your opinion on #1388 ? I am not sure that you are still a servant
maintainer, but since your interest was picked by this idea, I would appreciate your feedback.
There has been complaints in the past regarding the usability of APIs defined via nested generic products (see #1211 for example).
The problem with generic APIs is that one constantly needs to switch back and forth between generic and “vanilla” servant, at both term- and type-level. This is not an issue for flat APIs that correspond to a tuple of routes, but quickly becomes cumbersome when dealing with deeply nested APIs.
It occurred to me that the conversion machinery could be entirely hidden from the user, by allowing direct embedding of generic APIs via a new Servant combinator:
For which suitable instances could be defined, directly declaring clients and servers for
GenericApi MyRoutes
as records:I have taken a stab at it in this gist. Please disregard the quality of the code, this has been thrown together in a hurry :)
There is some ugly machinery there, but it works fine with servers. I haven't been able to write the
hoistClientMonad
function for clients though (it looks like we would need additional constraints on the monad type constructors for this to work), but clients in vanillaClientM
should work.Do you think this idea is worth pursuing ? Has this been tried before, or this somehow in violation with the Servant philosophy ?