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?
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.
3 Réponses :
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 }
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!
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 }
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.
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 pasContainer [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 deB
et vous pouvez créer un exemple similaire. Il en va de même pour les types de raffinement commeB {type T = Int}
.Autres sous-types qui ne sont probablement pas pertinents car ils n'ont pas d'instances:
Rien
, types composés commeB avec Iterable [Int ]
...
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.
Remplacez
List (b)
parList (content)
... mais je ne suis pas sûr que cette méthode fasse ce que vous pensez qu'elle fait ...