Quantcast

Unit of :<|>?

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
7 messages Options
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Unit of :<|>?

David Turner-3
Hi,

Is there a unit of the :<|> operator defined anywhere? I can't immediately think why it shouldn't exist, but nor can we find it.

The use case is that we're defining APIs in a handful of places and want to combine them together, but sometimes there's nothing to serve in one of the places. Clearly we can define a dummy route that returns NoContent in this situation, but a unit would seem slicker.

Cheers,

David

--





Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Unit of :<|>?

Alp Mestanogullari
The problem that I see with a notion of unit for :<|> is that you need a unit in API types, in server handlers, in client functions and in any other place where one uses :<|>. I never could come up with a notion of unit that made sense in all those places, or even in one of them.

Usually, people want a unit because they think of :<|> as an heterogeneous list, while it's actually closer to a pair. a :<|> b :<|> c ~ (a, (b, c)).

Would you mind expanding on your use case, so that we can try and figure something out?

On Mon, May 15, 2017 at 7:24 PM, David Turner <[hidden email]> wrote:
Hi,

Is there a unit of the :<|> operator defined anywhere? I can't immediately think why it shouldn't exist, but nor can we find it.

The use case is that we're defining APIs in a handful of places and want to combine them together, but sometimes there's nothing to serve in one of the places. Clearly we can define a dummy route that returns NoContent in this situation, but a unit would seem slicker.

Cheers,

David

--



--
Alp Mestanogullari

--
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Unit of :<|>?

Nickolay Kudasov
Hi Alp, David,

I think that a unit for :<|> would actually be nice to have. It just hasn't been *necessary* recently.
Coincidentally, just a few days before I was discussing a problem at work and an empty API (a.k.a. unit of :<|>) got mentioned (briefly).

The problem is that we have some endpoints that should be POST endpoints, but some clients (SmartTVs in our case) surprisingly don't support POST. Other clients have some weird settings, driving POST unreasonable for them to use. So for a part of our API we have a GET endpoint for every POST endpoint. This means that we need to duplicate everything — API types, server handlers, client functions.

To avoid this code duplication it would be nice to use Verb with a list of methods. In fact Verb is already poly-kinded and we can use a list of methods already:

type PostGet = Verb '[POST, GET] 200

For a server we can probably write something like this (untested): https://gist.github.com/fizruk/3bc23c90a33cd85f0e55167355ca6bda

There's an obvious corner case for Verb '[] and that'd be exactly the same as a unit for :<|>.
Also Verb '[method] would be equivallent to Verb method.

As you can see, handling Verb '[] is not really important for our project since we're only dealing with non-empty lists. However I still see the point in having empty API.
Just like David, we combine our API from a set of subAPIs which can have subAPIs of their own. When designing APIs it's sometimes known that there would be a certain subAPI, but its form is not defined yet. E.g.:

type MyAPI
    = "books" :> BooksAPI
 :<|> "services" :> ServicesAPI

type BooksAPI = ...          -- some defined API
type ServicesAPI = EmptyAPI  -- we're not sure what is going to be here

Currently one would have to resort to something like

type ServicesAPI = Get '[JSON] NoContent

Which does not describe well what's going on.

With EmptyAPI we can even add an optional type-level checker that'll ensure that there are no EmptyAPIs left.

To address Alp's concerns about instances (interpretations):
  • I think that to serve an empty API is to always fail (with 404). That way when you combine empty API with :<|> you immediately consider a non-empty part.
  • A Haskell client for an empty API is something isomorphic to (). E.g. data NoClient = NoClient
  • A JS client for an empty API would consist of no functions (empty module).
  • Documentation for an empty API is empty documentation with no endpoints.
Overall I think empty API can and should be a logically consistent part of Servant. There might be more nice uses for it. I would think there might be some uses in (whole) API manipulation functions.

Kind regards,
Nick

On Mon, 15 May 2017 at 20:35 Alp Mestanogullari <[hidden email]> wrote:
The problem that I see with a notion of unit for :<|> is that you need a unit in API types, in server handlers, in client functions and in any other place where one uses :<|>. I never could come up with a notion of unit that made sense in all those places, or even in one of them.

Usually, people want a unit because they think of :<|> as an heterogeneous list, while it's actually closer to a pair. a :<|> b :<|> c ~ (a, (b, c)).

Would you mind expanding on your use case, so that we can try and figure something out?

On Mon, May 15, 2017 at 7:24 PM, David Turner <[hidden email]> wrote:
Hi,

Is there a unit of the :<|> operator defined anywhere? I can't immediately think why it shouldn't exist, but nor can we find it.

The use case is that we're defining APIs in a handful of places and want to combine them together, but sometimes there's nothing to serve in one of the places. Clearly we can define a dummy route that returns NoContent in this situation, but a unit would seem slicker.

Cheers,

David

--



--
Alp Mestanogullari

--

--
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Unit of :<|>?

Alp Mestanogullari
If you can make a strong case for empty APIs, I would not be opposed to it. Not that my opinion is authoritative. For such a thing to be added to servant, I usually just want enough maintainers/users asking for it (unless it's reaaaallly ugly/hacky/fragile). But please allow me to challenge your case with more comments, hopefully you'll both find this discussion constructive.

On Mon, May 15, 2017 at 8:45 PM, Nickolay Kudasov <[hidden email]> wrote:
The problem is that we have some endpoints that should be POST endpoints, but some clients (SmartTVs in our case) surprisingly don't support POST. Other clients have some weird settings, driving POST unreasonable for them to use. So for a part of our API we have a GET endpoint for every POST endpoint. This means that we need to duplicate everything — API types, server handlers, client functions.

Couldn't you do some type-level and value-level transformations to do the duplication for you? You'd write the "sane" API and have some code inserting the GET-equivalents of each POST endpoint for you. It's admittedly not exactly trivial to do it, but allows you to simply build on top of the existing servant DSL.

To avoid this code duplication it would be nice to use Verb with a list of methods. In fact Verb is already poly-kinded and we can use a list of methods already:

type PostGet = Verb '[POST, GET] 200

For a server we can probably write something like this (untested): https://gist.github.com/fizruk/3bc23c90a33cd85f0e55167355ca6bda

There's an obvious corner case for Verb '[] and that'd be exactly the same as a unit for :<|>.

Verb '[] would mean "any method" I guess?
 
Also Verb '[method] would be equivallent to Verb method.

As you can see, handling Verb '[] is not really important for our project since we're only dealing with non-empty lists. However I still see the point in having empty API.
Just like David, we combine our API from a set of subAPIs which can have subAPIs of their own. When designing APIs it's sometimes known that there would be a certain subAPI, but its form is not defined yet. E.g.:
type MyAPI
    = "books" :> BooksAPI
 :<|> "services" :> ServicesAPI

type BooksAPI = ...          -- some defined API
type ServicesAPI = EmptyAPI  -- we're not sure what is going to be here

Currently one would have to resort to something like

type ServicesAPI = Get '[JSON] NoContent

Which does not describe well what's going on.

With EmptyAPI we can even add an optional type-level checker that'll ensure that there are no EmptyAPIs left.

That's actually an argument I don't have a counter-argument for.
 
To address Alp's concerns about instances (interpretations):
  • I think that to serve an empty API is to always fail (with 404). That way when you combine empty API with :<|> you immediately consider a non-empty part.
  • A Haskell client for an empty API is something isomorphic to (). E.g. data NoClient = NoClient

This is the bit I dislike the most I think. While for the other interpretations you can rely on some kind of `mempty` (empty docs, empty JS module, dummy server), this one just feels hacky to me. It makes no sense to me that we could derive something that we can't use. I think I'd prefer a solution where Client does not do anything on EmptyAPI. It's easy if you have say, 'Client (foo :<|> EmptyAPI), where you could just say it's 'Client foo', but what if you just ask for 'Client EmptyAPI'?
  • A JS client for an empty API would consist of no functions (empty module).
  • Documentation for an empty API is empty documentation with no endpoints.
Overall I think empty API can and should be a logically consistent part of Servant. There might be more nice uses for it. I would think there might be some uses in (whole) API manipulation functions.

I'm beginning to see a point in EmptyAPI, I just think there's a bit more work needed to flesh out exactly what it should be and how it should behave in the different interpretations. I basically don't want any part of it to feel like a hack, but rather have it be a principled solution that "just fits" with everything else.

But again, that's just my opinion, you know that I won't fight it if everyone else wants it. 

Kind regards,
Nick

On Mon, 15 May 2017 at 20:35 Alp Mestanogullari <[hidden email]> wrote:
The problem that I see with a notion of unit for :<|> is that you need a unit in API types, in server handlers, in client functions and in any other place where one uses :<|>. I never could come up with a notion of unit that made sense in all those places, or even in one of them.

Usually, people want a unit because they think of :<|> as an heterogeneous list, while it's actually closer to a pair. a :<|> b :<|> c ~ (a, (b, c)).

Would you mind expanding on your use case, so that we can try and figure something out?

On Mon, May 15, 2017 at 7:24 PM, David Turner <[hidden email]> wrote:
Hi,

Is there a unit of the :<|> operator defined anywhere? I can't immediately think why it shouldn't exist, but nor can we find it.

The use case is that we're defining APIs in a handful of places and want to combine them together, but sometimes there's nothing to serve in one of the places. Clearly we can define a dummy route that returns NoContent in this situation, but a unit would seem slicker.

Cheers,

David

--



--
Alp Mestanogullari

--



--
Alp Mestanogullari

--
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Unit of :<|>?

David Turner-3
Ok, so it's good to know that it doesn't currently exist, and I've had a brief look at implementing it and see the problems. I'm not that hot on all the type-level magic involved in Servant so I might be missing something.

The use case is, as Nickolay says, cases where you want to compose an API out of bits that come from various sources. For example, we have a Servant API, `CommonAPI`, and a function that has a type rather like this:

defaultMain :: HasServer specialAPI => Proxy specialAPI -> Server specialAPI -> ... -> IO a

the purpose of which is to serve CommonAPI :<|> specialAPI, plus a bunch of other common setup/teardown code. Most of the time there's interesting stuff in specialAPI, but in one or two cases there's nothing much to put there.

Re. the point about `a :<|> b` being like a pair: that's fine, but (up-to-isomorphism-and-ignoring-bottom) there's a unit for pairing, which is `()`. I imagine that there can't be an on-the-nose unit for (:<|>) because it's a concrete type constructor, so this one would have to be up-to-isomorphism too.

A client for EmptyAPI should do nothing; a server for EmptyAPI should never succeed. So I'd guess:

`Client EmptyAPI = ()`
`ServerT EmptyAPI m = m Void`
There's already a value called `emptyAPI` in `servant-docs` that would seem appropriate to use here.

Not sure how to implement `route` for the `HasServer` instance, but perhaps there would need to be another constructor in `Router'`?

Cheers,

David






On 15 May 2017 at 20:27, Alp Mestanogullari <[hidden email]> wrote:
If you can make a strong case for empty APIs, I would not be opposed to it. Not that my opinion is authoritative. For such a thing to be added to servant, I usually just want enough maintainers/users asking for it (unless it's reaaaallly ugly/hacky/fragile). But please allow me to challenge your case with more comments, hopefully you'll both find this discussion constructive.

On Mon, May 15, 2017 at 8:45 PM, Nickolay Kudasov <[hidden email]> wrote:
The problem is that we have some endpoints that should be POST endpoints, but some clients (SmartTVs in our case) surprisingly don't support POST. Other clients have some weird settings, driving POST unreasonable for them to use. So for a part of our API we have a GET endpoint for every POST endpoint. This means that we need to duplicate everything — API types, server handlers, client functions.

Couldn't you do some type-level and value-level transformations to do the duplication for you? You'd write the "sane" API and have some code inserting the GET-equivalents of each POST endpoint for you. It's admittedly not exactly trivial to do it, but allows you to simply build on top of the existing servant DSL.

To avoid this code duplication it would be nice to use Verb with a list of methods. In fact Verb is already poly-kinded and we can use a list of methods already:

type PostGet = Verb '[POST, GET] 200

For a server we can probably write something like this (untested): https://gist.github.com/fizruk/3bc23c90a33cd85f0e55167355ca6bda

There's an obvious corner case for Verb '[] and that'd be exactly the same as a unit for :<|>.

Verb '[] would mean "any method" I guess?
 
Also Verb '[method] would be equivallent to Verb method.

As you can see, handling Verb '[] is not really important for our project since we're only dealing with non-empty lists. However I still see the point in having empty API.
Just like David, we combine our API from a set of subAPIs which can have subAPIs of their own. When designing APIs it's sometimes known that there would be a certain subAPI, but its form is not defined yet. E.g.:
type MyAPI
    = "books" :> BooksAPI
 :<|> "services" :> ServicesAPI

type BooksAPI = ...          -- some defined API
type ServicesAPI = EmptyAPI  -- we're not sure what is going to be here

Currently one would have to resort to something like

type ServicesAPI = Get '[JSON] NoContent

Which does not describe well what's going on.

With EmptyAPI we can even add an optional type-level checker that'll ensure that there are no EmptyAPIs left.

That's actually an argument I don't have a counter-argument for.
 
To address Alp's concerns about instances (interpretations):
  • I think that to serve an empty API is to always fail (with 404). That way when you combine empty API with :<|> you immediately consider a non-empty part.
  • A Haskell client for an empty API is something isomorphic to (). E.g. data NoClient = NoClient

This is the bit I dislike the most I think. While for the other interpretations you can rely on some kind of `mempty` (empty docs, empty JS module, dummy server), this one just feels hacky to me. It makes no sense to me that we could derive something that we can't use. I think I'd prefer a solution where Client does not do anything on EmptyAPI. It's easy if you have say, 'Client (foo :<|> EmptyAPI), where you could just say it's 'Client foo', but what if you just ask for 'Client EmptyAPI'?
  • A JS client for an empty API would consist of no functions (empty module).
  • Documentation for an empty API is empty documentation with no endpoints.
Overall I think empty API can and should be a logically consistent part of Servant. There might be more nice uses for it. I would think there might be some uses in (whole) API manipulation functions.

I'm beginning to see a point in EmptyAPI, I just think there's a bit more work needed to flesh out exactly what it should be and how it should behave in the different interpretations. I basically don't want any part of it to feel like a hack, but rather have it be a principled solution that "just fits" with everything else.

But again, that's just my opinion, you know that I won't fight it if everyone else wants it. 

Kind regards,
Nick

On Mon, 15 May 2017 at 20:35 Alp Mestanogullari <[hidden email]> wrote:
The problem that I see with a notion of unit for :<|> is that you need a unit in API types, in server handlers, in client functions and in any other place where one uses :<|>. I never could come up with a notion of unit that made sense in all those places, or even in one of them.

Usually, people want a unit because they think of :<|> as an heterogeneous list, while it's actually closer to a pair. a :<|> b :<|> c ~ (a, (b, c)).

Would you mind expanding on your use case, so that we can try and figure something out?

On Mon, May 15, 2017 at 7:24 PM, David Turner <[hidden email]> wrote:
Hi,

Is there a unit of the :<|> operator defined anywhere? I can't immediately think why it shouldn't exist, but nor can we find it.

The use case is that we're defining APIs in a handful of places and want to combine them together, but sometimes there's nothing to serve in one of the places. Clearly we can define a dummy route that returns NoContent in this situation, but a unit would seem slicker.

Cheers,

David

--



--
Alp Mestanogullari

--



--
Alp Mestanogullari

--
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Unit of :<|>?

Nickolay Kudasov
​Alp, thanks for the feedback!​

 
Couldn't you do some type-level and value-level transformations to do the duplication for you? You'd write the "sane" API and have some code inserting the GET-equivalents of each POST endpoint for you. It's admittedly not exactly trivial to do it, but allows you to simply build on top of the existing servant DSL.

​Yes, but as you say it's not exactly trivial and it's best done with a new combinator (e.g. homegrown). It's just that reusing Verb for it would be really nice.

Verb '[] would mean "any method" I guess?

​On the contrary, it would mean "no methods". The list is supposed to mention allowed methods.

This is the bit I dislike the most I think. While for the other interpretations you can rely on some kind of `mempty` (empty docs, empty JS module, dummy server), this one just feels hacky to me. It makes no sense to me that we could derive something that we can't use. I think I'd prefer a solution where Client does not do anything on EmptyAPI. It's easy if you have say, 'Client (foo :<|> EmptyAPI), where you could just say it's 'Client foo', but what if you just ask for 'Client EmptyAPI'?

​I don't think it's a hack. It's a placeholder for a bunch of client functions that are going to be there later.
In fact at work we use a similar unit type for parts of API where we are not interesting in generating Haskell client functions.​ We use generic client and it goes something like this:

data NotImplemented = NotImplemented

data MyClient = MyClient
  { ...
  , mySubApiClient :: NotImplemented
  ...
  }

instance (Client MyAPI ~ client) => ClientLike client MyClient where
  mkClient = genericMkClientP

I think that having unit types like NotImplemented or NoClient is good to state placeholders clearly.

Remember that there is going to be a corresponding NoServer handler. The "always failing server" will be in HasServer instance, but you still have to list something as a handler. Since there's nothing to handle, you have to use some unit type there too.

A client for EmptyAPI should do nothing; a server for EmptyAPI should never succeed. So I'd guess:
`Client EmptyAPI = ()`
`ServerT EmptyAPI m = m Void`

​David, I believe the type for Server should also be a unit type. It's only in the HasServer instance we have access to a proper failing mechanisms.
ServerT EmptyAPI m should tell us a type of handlers and since there's nothing to handle for an empty API we only need a trivial handler — a unit.

Kind regards,
Nick


On Tue, 16 May 2017 at 00:08 David Turner <[hidden email]> wrote:
Ok, so it's good to know that it doesn't currently exist, and I've had a brief look at implementing it and see the problems. I'm not that hot on all the type-level magic involved in Servant so I might be missing something.

The use case is, as Nickolay says, cases where you want to compose an API out of bits that come from various sources. For example, we have a Servant API, `CommonAPI`, and a function that has a type rather like this:

defaultMain :: HasServer specialAPI => Proxy specialAPI -> Server specialAPI -> ... -> IO a

the purpose of which is to serve CommonAPI :<|> specialAPI, plus a bunch of other common setup/teardown code. Most of the time there's interesting stuff in specialAPI, but in one or two cases there's nothing much to put there.

Re. the point about `a :<|> b` being like a pair: that's fine, but (up-to-isomorphism-and-ignoring-bottom) there's a unit for pairing, which is `()`. I imagine that there can't be an on-the-nose unit for (:<|>) because it's a concrete type constructor, so this one would have to be up-to-isomorphism too.

A client for EmptyAPI should do nothing; a server for EmptyAPI should never succeed. So I'd guess:

`Client EmptyAPI = ()`
`ServerT EmptyAPI m = m Void`
There's already a value called `emptyAPI` in `servant-docs` that would seem appropriate to use here.

Not sure how to implement `route` for the `HasServer` instance, but perhaps there would need to be another constructor in `Router'`?

Cheers,

David






On 15 May 2017 at 20:27, Alp Mestanogullari <[hidden email]> wrote:
If you can make a strong case for empty APIs, I would not be opposed to it. Not that my opinion is authoritative. For such a thing to be added to servant, I usually just want enough maintainers/users asking for it (unless it's reaaaallly ugly/hacky/fragile). But please allow me to challenge your case with more comments, hopefully you'll both find this discussion constructive.

On Mon, May 15, 2017 at 8:45 PM, Nickolay Kudasov <[hidden email]> wrote:
The problem is that we have some endpoints that should be POST endpoints, but some clients (SmartTVs in our case) surprisingly don't support POST. Other clients have some weird settings, driving POST unreasonable for them to use. So for a part of our API we have a GET endpoint for every POST endpoint. This means that we need to duplicate everything — API types, server handlers, client functions.

Couldn't you do some type-level and value-level transformations to do the duplication for you? You'd write the "sane" API and have some code inserting the GET-equivalents of each POST endpoint for you. It's admittedly not exactly trivial to do it, but allows you to simply build on top of the existing servant DSL.

To avoid this code duplication it would be nice to use Verb with a list of methods. In fact Verb is already poly-kinded and we can use a list of methods already:

type PostGet = Verb '[POST, GET] 200

For a server we can probably write something like this (untested): https://gist.github.com/fizruk/3bc23c90a33cd85f0e55167355ca6bda

There's an obvious corner case for Verb '[] and that'd be exactly the same as a unit for :<|>.

Verb '[] would mean "any method" I guess?
 
Also Verb '[method] would be equivallent to Verb method.

As you can see, handling Verb '[] is not really important for our project since we're only dealing with non-empty lists. However I still see the point in having empty API.
Just like David, we combine our API from a set of subAPIs which can have subAPIs of their own. When designing APIs it's sometimes known that there would be a certain subAPI, but its form is not defined yet. E.g.:
type MyAPI
    = "books" :> BooksAPI
 :<|> "services" :> ServicesAPI

type BooksAPI = ...          -- some defined API
type ServicesAPI = EmptyAPI  -- we're not sure what is going to be here

Currently one would have to resort to something like

type ServicesAPI = Get '[JSON] NoContent

Which does not describe well what's going on.

With EmptyAPI we can even add an optional type-level checker that'll ensure that there are no EmptyAPIs left.

That's actually an argument I don't have a counter-argument for.
 
To address Alp's concerns about instances (interpretations):
  • I think that to serve an empty API is to always fail (with 404). That way when you combine empty API with :<|> you immediately consider a non-empty part.
  • A Haskell client for an empty API is something isomorphic to (). E.g. data NoClient = NoClient

This is the bit I dislike the most I think. While for the other interpretations you can rely on some kind of `mempty` (empty docs, empty JS module, dummy server), this one just feels hacky to me. It makes no sense to me that we could derive something that we can't use. I think I'd prefer a solution where Client does not do anything on EmptyAPI. It's easy if you have say, 'Client (foo :<|> EmptyAPI), where you could just say it's 'Client foo', but what if you just ask for 'Client EmptyAPI'?
  • A JS client for an empty API would consist of no functions (empty module).
  • Documentation for an empty API is empty documentation with no endpoints.
Overall I think empty API can and should be a logically consistent part of Servant. There might be more nice uses for it. I would think there might be some uses in (whole) API manipulation functions.

I'm beginning to see a point in EmptyAPI, I just think there's a bit more work needed to flesh out exactly what it should be and how it should behave in the different interpretations. I basically don't want any part of it to feel like a hack, but rather have it be a principled solution that "just fits" with everything else.

But again, that's just my opinion, you know that I won't fight it if everyone else wants it. 

Kind regards,
Nick

On Mon, 15 May 2017 at 20:35 Alp Mestanogullari <[hidden email]> wrote:
The problem that I see with a notion of unit for :<|> is that you need a unit in API types, in server handlers, in client functions and in any other place where one uses :<|>. I never could come up with a notion of unit that made sense in all those places, or even in one of them.

Usually, people want a unit because they think of :<|> as an heterogeneous list, while it's actually closer to a pair. a :<|> b :<|> c ~ (a, (b, c)).

Would you mind expanding on your use case, so that we can try and figure something out?

On Mon, May 15, 2017 at 7:24 PM, David Turner <[hidden email]> wrote:
Hi,

Is there a unit of the :<|> operator defined anywhere? I can't immediately think why it shouldn't exist, but nor can we find it.

The use case is that we're defining APIs in a handful of places and want to combine them together, but sometimes there's nothing to serve in one of the places. Clearly we can define a dummy route that returns NoContent in this situation, but a unit would seem slicker.

Cheers,

David

--



--
Alp Mestanogullari

--



--
Alp Mestanogullari

--
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate

Re: Unit of :<|>?

Alp Mestanogullari
OK I think I'm convinced enough to give my +1 for a suitable PR that implements this all properly. Thank you guys for taking the time to make the case. Bonus points if the PR augments the tutorial with a section about this that documents the workflow of using this for "not implemented yet" situations.

On Mon, May 15, 2017 at 11:38 PM, Nickolay Kudasov <[hidden email]> wrote:
​Alp, thanks for the feedback!​

 
Couldn't you do some type-level and value-level transformations to do the duplication for you? You'd write the "sane" API and have some code inserting the GET-equivalents of each POST endpoint for you. It's admittedly not exactly trivial to do it, but allows you to simply build on top of the existing servant DSL.

​Yes, but as you say it's not exactly trivial and it's best done with a new combinator (e.g. homegrown). It's just that reusing Verb for it would be really nice.

Verb '[] would mean "any method" I guess?

​On the contrary, it would mean "no methods". The list is supposed to mention allowed methods.

This is the bit I dislike the most I think. While for the other interpretations you can rely on some kind of `mempty` (empty docs, empty JS module, dummy server), this one just feels hacky to me. It makes no sense to me that we could derive something that we can't use. I think I'd prefer a solution where Client does not do anything on EmptyAPI. It's easy if you have say, 'Client (foo :<|> EmptyAPI), where you could just say it's 'Client foo', but what if you just ask for 'Client EmptyAPI'?

​I don't think it's a hack. It's a placeholder for a bunch of client functions that are going to be there later.
In fact at work we use a similar unit type for parts of API where we are not interesting in generating Haskell client functions.​ We use generic client and it goes something like this:

data NotImplemented = NotImplemented

data MyClient = MyClient
  { ...
  , mySubApiClient :: NotImplemented
  ...
  }

instance (Client MyAPI ~ client) => ClientLike client MyClient where
  mkClient = genericMkClientP

I think that having unit types like NotImplemented or NoClient is good to state placeholders clearly.

Remember that there is going to be a corresponding NoServer handler. The "always failing server" will be in HasServer instance, but you still have to list something as a handler. Since there's nothing to handle, you have to use some unit type there too.

A client for EmptyAPI should do nothing; a server for EmptyAPI should never succeed. So I'd guess:
`Client EmptyAPI = ()`
`ServerT EmptyAPI m = m Void`

​David, I believe the type for Server should also be a unit type. It's only in the HasServer instance we have access to a proper failing mechanisms.
ServerT EmptyAPI m should tell us a type of handlers and since there's nothing to handle for an empty API we only need a trivial handler — a unit.

Kind regards,
Nick


On Tue, 16 May 2017 at 00:08 David Turner <[hidden email]> wrote:
Ok, so it's good to know that it doesn't currently exist, and I've had a brief look at implementing it and see the problems. I'm not that hot on all the type-level magic involved in Servant so I might be missing something.

The use case is, as Nickolay says, cases where you want to compose an API out of bits that come from various sources. For example, we have a Servant API, `CommonAPI`, and a function that has a type rather like this:

defaultMain :: HasServer specialAPI => Proxy specialAPI -> Server specialAPI -> ... -> IO a

the purpose of which is to serve CommonAPI :<|> specialAPI, plus a bunch of other common setup/teardown code. Most of the time there's interesting stuff in specialAPI, but in one or two cases there's nothing much to put there.

Re. the point about `a :<|> b` being like a pair: that's fine, but (up-to-isomorphism-and-ignoring-bottom) there's a unit for pairing, which is `()`. I imagine that there can't be an on-the-nose unit for (:<|>) because it's a concrete type constructor, so this one would have to be up-to-isomorphism too.

A client for EmptyAPI should do nothing; a server for EmptyAPI should never succeed. So I'd guess:

`Client EmptyAPI = ()`
`ServerT EmptyAPI m = m Void`
There's already a value called `emptyAPI` in `servant-docs` that would seem appropriate to use here.

Not sure how to implement `route` for the `HasServer` instance, but perhaps there would need to be another constructor in `Router'`?

Cheers,

David






On 15 May 2017 at 20:27, Alp Mestanogullari <[hidden email]> wrote:
If you can make a strong case for empty APIs, I would not be opposed to it. Not that my opinion is authoritative. For such a thing to be added to servant, I usually just want enough maintainers/users asking for it (unless it's reaaaallly ugly/hacky/fragile). But please allow me to challenge your case with more comments, hopefully you'll both find this discussion constructive.

On Mon, May 15, 2017 at 8:45 PM, Nickolay Kudasov <[hidden email]> wrote:
The problem is that we have some endpoints that should be POST endpoints, but some clients (SmartTVs in our case) surprisingly don't support POST. Other clients have some weird settings, driving POST unreasonable for them to use. So for a part of our API we have a GET endpoint for every POST endpoint. This means that we need to duplicate everything — API types, server handlers, client functions.

Couldn't you do some type-level and value-level transformations to do the duplication for you? You'd write the "sane" API and have some code inserting the GET-equivalents of each POST endpoint for you. It's admittedly not exactly trivial to do it, but allows you to simply build on top of the existing servant DSL.

To avoid this code duplication it would be nice to use Verb with a list of methods. In fact Verb is already poly-kinded and we can use a list of methods already:

type PostGet = Verb '[POST, GET] 200

For a server we can probably write something like this (untested): https://gist.github.com/fizruk/3bc23c90a33cd85f0e55167355ca6bda

There's an obvious corner case for Verb '[] and that'd be exactly the same as a unit for :<|>.

Verb '[] would mean "any method" I guess?
 
Also Verb '[method] would be equivallent to Verb method.

As you can see, handling Verb '[] is not really important for our project since we're only dealing with non-empty lists. However I still see the point in having empty API.
Just like David, we combine our API from a set of subAPIs which can have subAPIs of their own. When designing APIs it's sometimes known that there would be a certain subAPI, but its form is not defined yet. E.g.:
type MyAPI
    = "books" :> BooksAPI
 :<|> "services" :> ServicesAPI

type BooksAPI = ...          -- some defined API
type ServicesAPI = EmptyAPI  -- we're not sure what is going to be here

Currently one would have to resort to something like

type ServicesAPI = Get '[JSON] NoContent

Which does not describe well what's going on.

With EmptyAPI we can even add an optional type-level checker that'll ensure that there are no EmptyAPIs left.

That's actually an argument I don't have a counter-argument for.
 
To address Alp's concerns about instances (interpretations):
  • I think that to serve an empty API is to always fail (with 404). That way when you combine empty API with :<|> you immediately consider a non-empty part.
  • A Haskell client for an empty API is something isomorphic to (). E.g. data NoClient = NoClient

This is the bit I dislike the most I think. While for the other interpretations you can rely on some kind of `mempty` (empty docs, empty JS module, dummy server), this one just feels hacky to me. It makes no sense to me that we could derive something that we can't use. I think I'd prefer a solution where Client does not do anything on EmptyAPI. It's easy if you have say, 'Client (foo :<|> EmptyAPI), where you could just say it's 'Client foo', but what if you just ask for 'Client EmptyAPI'?
  • A JS client for an empty API would consist of no functions (empty module).
  • Documentation for an empty API is empty documentation with no endpoints.
Overall I think empty API can and should be a logically consistent part of Servant. There might be more nice uses for it. I would think there might be some uses in (whole) API manipulation functions.

I'm beginning to see a point in EmptyAPI, I just think there's a bit more work needed to flesh out exactly what it should be and how it should behave in the different interpretations. I basically don't want any part of it to feel like a hack, but rather have it be a principled solution that "just fits" with everything else.

But again, that's just my opinion, you know that I won't fight it if everyone else wants it. 

Kind regards,
Nick

On Mon, 15 May 2017 at 20:35 Alp Mestanogullari <[hidden email]> wrote:
The problem that I see with a notion of unit for :<|> is that you need a unit in API types, in server handlers, in client functions and in any other place where one uses :<|>. I never could come up with a notion of unit that made sense in all those places, or even in one of them.

Usually, people want a unit because they think of :<|> as an heterogeneous list, while it's actually closer to a pair. a :<|> b :<|> c ~ (a, (b, c)).

Would you mind expanding on your use case, so that we can try and figure something out?

On Mon, May 15, 2017 at 7:24 PM, David Turner <[hidden email]> wrote:
Hi,

Is there a unit of the :<|> operator defined anywhere? I can't immediately think why it shouldn't exist, but nor can we find it.

The use case is that we're defining APIs in a handful of places and want to combine them together, but sometimes there's nothing to serve in one of the places. Clearly we can define a dummy route that returns NoContent in this situation, but a unit would seem slicker.

Cheers,

David

--



--
Alp Mestanogullari

--



--
Alp Mestanogullari




--
Alp Mestanogullari

--
Loading...