2013-01-06 11 views
9

Sto provando a scrivere alcuni metodi di estensione per le collezioni Scala, e nei guai li sto generando completamente.Come si usa il membro del tipo A in IsTraversableLike?

Un primo tentativo di tailOption produce qualcosa come:

implicit class TailOption[A, Repr <: GenTraversableLike[A, Repr]](val repr: Repr) { 
    def tailOption: Option[Repr] = 
    if (repr.isEmpty) None 
    else Some(repr.tail) 
} 

purtroppo, questo non funziona:

scala> List(1,2,3).tailOption 
<console>:19: error: value tailOption is not a member of List[Int] 
       List(1,2,3).tailOption 

Scala 2.10 fornisce l'IsTraversableLike tipo di classe per aiutare adattare questo genere di cose per tutte le raccolte (comprese quelle dispari, come le stringhe).

Con questo può per esempio implementare tailOption abbastanza facilmente:

implicit class TailOption[Repr](val r: Repr)(implicit fr: IsTraversableLike[Repr]) { 
    def tailOption: Option[Repr] = { 
    val repr = fr.conversion(r) 
    if (repr.isEmpty) None 
    else Some(repr.tail) 
    } 
} 

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

scala> "one".tailOption 
res13: Option[String] = Some(ne) 

Il risultato è del tipo corretto: Option[<input-type>]. In particolare, sono stato in grado di preservare il tipo Repr quando si chiamano metodi che restituiscono Repr, come `tail.

Purtroppo, non riesco a utilizzare questo trucco per preservare il tipo di elementi della collezione. Non riesco a chiamare metodi che restituiscono un elemento.

IsTraversableLike ha un membro A, ma non sembra molto utile. In particolare non riesco a ricostruire il mio tipo di elemento originale e il membro non è equivalente nel tipo. Per esempio, senza ulteriore lavoro, headTailOption assomiglia a questo:

implicit class HeadTailOption[Repr](val r: Repr)(implicit val fr: IsTraversableLike[Repr]) { 
    def headTailOption: Option[(fr.A, Repr)] = { 
    val repr = fr.conversion(r) 
    if (repr.isEmpty) None 
    else Some(repr.head -> repr.tail) 
    } 
} 

scala> val Some((c, _)) = "one".headTailOption 
c: _1.fr.A forSome { val _1: HeadTailOption[String] } = o 

Come possiamo vedere, C ha un tipo meravigliosamente barocco. Ma, questo tipo è non equivalente a Char:

scala> val fr = implicitly[IsTraversableLike[String]] 
fr: scala.collection.generic.IsTraversableLike[String] = [email protected] 

scala> implicitly[fr.A <:< Char] 
<console>:25: error: Cannot prove that fr.A <:< Char. 
       implicitly[fr.A <:< Char] 

Ho provato tutti i tipi di trucchi tra cui avere Repr[A] <: GenTraversableLike[A, Repr[A]] nessuno dei quali aiutano. Chiunque può capire la salsa magica per rendere headTailOption restituire i tipi giusti per:

val headTailString: Option[(Char, String)] = "one".headTailOption 
val headTailList: Option[(Int, List[Int])] = List(1,2,3).headTailOption 

risposta

3

una risposta parziale. Probabilmente hai iniziato dall'esempio di scaladoc per IsTraversableLike. Usa ancora il "vecchio approccio" per separare la conversione implicita e creare un'istanza della classe wrapper, invece di andare in una fase attraverso una classe implicita. Si scopre che il "vecchio approccio" funziona:

import collection.GenTraversableLike 
import collection.generic.IsTraversableLike 

final class HeadTailOptionImpl[A, Repr](repr: GenTraversableLike[A, Repr]) { 
    def headTailOption: Option[(A, Repr)] = { 
    if (repr.isEmpty) None 
    else Some(repr.head -> repr.tail) 
    } 
} 

implicit def headTailOption[Repr](r: Repr)(implicit fr: IsTraversableLike[Repr]): 
    HeadTailOptionImpl[fr.A,Repr] = new HeadTailOptionImpl(fr.conversion(r)) 

// `c` looks still weird: `scala.collection.generic.IsTraversableLike.stringRepr.A` 
val Some((c, _)) = "one".headTailOption 
val d: Char = c // ...but it really is a `Char`! 

val headTailString: Option[(Char, String)] = "one".headTailOption 
val headTailList: Option[(Int, List[Int])] = List(1,2,3).headTailOption 

Come Miles sottolinea, la scissione sembra essenziale per far funzionare tutto con la ricerca e il tipo implicita inferenza.

Un'altra soluzione, anche se naturalmente meno elegante, è quello di dare l'unificazione delle stringhe e collezioni:

trait HeadTailOptionLike[A, Repr] { 
    def headTailOption: Option[(A, Repr)] 
} 

implicit class GenHeadTailOption[A, Repr](repr: GenTraversableLike[A, Repr]) 
extends HeadTailOptionLike[A, Repr] { 
    def headTailOption = 
    if (repr.isEmpty) None 
    else Some(repr.head -> repr.tail) 
} 

implicit class StringHeadTailOption(repr: String) 
extends HeadTailOptionLike[Char, String] { 
    def headTailOption = 
    if (repr.isEmpty) None 
    else Some(repr.head -> repr.tail) // could use repr.charAt(0) -> repr.substring(1) 
} 

List(1,2,3).headTailOption 
"one".headTailOption 
+0

Mi hai battuto. Sfortunatamente la suddivisione tra il metodo implicito che usa il tipo dipendente fr.A e la classe parametrizzata dal tipo è essenziale qui, quindi non credo che una soluzione di classe implicita sia (attualmente) possibile. –

+0

Sì, sono giunto alla stessa conclusione ora. La classe implicita non è in grado di riprodurre la trasformazione/inferenza del tipo necessaria del tipo dipendente ... –

0

si può scrivere come una classe implicita se si estrae il tipo A dal IsTraversableLike.

import collection.generic.IsTraversableLike 

implicit class HeadTailOption[Repr,A0](val r: Repr)(implicit val fr: IsTraversableLike[Repr]{ type A = A0 }) { 
    def headTailOption: Option[(A0, Repr)] = { 
    val repr = fr.conversion(r) 
    if (repr.isEmpty) None 
    else Some(repr.head -> repr.tail) 
    } 
} 

o equivalentemente:

import collection.generic.IsTraversableLike 

type IsTraversableLikeAux[Repr,A0] = IsTraversableLike[Repr]{ type A = A0 } 

implicit class HeadTailOption[Repr,A](val r: Repr)(implicit val fr: IsTraversableLikeAux[Repr,A]) { 
    def headTailOption: Option[(A, Repr)] = { 
    val repr = fr.conversion(r) 
    if (repr.isEmpty) None 
    else Some(repr.head -> repr.tail) 
    } 
} 

E poi tutto funziona bene.

scala> val Some((c, _)) = "one".headTailOption 
c: scala.collection.generic.IsTraversableLike.stringRepr.A = o 

scala> c.isSpaceChar 
res0: Boolean = false 

Il compilatore sa che scala.collection.generic.IsTraversableLike.stringRepr.A è lo stesso di Char.