使用相同的方法生成派生特征,但在 Scala 中的每个方法中没有第一个参数
Generate derived trait with same methods but without first parameter in each method in Scala
上下文
我在我的 scala 后端应用程序中定义了一些“客户端”接口,它们大致具有以下形状:
trait BackendClient {
def foo: FooClient
}
trait FooClient {
def doSomething(authToken: AuthNToken, request: MyBarRequest): Future[Unit]
def getSomething(authToken: AuthNToken, id: MyId): Future[MyResource]
}
我想在后端的方法签名中保留 authToken
以提醒实现他们应该为每个方法调用检查身份验证。
虽然在前端,到处传递相同的令牌变得很烦人,所以理想情况下我想要一个“经过身份验证的”客户端:
// Would like to auto-generate this given `FooClient`
trait AuthenticatedFooClient {
def doSomething(request: MyBarRequest): Future[Unit]
def getSomething(id: MyId): Future[MyResource]
}
// Auto-generating this too would be nice but not as important
class AuthenticatedFooClientImpl(authToken: AuthNToken, delegate: FooClient) extends AuthenticatedFooClient {
override def doSomething(request: MyBarRequest): Future[Unit] = delegate.doSomething(authToken, request)
override def getSomething(id: MyId): Future[MyResource] = delegate.getSoemthing(authToken, id)
}
这样我可以在前端有这样的东西:
trait AuthenticatedBackendClient {
def foo: AuthenticatedFooClient
}
class FrontendAuthServiceImpl(backendClient: BackendClient) extends FrontendAuthService {
private val getTokenOrAuthenticate: Task[AuthNToken] = Task { ... }.memoizeOnSuccess
val backendClient: Task[AuthenticatedBackendClient] = for {
authToken <- getTokenOrAuthenticate
authenticatedFoo = new AuthenticatedFooClientImpl(authToken, backendClient.foo)
} yield new AuthenticatedBackendClient {
override def foo: AuthenticatedFooClient = authenticatedFoo
}
}
...
...
...
val someId: MyId = ...
val myResource: Task[MyResource] = for {
backendClient <- frontendAuthService.backendClient
myResource <- Task.fromFuture(backendClient.foo.getSomething(someId))
} yield myResource
问题
我宁愿不必手动维护我的客户端特征的 2 个副本,一个在方法中有 auth 令牌,一个没有。
我很确定我可以编写自己的注释处理器(我知道宏在 scala 中可能更惯用,但我从来没有真正开始学习它们)来生成我想要的代码,但这感觉比如应该可以用现有的库/工具解决的东西。
具体来说,感觉我应该能够生成一个与另一个特征相同的特征,但会删除所有其他特征方法的第一个参数。
有什么想法吗?
可以使用更高类型的参数,根据情况对客户端进行参数化:
trait GenFooClient[F[_]] {
def doSomething(request: MyBarRequest): F[Future[Unit]]
def getSomething(id: MyId): F[Future[MyResource]]
}
type Id[A] = A
type AuthenticatedFooClient = FooClient[Id]
type FromToken[A] = AuthNToken => A
type FooClient = GenFooClient[FromToken]
def authenticate(f: FooClient, token: AuthNToken): AuthenticatedFooClient =
new GenFooClient[Id] {
def doSomething(request: MyBarRequest): Future[Unit] =
f.doSomething(request)(token)
def getSomething(id: MyId): Future[MyResource] =
f.getSomething(id)(token)
}
如果使用 kind-projector 插件,写起来会容易一些:
// no need for `FromToken` type alias
type FooClient = GenFooClient[AuthNToken => *]
上下文
我在我的 scala 后端应用程序中定义了一些“客户端”接口,它们大致具有以下形状:
trait BackendClient {
def foo: FooClient
}
trait FooClient {
def doSomething(authToken: AuthNToken, request: MyBarRequest): Future[Unit]
def getSomething(authToken: AuthNToken, id: MyId): Future[MyResource]
}
我想在后端的方法签名中保留 authToken
以提醒实现他们应该为每个方法调用检查身份验证。
虽然在前端,到处传递相同的令牌变得很烦人,所以理想情况下我想要一个“经过身份验证的”客户端:
// Would like to auto-generate this given `FooClient`
trait AuthenticatedFooClient {
def doSomething(request: MyBarRequest): Future[Unit]
def getSomething(id: MyId): Future[MyResource]
}
// Auto-generating this too would be nice but not as important
class AuthenticatedFooClientImpl(authToken: AuthNToken, delegate: FooClient) extends AuthenticatedFooClient {
override def doSomething(request: MyBarRequest): Future[Unit] = delegate.doSomething(authToken, request)
override def getSomething(id: MyId): Future[MyResource] = delegate.getSoemthing(authToken, id)
}
这样我可以在前端有这样的东西:
trait AuthenticatedBackendClient {
def foo: AuthenticatedFooClient
}
class FrontendAuthServiceImpl(backendClient: BackendClient) extends FrontendAuthService {
private val getTokenOrAuthenticate: Task[AuthNToken] = Task { ... }.memoizeOnSuccess
val backendClient: Task[AuthenticatedBackendClient] = for {
authToken <- getTokenOrAuthenticate
authenticatedFoo = new AuthenticatedFooClientImpl(authToken, backendClient.foo)
} yield new AuthenticatedBackendClient {
override def foo: AuthenticatedFooClient = authenticatedFoo
}
}
...
...
...
val someId: MyId = ...
val myResource: Task[MyResource] = for {
backendClient <- frontendAuthService.backendClient
myResource <- Task.fromFuture(backendClient.foo.getSomething(someId))
} yield myResource
问题
我宁愿不必手动维护我的客户端特征的 2 个副本,一个在方法中有 auth 令牌,一个没有。
我很确定我可以编写自己的注释处理器(我知道宏在 scala 中可能更惯用,但我从来没有真正开始学习它们)来生成我想要的代码,但这感觉比如应该可以用现有的库/工具解决的东西。
具体来说,感觉我应该能够生成一个与另一个特征相同的特征,但会删除所有其他特征方法的第一个参数。
有什么想法吗?
可以使用更高类型的参数,根据情况对客户端进行参数化:
trait GenFooClient[F[_]] {
def doSomething(request: MyBarRequest): F[Future[Unit]]
def getSomething(id: MyId): F[Future[MyResource]]
}
type Id[A] = A
type AuthenticatedFooClient = FooClient[Id]
type FromToken[A] = AuthNToken => A
type FooClient = GenFooClient[FromToken]
def authenticate(f: FooClient, token: AuthNToken): AuthenticatedFooClient =
new GenFooClient[Id] {
def doSomething(request: MyBarRequest): Future[Unit] =
f.doSomething(request)(token)
def getSomething(id: MyId): Future[MyResource] =
f.getSomething(id)(token)
}
如果使用 kind-projector 插件,写起来会容易一些:
// no need for `FromToken` type alias
type FooClient = GenFooClient[AuthNToken => *]