Scala: заставить неявное преобразование A-> B работать для Option [A] -> Option [B] - PullRequest
21 голосов
/ 11 января 2012

Я пытаюсь написать функцию, которая повторно использует неявные преобразования, которые у меня есть для Объекта 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вал;см. следующий вывод консоли:

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 ]

13 голосов
/ 12 января 2012

Вот подсказка:

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
                            ^

Это говорит о том, что дисперсия является критической и взаимодействует с последствиями. Я предполагаю, что вы столкнулись с очень редкой, возможно, трудно исправить ошибкой, которую можно избежать, используя другие методы.

13 голосов
/ 12 января 2012

Вы можете не знать об этом, но есть флаг для этого: -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!

1 голос
/ 21 сентября 2016

Я улучшил @ jseureth answer и добавил поддержку 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)
1 голос
/ 12 января 2012

Это не работает, потому что Спецификация языка Scala определяет представление следующим образом:

Неявные параметры и методы также могут определять неявные преобразования, называемые views . Представление от типа 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)
1 голос
/ 11 января 2012

Действительно, это очень странная проблема. Я попытался использовать другой тип, кроме 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.

Теперь, когда мы знаем проблему source , вам нужно подождать, пока гуру типа объяснит почему это на самом деле проблема ... :

askForY(movea(f))  // succeeds, even with A[+B]
...