Scala: Fare conversione implicita A-> lavoro B per l'opzione [A] -> Opzione [B]

StackOverflow https://stackoverflow.com/questions/8823816

  •  27-10-2019
  •  | 
  •  

Domanda

Sto cercando di scrivere una funzione che riutilizza le conversioni implicite che ho per Object A -> Oggetto B quando vengono avvolti in un opzione in modo generico in modo che l'opzione [A] -> L'opzione [B ] Le conversioni anche il lavoro.

Quello che mi è venuta in mente è:

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

Questo funziona quando mi assegnano un Alcuni (..) per un valore ma non quando ho assegnare un val opzione; vedere il seguente output della console:

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.

io non veramente vedere la differenza tra la prima e la seconda conversione. In qualche modo non richiama la conversione implicita in quest'ultimo. Credo che abbia qualcosa a che fare con il sistema di tipo, ma non riesco a vedere come appena ancora. Qualche idea? -Albert (Io sono su scala 2.9.1)

È stato utile?

Soluzione

Ecco indizio:

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

E un altro:

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)

si presenta come un dispari bug legittimamente. Mi piacerebbe pop aprire un banco di prova più piccolo e apro un problema (o cercare uno in JIRA).

Per inciso:

Si potrebbe usare un po 'di teoria categoria a un sacco manico di diversi tipi di cose "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)

}

Questo è un po 'più avanzato, come si sta mappando qualche teoria di categoria FP sul problema, ma è una soluzione più generale per sollevare conversazioni impliciti in contenitori a seconda delle necessità. Notate come essi catena usando un metodo conversazione implicito che prende di un argomento implicito più limitata.

Inoltre, questo dovrebbe rendere il lavoro esempi:

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

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

E rendere il vostro utilizzo di Some più pericoloso:

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
                            ^

Che si sta dicendo che varianza è critica, e interagisce con impliciti. La mia ipotesi è è stato eseguito in un molto raro, probabilmente difficile da bug fix che può essere evitato con altre tecniche.

Altri suggerimenti

Non si potrebbe essere a conoscenza di essa, ma c'è una bandiera per questo: -Xlog-implicits. E questo è quello che dice:

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
                                  ^

E ci si va - che non si sa che tipo B deve essere. 0__ detto che questo problema non si verifica con le collezioni invarianti, e che fa un certo senso. In collezioni invarianti, B deve essere esattamente Bar, mentre per le collezioni covarianti potrebbe essere qualsiasi sottotipo di Bar.

Quindi, perché funziona val foo: Option[Foo] = Some(Bar(1))? Beh, c'è una bandiera anche per questo ... -Ytyper-debug. Non per i deboli, tuttavia, data l'estrema verbosità.

I waddled attraverso ogni caso, confrontando ciò che accade in entrambi i casi, e la risposta è piuttosto semplice ... non è la Option che viene convertito in quel caso, ma Bar! Ricordate, hai dichiarato una conversione implicita da Bar => Foo, per cui è l'applicazione che la conversione prima che passa il risultato al Some!

Non funziona perché visualizzare le definisce Scala linguaggio di specifica come segue:

parametri ei metodi impliciti possono anche definire conversioni implicite chiamato visite . Una vista dal tipo S al tipo T è definito da un valore implicito che ha funzione tipo S => T o (=> S) => T o con un metodo convertito in un valore di quel tipo.

fromOptionToOption non è conforme alle tre categorie in quanto ci vuole un parametro implicito. compilatore non sembra trovare convertitore sia con destinazione e di origine avendo tipo generico.

La definizione di una vista da Option[Foo] alle opere Option[Bar] come previsto.

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)

Esecuzione Questo stampa:

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

Tuttavia, non tutto è perduto. Non è così bello come Option generale Option, ma possiamo fare qualcosa di simile tutto ciò che può trasformarsi in Bar a Option[Bar] da vista legato.

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)

Ecco un'altra soluzione che può essere utilizzato per Option generale Option ma richiede chiamata .convert in più:

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 effetti si tratta di un problema molto strano. Ho provato ad utilizzare un altro tipo di Option, e si scopre che il problema è che Option è covariante nel suo parametro di tipo. Questo funziona tutto:

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)

Ma se invece mi definisco A come

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

Il caso (2) non riesce. Case (1) riesce sempre, perché Scala converte già X a Y prima avvolgendolo in un A.

Ora che sappiamo che il problema sorgente , è necessario attendere per un tipo di guru per spiegare perché questo è in realtà un problema ... La conversione è ancora valido, si vede:

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

I migliorato @jseureth risposta e aggiunto il supporto per 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)

}

Ora è possibile convertire implicitamente opzioni e traversables:

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

val a: Option[String] = Some(1)
val b: Seq[String] = Seq(1, 2, 3)
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top