2013-06-02 16 views
7

Czy jest możliwe aby użyć magnet pattern z varargs:Magnes z powtarzających się parametrami (varargs)

object Values { 
    implicit def fromInt (x : Int) = Values() 
    implicit def fromInts(xs: Int*) = Values() 
} 
case class Values() 

object Foo { 
    def bar(values: Values) {} 
} 

Foo.bar(0) 
Foo.bar(1,2,3) // ! "error: too many arguments for method bar: (values: Values)Unit" 

?

+0

nie jestem pewien, czy dobrze zrozumiałem to poprawnie : Czy to sprowadza się do tego pytania: Dlaczego '1, 2, 3' nie pasuje do parametru 2. niejawnej funkcji konwersji? – Beryllium

+0

Dokładnie, bym był li ke, aby zezwolić na przechwytywanie zmiennej liczby argumentów przy użyciu wartości 'Values' (bez przeciążania' bar' i bez umieszczania varargs w metodzie 'bar'). –

+1

Dla zapisu, terminologia Scala to" powtarzalne parametry, "nie" varargs. " –

Odpowiedz

2

Jak już wspomniano przez gourlaysama, zwracając z varargs w jeden Product rade, składniowo mówiąc:

implicit def fromInts(t: Product) = Values() 

pozwala to fol ryk zadzwonić do kompilacji porządku:

Foo.bar(1,2,3) 

To dlatego, że kompilator autmatically unosi 3 argumenty do Tuple3[Int, Int, Int]. To zadziała z dowolną liczbą argumentów aż do poziomu 22. Teraz problem polega na tym, jak sprawić, by był bezpieczny. Ponieważ jest to Product.productIterator jest jedynym sposobem na odzyskanie naszej listy argumentów w treści metody, ale zwraca ona Iterator[Any]. Nie mamy żadnej gwarancji, że metoda zostanie wywołana tylko z Int s. Nie powinno to dziwić, ponieważ w podpisie nigdy nie wspominaliśmy, że chcieliśmy tylko Int.

OK, więc kluczową różnicą między nieograniczoną listą Product a listą vararg jest to, że w tym drugim przypadku każdy element jest tego samego typu.Możemy zakodować to przy użyciu klasy typ:

abstract sealed class IsVarArgsOf[P, E] 
object IsVarArgsOf { 
    implicit def Tuple2[E]: IsVarArgsOf[(E, E), E] = null 
    implicit def Tuple3[E]: IsVarArgsOf[(E, E, E), E] = null 
    implicit def Tuple4[E]: IsVarArgsOf[(E, E, E, E), E] = null 
    implicit def Tuple5[E]: IsVarArgsOf[(E, E, E, E, E), E] = null 
    implicit def Tuple6[E]: IsVarArgsOf[(E, E, E, E, E), E] = null 
    // ... and so on... yes this is verbose, but can be done once for all 
} 

implicit class RichProduct[P](val product: P) { 
    def args[E](implicit evidence: P IsVarArgsOf E): Iterator[E] = { 
    // NOTE: by construction, those casts are safe and cannot fail 
    product.asInstanceOf[Product].productIterator.asInstanceOf[Iterator[E]] 
    } 
} 

case class Values(xs: Seq[Int]) 
object Values { 
    implicit def fromInt(x : Int) = Values(Seq(x)) 
    implicit def fromInts[P](xs: P)(implicit evidence: P IsVarArgsOf Int) = Values(xs.args.toSeq) 
} 


object Foo { 
    def bar(values: Values) {} 
} 

Foo.bar(0) 
Foo.bar(1,2,3) 

zmieniliśmy formularzu podpisów metoda

implicit def fromInts(t: Product) 

do:

implicit def fromInts[P](xs: P)(implicit evidence: P IsVarArgsOf Int) 

Wewnątrz korpusu metody, używamy nową methodd args aby odzyskać naszą listę argumentów.

Pamiętaj, że jeśli spróbujemy zadzwonić pod numer bar z krotką, która nie jest krotką Int s, otrzymamy błąd kompilacji, który przywraca nam bezpieczeństwo naszego typu.


UPDATE: Jak wskazano przez 0__ moje powyższe rozwiązanie nie gra dobrze z rozszerzeniem numerycznym. Innymi słowy, dodaje się nie kompiluje, chociaż byłoby to działa, jeśli bar po prostu przyjmować 3 Int parametry:

Foo.bar(1:Short,2:Short,3:Short) 
Foo.bar(1:Short,2:Byte,3:Int) 

Aby rozwiązać ten problem, wszystko co musisz zrobić, to zmodyfikować IsVarArgsOf tak, że wszystkie implicits pozwalają z elemts krotkowe być zamienny do wspólnego typu, zamiast być tego samego typu:

abstract sealed class IsVarArgsOf[P, E] 
object IsVarArgsOf { 
    implicit def Tuple2[E,X1<%E,X2<%E]: IsVarArgsOf[(X1, X2), E] = null 
    implicit def Tuple3[E,X1<%E,X2<%E,X3<%E]: IsVarArgsOf[(X1, X2, X3), E] = null 
    implicit def Tuple4[E,X1<%E,X2<%E,X3<%E,X4<%E]: IsVarArgsOf[(X1, X2, X3, X4), E] = null 
    // ... and so on ... 
} 

OK, faktycznie skłamałem, nie jesteśmy jeszcze zrobione. Ponieważ akceptujemy teraz różne typy elementów (o ile są one wymienialne na typ wspólny, nie możemy po prostu przesłać ich do oczekiwanego typu (doprowadziłoby to do błędu rzutowania w czasie wykonywania), ale zamiast tego musimy zastosować niejawne konwersje. możemy przerobić go tak:

abstract sealed class IsVarArgsOf[P, E] { 
    def args(p: P): Iterator[E] 
}; object IsVarArgsOf { 
    implicit def Tuple2[E,X1<%E,X2<%E] = new IsVarArgsOf[(X1, X2), E]{ 
    def args(p: (X1, X2)) = Iterator[E](p._1, p._2) 
    } 
    implicit def Tuple3[E,X1<%E,X2<%E,X3<%E] = new IsVarArgsOf[(X1, X2, X3), E]{ 
    def args(p: (X1, X2, X3)) = Iterator[E](p._1, p._2, p._3) 
    } 
    implicit def Tuple4[E,X1<%E,X2<%E,X3<%E,X4<%E] = new IsVarArgsOf[(X1, X2, X3, X4), E]{ 
    def args(p: (X1, X2, X3, X4)) = Iterator[E](p._1, p._2, p._3, p._4) 
    } 
    // ... and so on ... 
} 
implicit class RichProduct[P](val product: P) { 
    def args[E](implicit isVarArg: P IsVarArgsOf E): Iterator[E] = { 
    isVarArg.args(product) 
    } 
} 

to rozwiązuje problem z rozszerzeniem numerycznym, a my nadal uzyskać kompilacji podczas mieszania niepowiązanych typy:

scala> Foo.bar(1,2,"three") 
<console>:22: error: too many arguments for method bar: (values: Values)Unit 
      Foo.bar(1,2,"three") 
       ^
+0

Ok, widzę. Problem jest spowodowany trudnościami z implikacjami i poszerzaniem numerycznym, że faktycznie muszę przyznać "Int", "Float' i" Double "w moim przypadku, więc liczba implicite eksploduje. Mój wniosek jest taki, że po prostu nie jest to możliwe.Albo usuwam powtarzające się paramy jako opcje, albo muszę mieć co najmniej jedną przeciążoną metodę (jak to opisano w mojej odpowiedzi). : -/ –

+0

Istnieje rozwiązanie. Zobacz moją aktualizację. –

+0

Przyjmę to, ponieważ działa, ale naprawdę wolę, aby moja klasa była o wiele prostsza :) –

0

spec określa tylko typ powtarzających parametrów (varargs) z wewnątrz z funkcji:

typu takiego wielokrotnego parametru w tej metody jest to typ sekwencji scala.Seq [T ].

Nie obejmuje ona nigdzie indziej.

Zakładam więc, że kompilator wewnętrznie - w pewnej fazie - nie może pasować do typów.

Z tej obserwacji (nie skompilować => "podwójną definicję"):

object Values { 
    implicit def fromInt(x: Int) = Values() 
    implicit def fromInts(xs: Int*) = Values() 
    implicit def fromInts(xs: Seq[Int]) = Values() 
} 

wydaje się być seq []. Następnym krokiem jest uczynienie go innym:

object Values { 
    implicit def fromInt(x: Int) = Values() 
    implicit def fromInts(xs: Int*) = Values() 
    implicit def fromInts(xs: Seq[Int])(implicit d: DummyImplicit) = Values() 
} 

to kompiluje, ale to nie rozwiązuje prawdziwego problemu.

Jedyne obejście znalazłem jest przekształcenie varargs na sekwencję extenso:

def varargs(xs: Int*) = xs // return type is Seq[Int] 

Foo.bar(varargs(1, 2, 3)) 

ale to oczywiście nie jest to, co chcemy.

Prawdopodobnie powiązane: Niejawna funkcja konwersji ma tylko jeden parametr. Ale z punktu widzenia logicznego (lub tymczasowego) kompilatora, w przypadku varargs, może on również być wielokrotny.

chodzi o rodzajach, this może być interesujące

1

Edit:

var-args niejawne nie będą zbierane, ponieważ powtarzające się parametry nie są naprawdę obywateli pierwszej klasy, jeśli chodzi o rodzaje. .. są one dostępne tylko podczas sprawdzania, czy nie ma zastosowania metoda argumentów.

Tak więc, po wywołaniu Foo.bar(1,2,3) sprawdza, czy bar jest zdefiniowany za pomocą zmiennych argumentów, a ponieważ tak nie jest, nie ma zastosowania do argumentów. I nie może już iść dalej:

Jeśli wywołałeś to jednym argumentem, szukałby on niejawnej konwersji z typu argumentu na oczekiwany typ, ale ponieważ wywołałeś kilka argumentów, problem z arnością, nie ma możliwości przekonwertowania wielu argumentów na jeden z niejawną konwersją typu typu.


Ale: istnieje rozwiązanie wykorzystujące automatyczne przewijanie.

Foo.bar(1,2,3) 

może być rozumiane przez kompilator jako

Foo.bar((1,2,3)) 

co oznacza niejawna jak ten będzie działać:

implicit def fromInts[T <: Product](t: T) = Values() 
// or simply 
implicit def fromInts(t: Product) = Values() 

Problemem jest to, że jedynym sposobem, aby uzyskać Argumenty są następujące: t.productIterator, który zwraca wartość Iterator[Any] i należy ją przesłać.

Więc stracisz bezpieczeństwo typu; to byłoby skompilować (a nie w czasie wykonywania podczas używania go):

Foo.bar("1", "2", "3") 

Możemy zrobić to wpisać w pełni bezpieczny w użyciu Scala 2.10 ukryte makr. Makro będzie po prostu sprawdzić, czy argument jest rzeczywiście TupleX[Int, Int, ...] i tylko stają się dostępne jako niejawna konwersja, jeśli przejdzie tę kontrolę.

Aby przykładem bardziej użyteczne, zmieniłem Values zachować Int argumenty okolicy:

case class Values(xs: Seq[Int]) 

object Values { 
    implicit def fromInt (x : Int) = Values(Seq(x)) 
    implicit def fromInts[T<: Product](t: T): Values = macro Macro.fromInts_impl[T] 
} 

Z makro realizacji:

import scala.language.experimental.macros 
import scala.reflect.macros.Context 
object Macro { 
    def fromInts_impl[T <: Product: c.WeakTypeTag](c: Context)(t: c.Expr[T]) = { 
    import c.universe._ 

    val tpe = weakTypeOf[T]; 

    // abort if not a tuple 
    if (!tpe.typeSymbol.fullName.startsWith("scala.Tuple")) 
     c.abort(c.enclosingPosition, "Not a tuple!") 

    // extract type parameters 
    val TypeRef(_,_, tps) = tpe 

    // abort it not a tuple of ints 
    if (tps.exists(t => !(t =:= typeOf[Int]))) 
     c.abort(c.enclosingPosition, "Only accept tuples of Int!") 

    // now, let's convert that tuple to a List[Any] and add a cast, with splice 
    val param = reify(t.splice.productIterator.toList.asInstanceOf[List[Int]]) 

    // and return Values(param) 
    c.Expr(Apply(Select(Ident(newTermName("Values")), newTermName("apply")), 
     List(param.tree))) 
    } 
} 

I wreszcie, definiując Foo takiego:

object Foo { 
    def bar(values: Values) { println(values) } 
} 

Otrzymujesz PE-safe wezwanie ze składnią dokładnie takie jak powtarzające się parametry:

scala> Foo.bar(1,2,3) 
Values(List(1, 2, 3)) 

scala> Foo.bar("1","2","3") 
<console>:13: error: too many arguments for method bar: (values: Values)Unit 
       Foo.bar("1","2","3") 
        ^

scala> Foo.bar(1) 
Values(List(1)) 
0

Oto rozwiązanie, które używa przeciążenia (którego wolałbym nie)

object Values { 
    implicit def fromInt (x :  Int) = Values() 
    implicit def fromInts(xs: Seq[Int]) = Values() 
} 
case class Values() 

object Foo { 
    def bar(values: Values) { println("ok") } 
    def bar[A](values: A*)(implicit asSeq: Seq[A] => Values) { bar(values: Values) } 
} 

Foo.bar(0) 
Foo.bar(1,2,3)