合并类型以用于服务器端点

huangapple go评论61阅读模式
英文:

Combining types for a servant endpoint

问题

以下是您要求的代码部分的翻译:

type MyAPI endpointTail result = "blah" :> Capture "a" A :> endpointTail :> Get '[JSON] result
MyAPI "hello" HelloT
MyAPI "hello/world" HelloWorldT
MyAPI ("hello" :> "world") HelloWorldT
type MyAPIF endpointTailF result = "blah" :> Capture "a" A :> endpointTail (Get '[JSON] result)
type Blah t = "hello" :> "world" :> t

MyAPIF Blah HelloWorldT

希望这些翻译对您有所帮助。如果您有其他问题或需要进一步的帮助,请随时提出。

英文:

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?

答案1

得分: 1

你的推断都是正确的。除非你想深入研究单例去功能化(通常用于处理部分应用类型函数的方法),否则你应该避免将类型函数视为参数。而是使用一个普通的数据结构。

举个例子,如果你编写一个类型函数,它使用 URL 路径段的列表作为前缀:

type PrefixWith :: [Symbol] -> Type -> Type
type family PrefixWith segs t where
  PrefixWith (seg:segs) t = seg :> PrefixWith segs t
  PrefixWith '[] t = t

然后在你的 API 中调用这个函数:

type MyAPI endpointTail result
  = "blah"
  :> Capture "a" A
  :> PrefixWith endpointTail (Get '[JSON] result)

这使你能够编写以下代码:

MyAPI ["hello","world"] HelloWorldT

完整的代码示例:

{-# 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)))
-- 例如的URL,http://localhost:8080/blah/1/hello/world
英文:

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

huangapple
  • 本文由 发表于 2023年3月7日 08:44:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/75657102.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定