Start thinking about Scala 3
cb372 opened this issue · 1 comments
Scala 3 is just around the corner ("late 2020"). The macro annotation will not work, so we need to find a new way to generate servers and clients from service definitions.
I played around with Scala 3's generic derivation feature and macros today, and I think it might work for our use case. I couldn't get my code to compile (documentation is almost non-existent!) but I think it can work in theory.
My idea was to define type classes in Mu containing the various server/client factory methods, e.g.
trait RPCClientSide[S[_[_]]] {
def unsafeClient[F[_]: Async](channelFor: ChannelFor, serializationType: SerializationType): S[F]
}
trait RPCServerSide[S[_[_]]] {
// serialization type, compression type, other options would also be passed as arguments here
def bindService[F[_]: Async](service: S[F]): F[ServerServiceDefinition]
}
and implement derived
methods for each of them using macros and TASTy reflection:
import scala.quoted._
object RPCClientSide {
given gen[S[_[_]]: Type](using qctx: QuoteContext) as Expr[RPCClientSide[S]] = {
import qctx.tasty._
'{
new RPCClientSide[S] {
def unsafeClient[F[_]: Async](channelFor: ChannelFor, serializationType: SerializationType): S[F] =
throw new Exception("TODO generate the client implementation")
}
}
}
implicit inline def derived[S[_[_]]]: RPCClient[S] = ${ RPCClient.gen[S] }
}
Then the service definition trait would have a derives
clause instead of a @service
annotation:
trait MyService[F[_]] derives RPCServerSide, RPCClientSide {
def sayHello(req: HelloRequest): F[HelloResponse]
}
(We don't have to split the client and server side into separate type classes, it's just an idea.)
The derived type class instance can be summon
ed:
val channelFor = ...
val instance = summon[RPCClientSide[MyService]]
val client = instance.unsafeClient[IO](channelFor, Protobuf)
client.sayHello(HelloRequest("Chris"))
Another option is to do two stages of source code generation, as explored in #632. But that POC is based on a Scala 2 compiler plugin, so it would need to be rewritten.
There's a working proof-of-concept macro here to generate a gRPC service definition from a service instance.
There are also a few slides to explain the idea.
I made a few simplifications for the POC, e.g. the service trait is Greeter
, not Greeter[F[_]]
, and we don't use PBDirect to derive the protobuf marshaller. But I think it's enough to show that this idea would work. I won't do any more work on this for now, because:
- the dotty metaprogramming features are still quite buggy and under-documented. It's a bit early to start using them for anything proper.
- we will need dotty cross-builds of cats, cats-effect, PBDirect, avro4s and many other dependencies before we can support dotty in Mu