0

I have an application which is built on top of the Play/Lagom stack. I need to call a service which requires Source[T, NotUsed] to stream a file to it. This is the interface of the service:

def foo(fooId: UUID): ServiceCall[Source[ByteString, NotUsed], Source[String, NotUsed]]

I am therefore using Accumulator.source from the Play documentation, in the following way:

  private def doSomething(fooId: UUID): BodyParser[Future[Seq[String]]] = BodyParser { _ =>
    Accumulator.source[ByteString]
      .mapFuture { source: Source[ByteString, NotUsed] =>
        externalService
          .foo(fooId)
          .invoke(source)
          .map { x =>
            Right(x.runWith(Sink.seq[String]))
          }
      }
  }

Now, inside the mapFuture call, source's type is Source[ByteString, _], but if I change it to Source[ByteString, NotUsed] in order to call the service, as in the example above, I'm getting an error in the IDE that Source[ByteString, _] is expected. Isn't _ supposed to mean that I can change the type to whatever I want to, including NotUsed? On the other hand, shouldn't these two be semantically equivalent anyway? I found out that the Akka team introduced akka.NotUsed to replace Unit for when materialized values don't matter in some previous versions, but that still doesn't give me a clue on how to solve my problem.

The above snippet is similar to this example in the Play documentation on directing the body elsewhere.

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
arnaudoff
  • 686
  • 8
  • 20

1 Answers1

6

On the other hand, shouldn't these two be semantically equivalent anyway?

No. They are not equivalent because one is a subtype of another but not vice versa.

Source[ByteString, NotUsed] is a subtype of Source[ByteString, _]. And because Source is covariant Source[ByteString, _] is the same as Source[ByteString, Any].

implicitly[Source[ByteString, NotUsed] <:< Source[ByteString, _]]
implicitly[Source[ByteString, _] =:= Source[ByteString, Any]]
implicitly[Source[ByteString, Any] =:= Source[ByteString, _]]

Accumulator.source[ByteString] has type Accumulator[ByteString, Source[ByteString, _]]. .mapFuture(..) accepts Source[ByteString, _] => Future[B]. And since X => Y is covariant in Y but contravariant in X Source[ByteString, _] => Future[B] is a subtype of Source[ByteString, NotUsed] => Future[B])

implicitly[(Source[ByteString, _] => Future[B]) <:< (Source[ByteString, NotUsed] => Future[B])]

So you can use Source[ByteString, _] => Future[B] instead of Source[ByteString, NotUsed] => Future[B] but not vice versa.

(B seems to be Future[Either[Result, Future[Seq[String]]]].)

https://docs.scala-lang.org/tour/variances.html

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66