DJ Chen
DJ Chen

Reputation: 105

Multiple colons(:) after class type parameter

When I went through the Example code of Kafka4S, I could not understand what exactly do the multiple colons(:) after F[_] mean. After searching around, re-reading the Scala language spec, I can only guess that this code means that F[_] has the 3 types (Concurrent, ContextShift, Timer) as mixin?

final class StreamProducer[F[_] : Concurrent : ContextShift : Timer] {

  val example: F[Unit] =
    for {
      _ <- Sync[F].delay(println("Starting kafka4s example"))
      _ <- AdminApi.createTopicsIdempotent[F](kafkaBootstrapServers, topic)
      writeStream = Stream
        .resource(ProducerApi.resource[F, Int, Int](BootstrapServers(kafkaBootstrapServers)))
        .flatMap { producer =>
          Stream
            .awakeDelay[F](1.second)
            .evalMap { _ =>
              Sync[F].delay(Random.nextInt()).flatMap { i =>
                producer.sendAndForget(new ProducerRecord(topic.name, i, i))
              }
            }
        }
    } yield ()

Upvotes: 3

Views: 427

Answers (2)

Mario Galic
Mario Galic

Reputation: 48420

this code means that F[] has the 3 types (Concurrent, ContextShift, Timer) as mixin?

No that is not the right way to think about it, although at first glance it might look like that given Scala 2 reuses same language facilities to implement different object-oriented and functional programming concepts.

Consider first just the syntactic level; given

trait Concurrent[F[_]]   { def foos }
trait ContextShift[F[_]] { def bars }
trait Timer[F[_]]        { def zars }

then shorthand

class StreamProducer[F[_] : Concurrent : ContextShift : Timer]

expands to

class StreamProducer[F[_]](implicit ev1: Concurrent[F], ev2: ContextShift[F], ev3: Timer[F]) {
  ev1.foo // is available
  ev2.bar // is available
  ev3.zar // is available
}

Note how foo, bar and zar capabilities become available in the body of StreamProducer. The implementation of these capabilities are provided via implicit evidence values like so

class Qux[T] // some type constructor which maps types to types

implicit val quxConcurrent: Concurrent[Qux] = new Concurrent[Qux] {
  def foo = ???
}
implicit val quxContextShift: ContextShift[Qux] = new ContextShift[Qux] {
  def bar = ???
}
implicit val quxTimer: Timer[Qux] = new Timer[Qux] {
  def zar = ???
}

Given these implicit values, then compiler is able to automagically pass them in to the constructor of StreamProducer at call site so

new StreamProducer[Qux]

magically expands to

new StreamProducer[Qux](quxConcurrent, quxContextShift, quxTimer)

Now that was just meant to show the mechanics of the boring syntax (which is greatly clarified in Scala 3). What is much more interesting is the design pattern these mechanics intend to convey, namely, type classes. Conceptually they enable polymorphic types parameters such as F[_] to gain capabilities without us having to invent complicated inheritance hierarchies.

Upvotes: 3

Dima
Dima

Reputation: 40500

Close, but no. If you wanted to say, that F is a subclass of those three things, you would write it as F[_] <: Concurrent with ContextShift with Timer.

What those colons mean is that F belongs to those three type classes. If you are not familiar with the concept of type class, I'd recommend to start with some quick intro. Like here, or here, or here, but in a nutshell what that syntax means is that for every type F that is valid in that context, there will be instances of Concurrent[F], ContextShift[F] and Timer[F] implicitly available.

In general, def foo[Bar : Baz : Bat : Bak]() is a shortcut for def foo[Bar]()(implicit z: Baz[Bar], t: Bat[Bar], k: Bak[Bar])

Upvotes: 6

Related Questions