Frage

Ich versuche eine Funktion zu schreiben, die die impliziten Konvertierungen, die ich für Objekt A -> Objekt B habe, wieder ausverwendet, wenn sie auf generische Optionen eingewickelt werden, damit die Option [a] -> Option [B] auch Konvertierungen Arbeit.

Was ich mir ausgedacht habe, ist:

implicit def fromOptionToOption[A, B](from: Option[A])(implicit conversion: (A) => B): Option[B] = from.map(conversion(_))

Dies funktioniert, wenn ich einem Wert eine (..) zuordne, aber nicht, wenn ich eine Option zuordne. Siehe die folgende Konsolenausgabe:

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.

Ich sehe den Unterschied zwischen der ersten und zweiten Konvertierung nicht wirklich. Irgendwie ruft es in letzterem nicht die implizite Konvertierung auf. Ich denke, es hat etwas mit dem Typsystem zu tun, aber ich kann noch nicht sehen, wie. Irgendwelche Ideen? -Albert (ich bin auf Scala 2.9.1)

War es hilfreich?

Lösung

Hier ist Hinweis:

scala> val fooOpt: Option[Bar] = Option(Foo(1))
fooOpt: Option[Bar] = Some(Bar(1))

Und ein anderer:

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)

Sieht aus wie ein legitim seltsam Insekt. Ich würde einen kleineren Testfall öffnen und ein Problem öffnen (oder nach einem in Jira suchen).

Nebenbei:

Sie könnten eine Kategorie-Theorie verwenden, um viele verschiedene Arten von "optionsischen" Dingen zu bewältigen.

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)

}

Das ist etwas fortgeschrittener, da Sie einige Kategorie -Theorie FP auf das Problem abbilden, aber es ist eine allgemeinere Lösung, um implizite Gespräche nach Bedarf in Container zu heben. Beachten Sie, wie sie sich ketten, indem sie eine implizite Konversationsmethode verwenden, die nimmt Ein begrenzteres implizites Argument.

Dies sollte auch die Beispiele zum Laufen bringen:

scala> val tmp = Option(Foo(1))
tmp: Option[Foo] = Some(Foo(1))

scala> val y: Option[Bar] = tmp
y: Option[Bar] = Some(Bar(1))

Und mach deinen Gebrauch von Some gefährlicher:

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
                            ^

Das sagt dir das Varianz ist kritisch und interagiert mit impliziten. Ich vermute, Sie sind auf einen sehr seltenen, wahrscheinlich schwer zu behebenden Fehler gestoßen, der mit anderen Techniken vermieden werden kann.

Andere Tipps

Sie sind sich dessen vielleicht nicht bewusst, aber dafür gibt es eine Flagge: -Xlog-implicits. Und das sagt es:

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
                                  ^

Und da gehen Sie - es weiß nicht, welchen Typ B muss sein. 0__ erwähnte, dass dieses Problem bei invarianten Sammlungen nicht auftritt, und das macht einen Sinn. In invarianten Sammlungen, B muss genau sein Bar, während für kovariante Sammlungen es jeder Subtyp von sein könnte Bar.

Also, warum tut es? val foo: Option[Foo] = Some(Bar(1)) Arbeit? Nun, dafür gibt es auch eine Flagge ... -Ytyper-debug. Nicht für die Schwachen angesichts der extremen Ausführlichkeit.

Ich habe trotzdem durchgesehen und verglichen, was in beiden Fällen passiert, und die Antwort ist ziemlich einfach ... es ist nicht die Option das wird in diesem Fall konvertiert, aber Bar! Denken Sie daran, Sie haben eine implizite Konvertierung aus erklärt Bar => Foo, Daher wird diese Konvertierung angewendet Vor Übergeben des Ergebniss an Some!

Es funktioniert nicht, weil die Scala -Sprachspezifikation die Ansicht wie folgt definiert:

Implizite Parameter und Methoden können auch implizite Konvertierungen definieren, die genannt werden Ansichten. Eine Ansicht vom Typ S tippen T wird durch einen impliziten Wert definiert, der Funktionstyp hat S => t oder (=> S) => t oder mit einer Methode, die in einen Wert dieses Typs konvertiug wird.

fromOptionToOption entspricht nicht den drei Kategorien, da es einen impliziten Parameter nimmt. Der Compiler scheint keinen Konverter zu finden, der sowohl mit dem Ziel als auch mit der Quelle einen generischen Typ hat.

Definieren einer Ansicht von Option[Foo] zu Option[Bar] funktioniert wie erwartet.

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)

Ausdruck dieses Ausdrucks: Ausdruck:

$ scala so.scala
Some(Bar(4))

Es ist jedoch nicht alles verloren. Es ist nicht so schön wie allgemein Option zu Option, aber wir können so etwas tun, in das wir uns verwandeln können Bar zu Option[Bar] nach Ansicht gebunden.

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)

Hier ist eine weitere Problemumgehung, die für den Allgemeines verwendet werden kann Option zu Option erfordert aber extra .convert Anruf:

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)

In der Tat ist es ein sehr seltsames Problem. Ich habe versucht, einen anderen Typ zu verwenden als Option, und es stellt sich heraus, dass das Problem das ist Option ist kovariante in seinem Typparameter. Das funktioniert alles:

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)

Aber wenn ich stattdessen definiere A wie

case class A[+B](value: B)  // covariant in B

Der Fall (2) schlägt fehl. Fall (1) ist immer erfolgreich, weil Scala bereits konvertiert X zu Y Vor dem Wickeln in eine A.

Jetzt, wo wir das Problem kennen Quelle, Sie müssen warten, bis ein Guru vom Typ Guru erklärt wird warum Dies ist eigentlich ein Problem ... die Konvertierung ist immer noch gültig, Sie sehen:

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

Ich habe verbessert @jseureth antworten und Unterstützung für Unterstützung für 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)

}

Jetzt können Sie implizit Optionen und Traversables konvertieren:

implicit def f(i: Int): String = s"$i"

val a: Option[String] = Some(1)
val b: Seq[String] = Seq(1, 2, 3)
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top