1
votes

Impossible de faire correspondre un type paramétré avec un type concret après la mise en correspondance de modèles

En utilisant scala 2.12.8, cela ne serait pas compilé sans un cast:

sealed trait Content
case object A extends Content
final case class B(i: Int) extends Content

sealed trait Container[+C <: Content]
case class ContainerA(content: A.type) extends Container[A.type]
case class ContainerB(content: B) extends Container[B]

object Container {
  def apply[C <: Content](content: C): Container[C] = content match {
    case A => ContainerA(A) // compiles
    case b: B => ContainerB(b) // does not compile
  }
}
type mismatch;
 found   : b.type (with underlying type Playground.this.B)
 required: C

Voici un lien Scastie vers le problème: https://scastie.scala-lang.org/JIziYOYNTwKoZpdCIPCvdQ

Pourquoi travaille pour l'objet cas et pas pour la classe de cas? Comment puis-je le faire fonctionner pour la classe de cas?

EDIT

Les premières réponses m'ont fait réaliser que j'avais trop simplifié mon problème, voici une version mise à jour:

trait Content
case object A extends Content
case class B(i: Int) extends Content

def asList[C <: Content](content: C): List[C] = content match {
  case A => List(A) // compiles
  case b: B => List(b) // does not compile
}

Lien Scastie: https://scastie.scala-lang.org/TDlJM5SYSwGl2gmQPvKEX

C ne peut pas être un sous-type de B puisque B est final.


1 commentaires

Remplacez List (b) par List (content) ... mais je ne suis pas sûr que cette méthode fasse ce que vous pensez qu'elle fait ...


3 Réponses :


1
votes

La raison pour laquelle vous obtenez une erreur est que le type de retour de la méthode n'est pas explicite. Le remplacement du type de retour de Liste [C] par Liste [Contenu] résout le problème.

def asList[C <: Content](content: C): List[Content] = content match {
  case A => List(A) // compiles
  case b: B => List(b) // compiles
}


3 commentaires

Cela rend le paramètre de type inutile, il pourrait aussi bien être def asList (content: Content): List [Content] et je doute que ce soit ce que veut l'OP.


Si tel est le cas, pourquoi utilisons-nous TypeParameter C? Le contenu est de toute façon le super type afin que nous puissions placer un objet de type A et B sur la pile de contenu. Je ne pense pas que ce soit le problème du PO.


Désolé, j'ai trop simplifié le problème, je publie une modification dès que possible mais j'ai quand même appris quelque chose de la réponse de Chaitanya, merci!



3
votes

La solution est donnée dans le commentaire de @lasf:

case object A
case class B(i: Int)

sealed trait Container[C]
case class ContainerA(content: A.type) extends Container[A.type]
case class ContainerB(content: B) extends Container[B]

trait Containable[T] {
  def apply(value: T): Container[T]
}
object Containable {
  implicit object AContainer extends Containable[A.type] {
    def apply(value: A.type) = ContainerA(value)
  }
  implicit object BContainer extends Containable[B] {
    def apply(value: B) = ContainerB(value)
  }
}

object Container {
  def apply[C](content: C)(implicit containable: Containable[C]): Container[C] =
    containable(content)
}

Le problème est que le type de retour est List [C] mais le compilateur ne peut pas garantir que le type de Liste (b) est Liste [C] . En particulier, C pourrait être un sous-type de B auquel cas List (b) serait List [B] ce qui n'est pas compatible avec List[C .


La version mise à jour peut être résolue en utilisant asInstanceOf , même si ce n'est pas joli. / p>

object Container {
  def apply(content: A.type): Container[A.type] = ContainerA(content)
  def apply(content: B): Container[B] = ContainerB(content)
}

Vous pouvez également adopter une approche différente et utiliser la conversion implicite:

object Container {
  implicit def contain(content: A.type): Container[A.type] = ContainerA(content)
  implicit def contain(content: B): Container[B] = ContainerB(content)
}

val ca: Container[A.type] = A
val cb: Container[B] = B(0)

Ou même plusieurs constructeurs: p >

def apply[C <: Content](content: C): Container[C] = content match {
  case A => ContainerA(A) // compiles
  case b: B => ContainerB(b).asInstanceOf[Container[C]]
}

Voici une conception alternative utilisant un typeclass . Cela remplace la superclasse Content par une classe de types Containable . La classe Container peut désormais contenir n'importe quoi tant qu'il existe une instance de Containable pour cette classe.

def asList[C <: Content](content: C): List[C] = content match {
  case A => List(A) // compiles
  case b: B => List(content) // compiles
}


4 commentaires

Désolé, j'ai trop simplifié le problème, je viens de modifier la description. Quant à votre réponse, si je rends B final, C ne peut pas être un sous-type de B mais il ne se compile pas.


En fait, j'essaie d'éviter ces moulages et le constructeur unique permet plus de factorisation de code à mes côtés, mais peut-être que je suis juste confronté à un problème de conception de code.


@ M.Karassev Je pense que vous devriez probablement envisager d'utiliser des typeclasses < / a>. J'ai ajouté un exemple de la façon dont cela pourrait fonctionner.


Merci, j'ai en fait refactoré mon code pour ne plus avoir besoin de ces types paramétrés.



1
votes

C ne peut pas être un sous-type de B puisque B est final.

Wrong !

Types de singleton de B sont des sous-types de B:

case class ContainerB(content: B) extends Container[content.type]

Puisque ContainerB ne prolonge pas Container [b.type] , il ne peut pas être retourné par la dernière ligne. Et il ne peut pas être modifié de cette manière;

val b = B(0)
val container: Container[b.type] = Container[b.type](b)

n'est pas légal dans Scala.

Null est également un sous-type de B et vous pouvez créer un exemple similaire. Il en va de même pour les types de raffinement comme B {type T = Int} .

Autres sous-types qui ne sont probablement pas pertinents car ils n'ont pas d'instances: Rien , types composés comme B avec Iterable [Int ] ...


2 commentaires

Si nous utilisons des casts comme suggéré par @Tim ( case b: B => ContainerB (b) .asInstanceOf [Container [C]] ), nous devrions pouvoir le casser avec une ClassCastException , non? Pouvez-vous fournir un tel exemple? Container [b.type] (b) ne casse pas: scastie.scala- lang.org/YN2Wr16zSxucs5fnk8HVWg


Non, à cause de l'effacement de type, toutes ces transtypages deviennent des transtypages dans Container [_] au moment de l'exécution et réussiront.