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")
^
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
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'). –
Dla zapisu, terminologia Scala to" powtarzalne parametry, "nie" varargs. " –