Reputation: 105
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
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
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