2016-08-25 5 views
5

Ich möchte alle Iterable s mit einigen benutzerdefinierten Code zu verbessern. Dazu schrieb ich folgendes:Implizite Klasse für Untertypen einer generischen Klasse

implicit class RichIterable[A, B <: Iterable[A]](b: B) { 
    def nonEmptyOpt: Option[B] = if (b.nonEmpty) Some(b) else None 
} 

Nun, wenn ich diese Methode auf einem List verwenden möchten, die auf jeden Fall eine Unterklasse von Iterable wie

List(1, 2, 3).nonEmptyOpt 

ich

value nonEmptyOpt is not a member of List[Int] 
so ist

Wie kann ich das beheben?

Antwort

7

Bei einem gegebenen Parameter mit nur dem Typ B <: Iterable[A] weiß der Compiler nicht, wie man leicht herausfinden kann, was A ist, weil er nicht einfach aus B berechnet werden kann (muss nach den niedrigsten Obergrenzen suchen).

Stattdessen können Sie die Typabhängigkeiten neu definieren, ohne Tricks zu verwenden. Im Wesentlichen sollte B wirklich ein Typkonstruktor sein, der oben durch Iterable begrenzt wird. Dann ist Ihre implizite Klasse eine Umwandlung von einigen B[A] in Ihre angereicherte Klasse. Ein Parameter von B[A] hilft dem Compiler, A zu berechnen, da erwartet wird, dass es das Argument des Typkonstruktors B ist.

implicit class RichIterable[A, B[X] <: Iterable[X]](b: B[A]) { 
    def nonEmptyOpt: Option[B[A]] = if (b.nonEmpty) Some(b) else None 
} 

scala> List(1, 2, 3).nonEmptyOpt 
res0: Option[List[Int]] = Some(List(1, 2, 3)) 

scala> List.empty[Int].nonEmptyOpt 
res1: Option[List[Int]] = None 
+0

neugierig Just, ist alles verloren/geändert von "existentiell" geht? 'RichIterable [A, B [_] <: Iterable [_]] (b: B [A]) {...' – jwvh

+0

@jwvh Dies verliert den exacta-Rückgabetyp für alle Unterklassen, die nicht genau einen Typparameter haben , das ist das gleiche wie im 'Iterable', das sie ausdehnen. Einige Beispiele dafür sind 'Map [A, B]' oder 'scala.xml.NodeSeq'. ZB 'Map (1-> 2) .nonEmptyOpt' wird' Option [Iterable [...]] 'sein, nicht' Option [Map [...]] ' – Kolmar

+0

@Kolmar, ich gehe zurück und weiter zwischen 'B [X] ...' und 'B [_] ...', und bis jetzt konnte ich keinen Unterschied in der Handhabung von 'Map()' feststellen. – jwvh

7

Kleiner Trick, den ich auf einmal gestolpert:

scala> implicit class RichIterable[A, B <: Iterable[A]](b: B with Iterable[A]) { 
| def nonEmptyOpt: Option[B] = if (b.nonEmpty) Some(b) else None 
| } 
defined class RichIterable 

scala> List(1,2,3).nonEmptyOpt 
res3: Option[List[Int]] = Some(List(1, 2, 3)) 

Notiere die B with Iterable[A] auf den Parameter.

By the way, wenn implicits Debuggen, hilft es manchmal, sie zu versuchen explizit anzuwenden (vor der Änderung):

scala> new RichIterable(List(1,2,3)).nonEmptyOpt 
<console>:15: error: inferred type arguments [Nothing,List[Int]] do not conform to class RichIterable's type parameter bounds [A,B <: Iterable[A]] 
      new RichIterable(List(1,2,3)).nonEmptyOpt 

Also, der Compiler eine harte Zeit mit der Art von A herauszufinden. Die Typenverfeinerung hilft offenbar dabei.

0

Eine einfachere Lösung wäre:

implicit class RichIterable[A](b: Iterable[A]) { 
    def nonEmptyOpt: Option[Iterable[A]] = if (b.nonEmpty) Some(b) else None 
} 

scala> List(1,2,3).nonEmptyOpt 
res0: Option[Iterable[Int]] = Some(List(1, 2, 3)) 
+0

Das Problem bei diesem Ansatz besteht darin, dass Sie eine Liste in eine Iterable konvertiert haben. –

Verwandte Themen