Как я могу получить неявные преобразования для работы внутри коллекций?

Скажем, что у меня есть неявное преобразование:

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

Как я могу заставить это неявное преобразование работать с элементами списка?

Если у меня есть:

val listOfA: List[A] ...

и у меня есть функция, которая принимает список из B, можно ли позволить Scala неявно преобразовать все элементы из A в B?

Без неявных преобразований преобразование может выглядеть так:

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

Но я бы хотел, чтобы это произошло как "волшебство"... это слишком много, чтобы спросить.

Ответы

Ответ 1

Вот несколько альтернатив, которые вы, возможно, пожелаете рассмотреть:

1. Используйте привязку вида

Если возможно изменить функцию, которая принимает список Bs, это было бы самым простым решением. Измените его, чтобы принять список вещей, которые можно преобразовать в Bs. То есть

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

станет

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

Затем вы можете просто вызвать функцию с помощью listOfA:

yourFn(listOfA)

2. Ввести метод преобразования

Это похоже на первое решение Rogach, за исключением того, что внешнее преобразование неявно:

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

Затем на вашей функции call-site вы должны написать

yourFn(convert(listOfA))

Как и второе решение Rogach, это немного безопаснее, чем приведение неявного преобразования.

3. Внесите неявное преобразование

Это эквивалентно первому решению Rogach, но нотация немного лучше (IMO).

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

Если это преобразование находится в области на вашем сайте вызова, вы можете просто вызвать свою функцию с помощью listOfA:

yourFn(listOfA)

Размышления о мыслях

Интересно рассмотреть, как решить эту проблему в общем виде. Что делать, если я хочу определить свой метод преобразования, чтобы он мог обрабатывать любой тип, реализующий метод map? т.е.

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

Это не сработает, конечно, поскольку ничего в сигнатуре не выражает ограничение, которое C должно реализовать map. Насколько я знаю, выражение этого ограничения довольно активно и не может быть выполнено таким образом, чтобы обеспечить автономную поддержку любого типа, реализующего map. См. Понятия типа Scala.

Ответ 2

Ниже приведено общее решение, обеспечивающее неявное преобразование списков (List [A] = > List [B]), если неявное преобразование A = > B доступно в области видимости:

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([email protected])

Это то, что вам нужно?

Кроме того, поскольку у вас уже есть это неявное преобразование, вы можете просто написать:

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

Это будет немного более подробным, но дает вам более четкое управление вашим кодом.

Ответ 3

Я думаю, что лучшее, что возможно, это

implicit def asToBs(as: List[A]): List[B] = as map aToB

Ответ 4

Для полноты вы можете рассмотреть более общий подход, используя CanBuildFrom.

Эта программа:

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

Урожайность:

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, ?)