I'm reading the excellent Why is servant a type-level DSL?. I've got to the section where the problem with the implementation presented so far is that the number of captures in an Endpoint
can vary and there is no way of implementing a total a link generating function without dependant types.
The Endpoint
definition is:
data Method = Get | Post
data Endpoint = Static String Endpoint
| Capture Endpoint
| Verb Method
and example definitions are:
getHello :: Endpoint
getHello = Static "hello" (Capture (Verb Get))
twoCaptures :: Endpoint
twoCaptures = Capture (Capture (Verb Post))
noCaptures :: Endpoint
noCaptures = Static "hello" (Verb Post)
and the non-total link making function is:
linkTo :: Endpoint -> [String] -> Link
linkTo (Static str rest) captureValues = str : linkTo rest captureValues
linkTo (Capture rest) (c : cs) = c : linkTo rest cs
linkTo (Capture rest) [] = error "linkTo: capture value needed but the list is empty" -- :-(
linkTo (Verb method) _ = []
I'm intrigued by the following:
Fortunately, GADTs can help here. We could turn
Endpoint
into a GADT that tracks captures and then use some type-level computations to get the type of the link-making function from our list of captures, as well as define the link making function through typeclass instances that would go through the captures and add an argument for each of them ... a GADT-based approach would work well (in addition to being more approachable) for very stable domains, and is not considered here because of the kind of flexibility we are asking for.
I'm interested in experimenting with the GADT approach however I could do with some hints for how to create a GADT which will "track captures and then use some type-level computations to get the type of the link-making function from our list of captures"
Can anyone give me some hints for how to get started with a GADT version. Thanks.