Haskell仆人:使用泛型时如何为路由添加前缀?



我正在使用 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"前缀更改为"后端/身份验证"。所以我尝试了查点:

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

最新更新