Chain implicit conversion of collection
Asked Answered
V

0

3

I fail make an implicit conversion List[A] => List[B], given implicit conversion A => B.

There is a very related question that has a solution but does not work for me. Also, here is a nice documentation for chaining implicits, which I used as the base of my code below.

The point I try to make in the code below is that chaining implicits works fine in all expected cases, even for collection-like objects like the Container below, but fails for collections:

object ChainImplicits extends App{
  case class A(n: Int)
  implicit def toA(n: Int): A = A(n)

  case class B(m: Int, n: Int)
  implicit def aToB[T](a: T)(implicit f: T => A): B = B(a.n, a.n)

  case class C(m: Int, n: Int, o: Int) {
    def total = m + n + o
  }
  implicit def bToC[T](b: T)(implicit f: T => B): C = C(b.m, b.n, b.m + b.n)

  // works
  println(5.total)
  println(new A(5).total)
  println(new B(5, 5).total)
  println(new C(5, 5, 10).total)

  case class Container[T](value:T) {
    def map[B](f: T => B) = Container(f(value))
  }
  implicit def ContainerConv[A,B](container:Container[A])
                                 (implicit f: A => B): Container[B] = container.map(f)

  val container = Container(1)
  container.value.total //Works, as expected

  def containerCTotal(containerC: Container[C]) = containerC.value.total
  containerCTotal(container) //Works too!

  implicit def listConv[A,B](collection: List[A])
                            (implicit f: A => B): List[B] = collection.map(f)
  
  val list = List(1)
  def CTotals(list: List[C]) = list.map(_.total)
  CTotals(listConv(list)) //Explicit conversion works, finds the chain implicit conversions Int => C :)
  CTotals(list) //... but implicit does not :(

  def ATotals(list: List[A]) = list.map(_.total)
  ATotals(list) // Simple (non-chained) conversion of contained values does not work either :(
}

How can I make these last conversions work?

I also tried the (deprecated) view bounds, getting the same results:

implicit def convert[B, A <% B](l: List[A]): List[B] = l map { a => a: B }

Just in case, the compilation error is the expected:

type mismatch;
 found   : List[Int]
 required: List[ChainImplicits.C]
 CTotals(list) //... but implicit does not :(

And similarly for ATotals(list)

I tried it on scala versions 2.11.8 and 2.11.4.

UPDATE:

I confirmed after Andreas' comment that the underlying issue has something to do with the List being covariant. Giving covariance to the Container class above (i.e. Container[+A]) makes the previously working implicit conversion at containerCTotal(container) to fail.

Valorie answered 5/7, 2016 at 10:9 Comment(8)
My first guess is that this is due to the covariance of List[+A], which might prevent the compiler from resolving the implicit conversion chain. I'll do a quick search to see if I can find a confirmation and/or workaround.Offish
Good catch. By making the Container above covariant, containerCTotal(container) produced the same error.Valorie
Me too! I mean, yeah, this looks weird, and doesn't seem to have anything to do with covariance. Here is a simple (and even more baffling) way to demonstrate: val x: List[A] = List(1). This works. val list = List(1); val x: List[A] = list. This does not.Extortionate
In this case, I think that the implicit conversion is done directly on the value(s) provided on the list factory, i.e. you never get a List[Int], but directly get a List[A] in the first case.Valorie
@Extortionate with or without implicit conversion for lists in the scope ?Appendage
@Appendage yes, with everything the OP posted in scope.Extortionate
@V-Lamp, yes, I guess, you are right about why the first cas works.Extortionate
Well, it makes sense in a way. List is covariant on A, but A it appears in a contravariant position (as a parameter) here: implicit f: A => B. I guess, that's why the implicit conversion can't be applied.Oubliette

© 2022 - 2024 — McMap. All rights reserved.