Scala: заставить неявное преобразование A->B работать для Option[A] -> Option[B]
Я пытаюсь написать функцию, которая повторно использует неявные преобразования, которые у меня есть для Объекта A -> Объекта B, когда они обернуты в Option общим способом, чтобы преобразования Option[A] -> Option[B] также Работа.
То, что я придумал, это:
implicit def fromOptionToOption[A, B](from: Option[A])(implicit conversion: (A) => B): Option[B] = from.map(conversion(_))
Это работает, когда я присваиваю Some (..) значению, но не когда я присваиваю Option val; см. следующий вывод консоли:
scala> trait T
defined trait T
scala> case class Foo(i: Int) extends T
defined class Foo
scala> case class Bar(i: Int) extends T
defined class Bar
scala> implicit def fromFooToBar(f: Foo):Bar = Bar(f.i)
fromFooToBar: (f: Foo)Bar
scala> implicit def fromBarToFoo(b: Bar):Foo = Foo(b.i)
fromBarToFoo: (b: Bar)Foo
scala> implicit def fromOptionToOption[A, B](from: Option[A])(implicit conversion: (A) => B): Option[B] = from.map(conversion(_))
fromOptionToOption: [A, B](from: Option[A])(implicit conversion: (A) => B)Option[B]
scala> val foo: Option[Foo] = Some(Bar(1))
foo: Option[Foo] = Some(Foo(1))
// THIS WORKS as expected
scala> val fooOpt = Some(Foo(4))
fooOpt: Some[Foo] = Some(Foo(4))
scala> val barOpt2: Option[Bar] = fooOpt
<console>:16: error: type mismatch;
found : Some[Foo]
required: Option[Bar]
val barOpt2: Option[Bar] = fooOpt
^
//THIS FAILS.
Я действительно не вижу разницы между первым и вторым преобразованием. Каким-то образом это не вызывает неявное преобразование в последнем. Я предполагаю, что это как-то связано с системой типов, но пока не вижу, как именно. Есть идеи? -Альберт (я на скале 2.9.1)
5 ответов
Вот подсказка:
scala> val fooOpt: Option[Bar] = Option(Foo(1))
fooOpt: Option[Bar] = Some(Bar(1))
И другой:
scala> implicit def foobar(x: String): Int = augmentString(x).toInt
foobar: (x: String)Int
scala> val y: Option[String] = Option(1)
y: Option[String] = Some(1)
scala> val y: Option[Int] = Option("1")
y: Option[Int] = Some(1)
Похоже на законно странную ошибку. Я бы открыл меньший тестовый пример и открыл проблему (или искал ее в JIRA).
Как в сторону:
Вы можете использовать некоторую теорию категорий для обработки множества различных типов вещей "Option-ish".
package object fun {
trait Functor[Container[_]] {
def fmap[A,B](x: Container[A], f: A => B): Container[B]
}
object Functor {
implicit object optionFunctor extends Functor[Option] {
override def fmap[A,B](x: Option[A], f: A => B): Option[B] = x map f
}
// Note: With some CanBuildFrom magic, we can support Traversables here.
}
implicit def liftConversion[F[_], A, B](x: F[A])(implicit f: A => B, functor: Functor[F]): F[B] =
functor.fmap(x,f)
}
Это немного сложнее, так как вы отображаете некоторую теорию категорий FP на проблему, но это более общее решение для переноса неявных диалогов в контейнеры по мере необходимости. Обратите внимание, как они объединяются в цепочку, используя один неявный метод диалога, который принимает более ограниченный неявный аргумент.
ТАКЖЕ, это должно заставить работать примеры:
scala> val tmp = Option(Foo(1))
tmp: Option[Foo] = Some(Foo(1))
scala> val y: Option[Bar] = tmp
y: Option[Bar] = Some(Bar(1))
И сделайте ваше использование Some
более опасный:
scala> val tmp = Some(Foo(1))
tmp: Some[Foo] = Some(Foo(1))
scala> val y: Option[Bar] = tmp
<console>:25: error: could not find implicit value for parameter functor: fun.Functor[Some]
val y: Option[Bar] = tmp
^
Это говорит о том, что дисперсия имеет решающее значение и взаимодействует с последствиями. Я предполагаю, что вы столкнулись с очень редкой, возможно, трудно исправить ошибкой, которую можно избежать, используя другие методы.
Вы можете не знать об этом, но есть флаг для этого: -Xlog-implicits
, И вот что это говорит:
scala> val barOpt2: Option[Bar] = fooOpt
fromOptionToOption is not a valid implicit value for Some[Foo] => Option[Bar] because:
incompatible: (from: Option[Foo])(implicit conversion: Foo => B)Option[B] does not match expected type Some[Foo] => Option[Bar]
<console>:16: error: type mismatch;
found : Some[Foo]
required: Option[Bar]
val barOpt2: Option[Bar] = fooOpt
^
И вот вы идете - он не знает, какой тип B
должно быть. 0__ упомянул, что эта проблема не возникает с инвариантными коллекциями, и это имеет некоторый смысл. В инвариантных коллекциях B
должно быть точно Bar
в то время как для ковариантных коллекций это может быть любой подтип Bar
,
Итак, почему val foo: Option[Foo] = Some(Bar(1))
Работа? Ну, есть флаг для этого тоже... -Ytyper-debug
, Однако не для слабых, учитывая крайнее многословие.
Я все равно пробирался, сравнивая, что происходит в обоих случаях, и ответ довольно прост... это не Option
что преобразуется в этом случае, но Bar
! Помните, что вы объявили неявное преобразование из Bar => Foo
поэтому он применяет это преобразование перед передачей результата Some
!
Это не работает, потому что Спецификация языка Scala определяет представление следующим образом:
Неявные параметры и методы также могут определять неявные преобразования, называемые представлениями. Представление от типа S к типу T определяется неявным значением, имеющим тип функции S=>T или (=>S)=>T, или методом, конвертируемым в значение этого типа.
fromOptionToOption
не соответствует трем категориям, так как принимает неявный параметр. Компилятор, похоже, не находит конвертер с целевым и исходным типом.
Определение вида из Option[Foo]
в Option[Bar]
работает как положено.
trait T
case class Foo(i: Int) extends T
case class Bar(i: Int) extends T
object Main {
implicit def fromFooToBar(f: Foo):Bar = Bar(f.i)
implicit def fromBarToFoo(b: Bar):Foo = Foo(b.i)
// implicit def fromOptionToOption[A, B](from: Option[A])(implicit conversion: (A) => B): Option[B] =
// from.map(conversion(_))
implicit def fromOptionFooToOptionBar(o: Option[Foo]): Option[Bar] = o map { foo => foo }
def test(): Option[Bar] = {
val fooOpt = Some(Foo(4))
val barOpt2: Option[Bar] = fooOpt
barOpt2
}
}
println(Main.test)
Запуск этого распечатывает:
$ scala so.scala
Some(Bar(4))
Однако еще не все потеряно. Это не так хорошо, как в целом Option
в Option
, но мы можем сделать что-то вроде того, что может превратиться в Bar
в Option[Bar]
по виду связаны.
trait T
case class Foo(i: Int) extends T
case class Bar(i: Int) extends T
object Main {
implicit def fromFooToBar(f: Foo):Bar = Bar(f.i)
implicit def fromBarToFoo(b: Bar):Foo = Foo(b.i)
implicit def fromOptionToOptionBar[A <% Bar](from: Option[A]): Option[Bar] =
from map { foo => foo }
def test(): Option[Bar] = {
val fooOpt = Some(Foo(4))
val barOpt2: Option[Bar] = fooOpt
barOpt2
}
}
println(Main.test)
Вот еще один обходной путь, который можно использовать для общего Option
в Option
но требует дополнительного .convert
вызов:
trait T
case class Foo(i: Int) extends T
case class Bar(i: Int) extends T
case class Converter[A](x: Option[A]) {
def convert[B](implicit ev: Function1[A, B]): Option[B] = x map { a: A => ev(a) }
}
object Main {
implicit def optionToConverter[A](x: Option[A]) = Converter(x)
implicit def fooToBar(x: Foo) = Bar(x.i)
def test(): Option[Bar] = {
val fooOpt = Some(Foo(4))
val barOpt: Option[Bar] = fooOpt.convert
barOpt
}
}
println(Main.test)
Я улучшил ответ @jseureth и добавил поддержку Traversable
:
trait Mappable[A, B, C[_]] {
def apply(f: A => B): C[B]
}
package object app {
implicit class OptionMappable[A, B, C[X] <: Option[X]](option: C[A]) extends Mappable[A, B, Option] {
override def apply(f: A => B): Option[B] = option.map(f)
}
implicit class TraversableMappable[A, B, C[X] <: Traversable[X]](traversable: C[A])
(implicit cbf: CanBuildFrom[C[A], B, C[B]]) extends Mappable[A, B, C] {
override def apply(f: A => B): C[B] = {
val builder = cbf(traversable)
builder.sizeHint(traversable)
builder ++= traversable.map(f)
builder.result()
}
}
implicit def liftConversion[C[_], A, B](x: C[A])
(implicit f: A => B, m: C[A] => Mappable[A, B, C]): C[B] = m(x)(f)
}
Теперь вы можете неявно конвертировать опции и обходные пути:
implicit def f(i: Int): String = s"$i"
val a: Option[String] = Some(1)
val b: Seq[String] = Seq(1, 2, 3)
На самом деле это очень странная проблема. Я пытался использовать другой тип, чем Option
и получается, что проблема в том, что Option
является ковариантным по параметру типа. Это работает все:
case class A[B](value: B) // invariant in B
case class X()
case class Y()
implicit def xtoy(x: X): Y = Y()
implicit def ytox(x: Y): X = X()
implicit def movea[U, V](from: A[U])(implicit view: U => V): A[V] = A[V](from.value)
def test(a: A[Y]) = "ok"
test(A(X())) // (1)
val f = A(X())
test(f) // (2)
Но если вместо этого я определяю A
как
case class A[+B](value: B) // covariant in B
Случай (2) не выполняется. Случай (1) всегда успешен, потому что Scala уже конвертирует X
в Y
прежде чем обернуть его в A
,
Теперь, когда мы знаем источник проблемы, вам нужно подождать, пока гуру типа объяснит, почему это на самом деле проблема... Преобразование все еще действует, вы видите:
askForY(movea(f)) // succeeds, even with A[+B]