How can I get implicit conversions to work inside collections?
Asked Answered
V

4

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.

Valdes answered 16/9, 2012 at 15:1 Comment(0)
K
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.

Kempis answered 16/9, 2012 at 23:23 Comment(6)
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?Valdes
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.Kempis
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.Limn
@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.Kempis
Has this been successfully tested? In this question I show that 3. (implicit conversions) don't actually work. Please have a look if possible :)Casting
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.Flat
R
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.

Richie answered 16/9, 2012 at 15:15 Comment(1)
This does not really work: val list = List(new A) val listB: List[B] = list //this does not compile. check this questionCasting
F
2

I think the best that is possible is

implicit def asToBs(as: List[A]): List[B] = as map aToB
Feldspar answered 16/9, 2012 at 15:16 Comment(4)
Are you sure, there will be no problems with type erasure?Schoening
@Schoening - Why should there be any? Implicits are resolved before type erasure.Richie
+1 I did not follow this approach, but it's a refreshingly simple answer, thanks.Valdes
It does not work. Apparently list covariance confuses the compiler. Check this question pleaseCasting
A
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, ?)
Accouter answered 22/6, 2016 at 22:21 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.