Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Scalaz: what role does filter play in |@|?

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

like image 645
Wouter Stekelenburg Avatar asked Dec 31 '15 16:12

Wouter Stekelenburg


1 Answers

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.

like image 80
Travis Brown Avatar answered Nov 15 '22 05:11

Travis Brown