Search code examples
scalaakkaimplicitakka-stream

Akka Streams: Why does the GraphDSL.Builder have to be marked as implicit?


I struggle to apply the idea of implicit in Scala to Akka Streams.

According to http://docs.scala-lang.org/overviews/core/implicit-classes.html, the basic concept of an implicit class in Scala is that for 5 times prinln("foo") an object of IntWithTimes is created, making the method times implicitly available through importing Helpers._.

object Helpers {
  implicit class IntWithTimes(x: Int) {
    def times[A](f: => A): Unit = {
      def loop(current: Int): Unit =
        if(current > 0) {
          f
          loop(current - 1)
        }
      loop(x)
    }
  }
}

Let's consider the following example:

val g = RunnableGraph.fromGraph(GraphDSL.create() {
  implicit builder: GraphDSL.Builder[Unit] =>
    import GraphDSL.Implicits._

    val in = Source(1 to 100)
    val flow = Flow[Int].map(_ + 1)
    val out = Sink.foreach(println)

    in ~> flow ~> out
    ClosedShape
})

g.run()

Obviously being new to Scala and Akka, my unsatisfying theory so far is that using create() of GraphDSL creates a RunnableGraph by passing the Builder into it.

Why does it have to be marked as implicit? If left away, the ~> operators cannot be resolved anymore - even though GraphDSL.Implicits._ is explicitly imported.


Solution

  • implicit has multiple use cases in Scala, one of them being the implicit classes you mention. However, there are also implicit parameters and implicit conversions. I recommend reading up on those, it's a bit much for the scope of this answer and would needlessly duplicate information. Note however that implicit classes are mostly syntactic sugar for a class with an implicit conversion, so they function the same.

    If left away, the ~> operators cannot be resolved anymore - even though GraphDSL.Implicits._ is explicitly imported.

    This imports the implicit conversions to FlowOps where ~> is actually defined. At this point Scala knows about the conversion. However to actually perform this it needs an implicit parameter b : Builder[_]. Take a look at the conversion definition in Implicits:

    implicit def fanOut2flow[I, O](j: UniformFanOutShape[I, O])(implicit b: Builder[_]): PortOps[O]
    

    Now you can mark any Builder[_] as implicit and Scala will fill this parameter in for you automatically if it's missing. Removing the implicit keyword from your Builder means that there is no longer any implicit value available to fill this parameter, which means the conversion can't happen and subsequently the method ~> is not available.

    You are however free to call the implicit conversion manually and fill in the missing parameter yourself (circumventing the whole implicit parameter functionality), but this would look very ugly/verbose in comparison:

    in ~> // .. and so on
    // would become
    fanOut2flow(in)(builder) ~>  // .. and so on