Scala: Faire conversion implicite A-> travail B pour l'option [A] -> Option [B]

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

  •  27-10-2019
  •  | 
  •  

Question

Je suis en train d'écrire une fonction qui réutilise les conversions implicites que j'ai pour objet A -> Objet B quand ils sont enveloppés dans une option d'une manière générique afin que l'option [A] -> Option [B ] Les conversions également.

Ce que je suis venu avec est:

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

Cela fonctionne quand j'assignent une partie (..) à une valeur, mais pas quand j'attribuer une option val; voir la sortie de la console suivante:

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.

Je ne vois pas vraiment la différence entre la première et la deuxième conversion. D'une certaine façon, il n'invoque pas la conversion implicite dans celle-ci. Je suppose qu'il a quelque chose à voir avec le système de type, mais je ne vois pas comment tout de suite. Des idées? -Albert (Je suis sur scala 2.9.1)

Était-ce utile?

La solution

Voici la moindre idée:

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

Et un autre:

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)

On dirait un légitimement impair bug. Je pop ouvert un petit cas de test et d'ouvrir un problème (ou recherchez un dans JIRA).

En aparté:

Vous pouvez utiliser une théorie de la catégorie à beaucoup de poignées de différents types de choses « 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)

}

C'est un peu plus avancé, comme vous une cartographie théorie des catégories FP sur le problème, mais il est une solution plus générale pour soulever des conversations implicites dans des conteneurs au besoin. Remarquez comment ils chaîne en utilisant une méthode de conversation implicite que prend un argument implicite plus limité.

En outre, cela devrait rendre le travail des exemples:

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

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

Et rendre votre utilisation de Some plus dangereux:

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
                            ^

Ce vous dit que variance est critique, et interagit avec implicits. Je suppose que vous a rencontré un très rare, sans doute difficile de bogue de correctif qui peut être évité en utilisant d'autres techniques.

Autres conseils

Vous ne pourriez pas être au courant, mais il y a un drapeau pour cela: -Xlog-implicits. Et voici ce qu'il dit:

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
                                  ^

Et vous y allez - il ne sait pas quel type B doit être. 0__ a mentionné que ce problème ne se produit pas avec des collections invariantes, et qui fait un certain sens. Dans les collections invariantes, B doit être exactement Bar, alors que pour les collections covariants il pourrait être un sous-type de Bar.

Alors, pourquoi fonctionne-t-val foo: Option[Foo] = Some(Bar(1))? Eh bien, il y a un drapeau pour ça aussi ... -Ytyper-debug. Pas pour les faibles, cependant, étant donné l'extrême prolixité.

Je dandinant à travers toute façon, en comparant ce qui se passe dans les deux cas, et la réponse est assez simple ... ce n'est pas Option qui est en cours de conversion dans ce cas, mais Bar! Rappelez-vous, vous avez déclaré une conversion implicite de Bar => Foo, il applique cette conversion avant passer le résultat à Some!

Il ne fonctionne pas parce que les définit Scala Language Specification voir comme suit:

paramètres implicites et méthodes peuvent également définir des conversions implicites appelées vues . Une vue à partir du type S taper T est définie par une valeur implicite qui a le type de fonction S => T ou (=> S) => T ou par un procédé pouvant être converti en une valeur de ce type.

fromOptionToOption ne sont pas conformes aux trois catégories, car il faut un paramètre implicite. compilateur ne semble pas trouver convertisseur à la fois source et destination ayant le type générique.

Définir une vue de Option[Foo] aux travaux de Option[Bar] comme prévu.

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)

L'exécution de cette imprime:

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

Cependant, tout ne soit pas perdu. Ce n'est pas aussi beau que Option général à Option, mais nous pouvons faire quelque chose comme tout ce qui peut se transformer en Bar à Option[Bar] par vue liée.

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)

Voici une autre solution qui peut être utilisé pour Option générale Option mais nécessite appel .convert supplémentaire:

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)

En effet, il est un problème très étrange. J'ai essayé d'utiliser un autre type que Option, et il se trouve que le problème est que Option est covariant dans son paramètre de type. Cela fonctionne tout:

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)

Mais si au lieu que je définis comme A

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

Le cas (2) tombe en panne. Cas (1) réussit toujours, parce que Scala convertit déjà X à Y avant de l'emballer dans un A.

Maintenant que nous connaissons le problème source , vous devez attendre un gourou de type à expliquer pourquoi c'est en fait un problème ... La conversion est toujours valide, vous voyez:

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

J'amélioré @jseureth réponse et un soutien supplémentaire pour 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)

}

Maintenant, vous pouvez convertir implicitement les options et traversables:

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

val a: Option[String] = Some(1)
val b: Seq[String] = Seq(1, 2, 3)
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top