Haskell Servant:使用泛型时如何给路由加上前缀?

Haskell Servant: How to prefix routes when using generics?

我正在使用 Servant 泛型,并且我的路由有一个数据类型:

data Routes route = Routes
  { getLiveness :: route :- GetLiveness,
    getReadiness :: route :- GetReadiness,

    getAuthVerifyEmailToken :: route :- GetAuthVerifyEmailToken,
    postAuthEmail :: route :- PostAuthEmail,
    ...
  }
  deriving (Generic)

type BackendPrefix = "backend"

type AuthPrefix = "auth"

type GetLiveness = BackendPrefix :> "liveness" :> Get '[JSON] Text

type GetReadiness = BackendPrefix :> "readiness" :> Get '[JSON] Text

type GetAuthVerifyEmailToken = AuthPrefix :> "verify" :> "email" :> Capture "token" JWT :> RedirectResponse '[PlainText] NoContent

type PostAuthEmail = AuthPrefix :> "email" :> ReqBody '[JSON] AuthEmailRequest :> PostNoContent

前两个使用相同的前缀 "backend",所有其他的都使用 "auth" 前缀。

但是,我现在想将 "auth" 前缀更改为 "backend/auth"。所以我尝试更改:

type AuthPrefix = BackendPrefix :> "auth"

这会导致错误

>     • Expected a type, but
>       ‘"auth"’ has kind
>       ‘ghc-prim-0.6.1:GHC.Types.Symbol’
>     • In the second argument of ‘(:>)’, namely ‘"auth"’
>       In the type ‘BackendPrefix :> "auth"’
>       In the type declaration for ‘AuthPrefix’
>    |
> 34 | type AuthPrefix = BackendPrefix :> "auth"
>    |          

所以我用谷歌搜索,发现你可以 在不使用泛型时你可以这样做:

type APIv1 = "api" :> "v1" :> API

但我不知道如何使用泛型来做到这一点。

我想还有两个问题:

  1. 上述错误是什么意思,我可以使用类似 type AuthPrefix = BackendPrefix :> "auth" 的东西来创建更复杂的前缀吗?
  2. 在 Servant 中使用泛型时,有没有办法为一些路由添加一个前缀,而其他路由使用不同的前缀?

:>的定义是

data (path :: k) :> (a :: *)

注意右边的部分必须有种类*,也称为Type。这是一种“正常”Haskell 类型,如 IntBool 具有提升值。

但是,在类型级表达式BackendPrefix :> "auth"中,"auth"的种类是Symbol,也就是类型级字符串的种类。类型不匹配,导致类型错误。

(您可能想知道,为什么像 "foo" :> "bar" :> Post '[JSON] User 这样的路径会起作用?原因是完全应用的 Post 具有种类 Type,完全应用的 :> 也有种类 Type,并且 :> 关联到右边,如 "foo" :> ("bar" :> Post '[JSON] User),所以它全部检查出来。)

解决办法?也许给 AuthPrefix 一个类型参数,比如

type AuthPrefix restofpath = BackendPrefix :> "auth" :> restofpath

这样我们就不会在 Symbol.

中结束路由片段

我们现在使用 AuthPrefix 有点不同:

 AuthPrefix ("email" :> ReqBody '[JSON] AuthEmailRequest :> PostNoContent)

这是因为新定义已经负责将 :> 应用到路径的其余部分。

:>infixr 4,这意味着它是右结合的。考虑这种类型:

type GetFoo = "backend" :> "auth" :> "foo" :> Get '[JSON] Text

它被解释为好像你这样用括号括起来:

type GetFoo = "backend" :> ("auth" :> ("foo" :> Get '[JSON] Text))

使用您尝试的类型同义词,它会像这样被括起来:

type GetFoo = ("backend" :> "auth") :> ("foo" :> Get '[JSON] Text)

这显然不是一回事,事实上什至无效。


为了帮助理解,请考虑以下没有高级类型的普通 Haskell 代码:

xs = 1:2:3:4:5:6:[]
ys = 1:2:4:8:16:32:[]

现在想象一下你试着写这个:

zs = 1:2
xs = zs:3:4:5:6:[]
ys = zs:4:8:16:32:[]

它不起作用的原因与您无法使用类型同义词的原因完全相同。


最后一个例子:

x = 2 ^ 3 ^ 2 -- evaluates to 2 ^ (3 ^ 2) = 512
y = 2 ^ 3
x = y ^ 2 -- evaluates to (2 ^ 3) ^ 2 = 64