Using servant, I've got a type like the following, but more complex:
type MyAPI endpointTail result = "blah" :> Capture "a" A :> endpointTail :> Get '[JSON] result
Which means I can do things like this:
MyAPI "hello" HelloT
but when I do:
MyAPI "hello/world" HelloWorldT
Servant silently fails to produce my endpoint correctly, presumably because it doesn't expect a literal slash
When I try:
MyAPI ("hello" :> "world") HelloWorldT
I get a type error because :>
is only defined when the right argument is of kind *
, which "world"
is not, it's of kind Symbol
.
It seems for servant
to work correctly, the :>
has to be applied in a right associative fashion, one can't just add brackets willy nilly. So what I think I need is something like this:
type MyAPIF endpointTailF result = "blah" :> Capture "a" A :> endpointTail (Get '[JSON] result)
Note endpointTail
is now a type function, endpointTailF
.
Then I could do
type Blah t = "hello" :> "world" :> t
MyAPIF Blah HelloWorldT
But now I've got the issue of the compiler saying it doesn't like Blah
being partially applied.
So in summary, I've got a nice reusable type
which I'd like to keep using, I'd just like to be able to pass more parameters. If these were values I'd be able to do this easily, by passing a function "continuation" style, but I'm not sure of the solution in the type world. Any ideas?
Your deductions are all correct. Unless you want to crawl down the rabbit hole that is singleton defunctionalization (the usual method for working with partially applied type functions), you'll want to avoid treating type functions as arguments. Use a plain data structure instead.
For example, if you write a type function that prefixes a type using a list of URL path segments:
type PrefixWith :: [Symbol] -> Type -> Type
type family PrefixWith segs t where
PrefixWith (seg:segs) t = seg :> PrefixWith segs t
PrefixWith '[] t = t
and then incorporate a call to this function within your API:
type MyAPI endpointTail result
= "blah"
:> Capture "a" A
:> PrefixWith endpointTail (Get '[JSON] result)
then this lets you write:
MyAPI ["hello","world"] HelloWorldT
Full code example:
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE StandaloneKindSignatures #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE TypeFamilies #-}
import Servant
import Data.Aeson
import Data.Kind
import GHC.Generics
import GHC.TypeLits
import Network.Wai.Handler.Warp
type PrefixWith :: [Symbol] -> Type -> Type
type family PrefixWith segs t where
PrefixWith (seg:segs) t = seg :> PrefixWith segs t
PrefixWith '[] t = t
type MyAPI endpointTail result
= "blah"
:> Capture "a" A
:> PrefixWith endpointTail (Get '[JSON] result)
data HelloWorldT = HelloWorld { success :: Int } deriving (Generic)
instance ToJSON HelloWorldT
type A = Int
main :: IO ()
main = run 8080 (serve (Proxy @(MyAPI ["hello", "world"] HelloWorldT))
(\n -> return (HelloWorld n)))
-- example URL, http://localhost:8080/blah/1/hello/world