We used the WriterT monad transformer on Futures hoping to get more organized logs from an asynchronous application, but we ran into some trouble.
If I compile the application below, I get the following error. Please note that this isn't the warning about withFilter.
[error] value filter is not a member of scalaz.WriterT[scala.concurrent.Future,List[String],String]
Why does |@| need filter here? Does Scalaz provide an implicit conversion for this case?
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scalaz._
import Scalaz._
object Failure extends App {
type LoggedT[F[_], A] = WriterT[F, List[String], A]
type LoggedFuture[A] = LoggedT[Future, A]
//this is the applicative behavior that we want WriterT to preserve
val example = for {
z <- (Future("left") |@| Future("right")) { (x: String, y: String) => x + " " + y }
} yield z
example.onSuccess { case x => println(x) }
val test = for {
z: String <- (Future("left").liftM[LoggedT] |@| Future("right").liftM[LoggedT]) { (x: String, y: String) => x + " " + y }
} yield z
test.value.onSuccess { case x => println(x) }
}
The error occurs with Scala version: 2.11.7 and Scalaz version: 7.2.0
It's often convenient to use the reflection library's reify
to see what's happening during desugaring (this is the only time I'd ever suggest importing anything from scala.reflect.runtime
, and even in this case only in the REPL during development):
scala> import scala.reflect.runtime.universe.reify
import scala.reflect.runtime.universe.reify
scala> reify(for { x: Int <- Option(1) } yield x)
res5: reflect.runtime.universe.Expr[Option[Int]] =
Expr[scala.Option[Int]](Option.apply(1).withFilter(((check$ifrefutable$1) => check$ifrefutable$1: @unchecked match {
case (x @ (_: Int)) => true
case _ => false
})).map(((x: Int) => x)))
The problem is the type-case pattern match in the for-comprehension. Even though the compiler will verify that the match is safe (e.g. for { x: String <- Option(1) } yield x
won't compile), it's still desugared to a filtering operation. I'm not sure why—there's probably a reason for it, and there's even a small chance that it's a good reason.
You get a message about filter
because that's the compiler's last resort in this case. First it will try to desugar to a call to withFilter
, and if it doesn't find a withFilter
, it'll use filter
(which at least in the collection library's implementations is generally less efficient in cases like this). In the case of WriterT
it finds neither (since filtering a writer doesn't make sense in the general case), so it complains about the last one it tried.
The solution is just not to use type-case matching. Write a plain old for { z <- ... } yield z
and everything will work just fine. In fact I'd suggest not ever using type-case matching—it's the sneakiest way for runtime reflection-related problems to end up in your code. In this case the operation will be checked at compile-time (at least in the obvious cases), but it's still unnecessary and potentially less efficient.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With