Type class derivation in Scala 3

 

Chris Birchall

47 Degrees

Agenda

  • Type class derivation in Scala 2
  • In Haskell and Rust
  • New features in Scala 3
  • Show
  • Functor
  • Macros: generating gRPC clients and servers

Status quo in Scala 2

  • Implementation choices
    • Magnolia
    • Shapeless
    • Hand-rolled macros
  • A few standard usage patterns
    • define in TC's companion object
    • import io.circe.generic.auto._
    • import io.circe.generic.semiauto._

Haskell

  • `deriving` keyword to generate TC instances
  • Language extensions galore
    • DeriveFunctor, DeriveFoldable, DeriveTraversable
    • DeriveGeneric
    • DeriveAnyClass
    • DeriveDataTypeable
    • DerivingStrategies
  • Sky's the limit with DerivingVia
  • Print derived instances with -ddump-deriv

Rust

  • #[derive(...)] attribute
  • Auto-derivation of stock traits (e.g. Eq)
  • Derive custom traits using procedural macros

New features in Scala 3

  • derives keyword
  • Mirror

Live coding 😅

gRPC client/server generation

  • Feature of Mu, a microservices framework
  • Currently implemented as a macro annotation

gRPC client/server generation

(in Scala 2)

message HelloRequest {
  string name = 1;
}

message HelloResponse {
  string name = 1;
}

service Greeter {
  rpc SayHello(HelloRequest)
      returns (HelloResponse);
}
greeter.proto
case class HelloRequest(name: String)

case class HelloResponse(greeting: String)

@service(Protobuf)
trait Greeter[F[_]] {
  def SayHello(req: HelloRequest): F[HelloResponse]
}
Greeter.scala
  • srcgen using sbt plugin
  • write by hand

gRPC client/server generation

(in Scala 2)

@service(Protobuf)
trait Greeter[F[_]] {
  def SayHello(req: HelloRequest): F[HelloResponse]
}

// Generated by macro annotation
object Greeter {

  def toServerServiceDefinition[F: ConcurrentEffect](
    service: Greeter[F]
  ): io.grpc.ServerServiceDefinition
  
  def client[F: ConcurrentEffect](
    host: String, port: Int
  ): Resource[F, Greeter[F]]

}

gRPC client/server generation

(in Scala 2)

Macro takes care of

  • mapping methods to RPC endpoints
  • Protobuf marshalling of requests/responses

gRPC client/server generation

Plan for Scala 3

  • move server/client factory methods to type classes
  • derive instances using macros
trait GrpcServerSide[S[_[_]]] {

  def [F[_]: ConcurrentEffect] (service: S[F])
    .toServerServiceDefinition: io.grpc.ServerServiceDefinition
  
}

trait GrpcClientSide[S[_[_]]] {

  def client[F: ConcurrentEffect](
    host: String, port: Int
  ): Resource[F, S[F]]

}

gRPC client/server generation

(in Scala 3)

message HelloRequest {
  string name = 1;
}

message HelloResponse {
  string name = 1;
}

service Greeter {
  rpc SayHello(HelloRequest)
      returns (HelloResponse);
}
greeter.proto
case class HelloRequest(name: String)

case class HelloResponse(greeting: String)

@service(Protobuf)
trait Greeter[F[_]] derives GrpcServerSide, GrpcClientSide {
  def SayHello(req: HelloRequest): F[HelloResponse]
}
Greeter.scala
  • srcgen using sbt plugin
  • write by hand

gRPC client/server generation

(in Scala 3)

class MyGreeter[F[_]: Applicative] extends Greeter[F] {
  def SayHello(req: HelloRequest): F[HelloResponse] =
    HelloResponse(s"Hi, ${req.name}!").pure
}

val serverServiceDef =
  new MyGreeter[IO].toServerServiceDefinition

val client =
  summon[GrpcClientSide[Greeter]].client[IO]("localhost", 8080)

Summary

  • `derives` clause + `derived` method

  • Extension methods
  • `using` and `given`
  • `inline`
  • Derivation using mirrors
  • Derivation using a macro
Made with Slides.com