Manuel Leduc Manuel Leduc - 1 month ago 18
Scala Question

Stacking implicits conversions

I'm trying to make my use of futures as lightweight as possible.

Here is my current test code :

import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.Future
import scala.util.Failure
import scala.util.Success

object Test1 extends App {

def lift[A](a: A): Future[A] = Future { a }

def ap[A, B](fct: Future[A => B], param: Future[A]): Future[B] =
for {
c <- param
o <- fct
} yield (o(c))

implicit def valToFuture[A](elem: A): Future[A] = lift(elem)
implicit class FutureInfix[A, B](val x: Future[A => B]) extends AnyVal {
def <*>(that: Future[A]) = ap(this.x, that)
}

val plus1: Int => Int = (x: Int) => x + 1
val cst1: Int = 1

val extracted: Future[Int] = ap(plus1, cst1)
val extracted2: Future[Int] = lift(plus1) <*> lift(cst1)
val extracted3: Future[Int] = plus1 <*> cst
/*
* - value <*> is not a member of Int ⇒ Int
* - not found: value cst
*/
}


extracted and extracted2 are working but are each using only one of the two implicit conversion I've defined.

extracted3 is what I am aiming at, implicitly lifting plus and cst1 to Future[X] values and converting
a <*> b
to
ap(a,b)
. But the compiler does not seems to agree with me.

Is what i'm trying to achieve possible ? If so, what do I have to change to make it works ?

PS : This i kind of inspired from what I've seen using haskell.

Answer

I think you're bumping up against the "one at a time" rule.

From Programming in Scala, (1st edition):

One-at-a-time Rule: Only one implicit is tried. The compiler will never rewrite x + y to convert1(convert2(x)) + y. Doing so would cause compile times to increase dramatically on erroneous code, and it would increase the difference between what the programmer writes and what the program actually does. For sanity's sake, the compiler does not insert further implicit conversions when it is already in the middle of trying another implicit.


As @tkachuko has pointed out, there likely is a work-around for this limitation using implicit parameters, but one simple solution is to split FutureInfix into two parts.

implicit class FromFutureInfix[A, B](x: Future[A => B]) {
  def <*>(that: Future[A]) = ap(x, that)
}
implicit class ToFutureInfix[A, B](x: A => B) {
  def <*>(that: A) = ap(x, that)
}
Comments