20

Say I have an implicit conversion:

implicit def aToB(a: A):B={
...
}

How can I get this implicit conversion to work on the elements of a List?

If I have:

val listOfA: List[A] ...

and I have a function that takes a List of B, is it possible to let Scala implicitly convert all of the elements from A's to B's?

Without implicit conversions, the conversion might look like:

lisftOfA.map(a => new B(a.someValue, a.anotherValue))

But I would love for this to happen like 'magic'... is that too much to ask.

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66
Jack
  • 16,506
  • 19
  • 100
  • 167

4 Answers4

18

Here are a few alternatives you might wish to consider:

1. Use a view bound

If it's possible to change the function that takes a List of Bs, this would be the simplest solution. Modify it to accept a List of things that can be converted to Bs. That is,

def yourFn(l: List[B]) = ...

would become

def yourFn[X <% B](l: List[X]) = ...

Then, you can just call the function with listOfA:

yourFn(listOfA)

2. Introduce a conversion method

This is similar to Rogach's first solution, except that the outer conversion is non-implicit:

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

Then at your function's call-site, you would write

yourFn(convert(listOfA))

Like Rogach's second solution, this is bit safer than bringing in an implicit conversion.

3. Introduce an implicit conversion

This is equivalent to Rogach's first solution, but the notation is a bit nicer (IMO).

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

If this conversion is in scope at your call-site, you can just call your function with the listOfA:

yourFn(listOfA)

Parting Thoughts

It's interesting to consider how to solve this problem in a general way. What if I want to define my conversion method so that it can handle any type that implements the map method? i.e.,

def convert[B, A <% B, C[_]](c: C[A]): C[B] = c map { a => a: B }

This won't work, of course, since nothing in the signature expresses the constraint that C must implement map. As far as I know, expressing this constraint is fairly involved and cannot be done in a way that provides out-of-the-box support for any type that implements map. See Type-safe Scala sequence comprehensions.

Aaron Novstrup
  • 20,967
  • 7
  • 70
  • 108
  • Thanks for the great examples and explanations. For one, I was wondering how to 'type cast' in the map, ie. a => a: B. Is there a name for doing that? – Jack Sep 17 '12 at 08:13
  • 2
    It's called *type ascription*. You're *ascribing* type `B` to an object of type `A`. This is in contrast to a *cast*, which uses the `asInstanceOf` method in Scala, in that it is checked at compile-time and can never result in a runtime exception. – Aaron Novstrup Sep 17 '12 at 10:12
  • This doesn't seem to be quite what @JacobusR was asking. We would like conversion to work 'inside' the `map` function which is a function of `listOfA`. You have introduced `yourFn` not mentioned by OP, but we need `listOfA.map((b: B) => ...)` to run as if it was `convert(listOfA).map((b: B) => ...)`. It seems your solution would only work if we defined `def yourFn[T](listOfB: List[B), fn: B => T): List[T] = listOfB.map(fn)`. i.e. if we wrapped `map` just to help with type inference, which is not what we want to do. – Silas Davis Sep 02 '15 at 11:22
  • @silasdavis It sounds like you have a different question than the OP. @JacobusR had a `List[A]` and a function that takes a `List[B]` that he wanted to apply to his `List[A]` -- I named this function `yourFn`, but it was the OP who introduced it in his question. – Aaron Novstrup Sep 02 '15 at 17:40
  • 1
    Has this been successfully tested? In [this question](http://stackoverflow.com/questions/38200682/chain-implicit-conversion-of-collection) I show that 3. (implicit conversions) don't actually work. Please have a look if possible :) – V-Lamp Jul 05 '16 at 13:24
  • Sadly, none of these are acceptable solutions anymore. View bounds (<%) are used in all of them, and they became deprecated since scala 2.10 a few years ago. Avoid using view bounds and use implicit parameters instead. – RoyB Nov 08 '16 at 16:11
3

The following is a general solution, enabling implicit conversion of lists (List[A] => List[B]) if implicit conversion A=>B is available in scope:

scala> class A
defined class A

scala> class B
defined class B

scala> implicit def a2b(a:A) = new B
a2b: (a: A)B

scala> implicit def mapI[A,B](l: List[A])(implicit conv: A => B): List[B] = l.map(conv)
mapI: [A, B](l: List[A])(implicit conv: (A) => B)List[B]

scala> List(new A): List[B]
res0: List[B] = List(B@efa0bf4)

Is this what you need?

Also, since you already have that implicit conversion, you can just write:

listOfA.map(a2b) // List[B]

It would be a bit more verbose, but gives you a bit more explicit control of your code.

Rogach
  • 26,050
  • 21
  • 93
  • 172
  • 2
    This does not really work: val list = List(new A) val listB: List[B] = list //this does not compile. check [this question](http://stackoverflow.com/questions/38200682/chain-implicit-conversion-of-collection) – V-Lamp Jul 05 '16 at 13:17
2

I think the best that is possible is

implicit def asToBs(as: List[A]): List[B] = as map aToB
jazmit
  • 5,170
  • 1
  • 29
  • 36
  • Are you sure, there will be no problems with type erasure? – om-nom-nom Sep 16 '12 at 15:19
  • 1
    @om-nom-nom - Why should there be any? Implicits are resolved before type erasure. – Rogach Sep 16 '12 at 15:29
  • +1 I did not follow this approach, but it's a refreshingly simple answer, thanks. – Jack Sep 17 '12 at 08:09
  • 2
    It does not work. Apparently list covariance confuses the compiler. Check [this question](http://stackoverflow.com/questions/38200682/chain-implicit-conversion-of-collection) please – V-Lamp Jul 05 '16 at 13:22
1

For completeness, you may wish to consider a more generic approach employing CanBuildFrom.

This program:

import scala.collection.generic.CanBuildFrom
import scala.language.{higherKinds, implicitConversions}

implicit def implyConvertedTraversable[A, B, C[X] <: Traversable[X]](as: C[A])(implicit conversion: A => B, cbf: CanBuildFrom[C[A], B, C[B]]): C[B] = {
  val builder = cbf(as)
  builder.sizeHint(as)
  builder ++= as.map(conversion)
  builder.result()
}

implicit def implyString(a: Int): String = a.toString

val intList = List(1, 2, 3)
val intStream = Stream(1, 2, 3)

val stringList: List[String] = intList
val stringStream: Stream[String] = intStream

Yields:

intList: List[Int] = List(1, 2, 3)
intStream: scala.collection.immutable.Stream[Int] = Stream(1, ?)

stringList: List[String] = List(1, 2, 3)
stringStream: Stream[String] = Stream(1, ?)
Michael Ahlers
  • 616
  • 7
  • 21