使用相同的方法生成派生特征,但在 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 => *]