Co oznacza param: _ * w Scali?

87

Będąc nowym w Scali (2.9.1), mam a List[Event]i chciałbym go skopiować do a Queue[Event], ale następująca składnia daje Queue[List[Event]]zamiast tego a:

val eventQueue = Queue(events)

Z jakiegoś powodu działa:

val eventQueue = Queue(events : _*)

Ale chciałbym zrozumieć, co to robi i dlaczego działa? Spojrzałem już na podpis Queue.applyfunkcji:

def apply[A](elems: A*)

I rozumiem, dlaczego pierwsza próba nie działa, ale jakie jest znaczenie drugiej? Co to jest :iw _*tym przypadku i dlaczego applyfunkcja nie przyjmuje po prostu znaku Iterable[A]?

Chris
źródło

Odpowiedzi:

93

a: Ajest przypisaniem typu; zobacz Jaki jest cel przypisywania typów w Scali?

: _* jest specjalną instancją typu ascription, która mówi kompilatorowi, aby traktował pojedynczy argument typu sekwencji jako zmienną sekwencję argumentów, tj. varargs.

Całkowicie poprawne jest tworzenie Queueusing, Queue.applyktóre ma pojedynczy element, który jest sekwencją lub iteracją, więc dokładnie to się dzieje, gdy dajesz pojedynczy Iterable[A].

Ben James
źródło
83

Jest to specjalna notacja, która mówi kompilatorowi, aby przekazał każdy element jako własny argument, a nie całość jako pojedynczy argument. Zobacz tutaj .

Jest to adnotacja typu, która wskazuje argument sekwencji i jest wymieniona jako „wyjątek” od ogólnej reguły w sekcji 4.6.2 specyfikacji języka, „Parametry powtarzane”.

Jest to przydatne, gdy funkcja przyjmuje zmienną liczbę argumentów, na przykład takich jak funkcjonować def sum(args: Int*), które mogą być powoływane jako sum(1), sum(1,2)itd. Jeśli masz listę takich jak xs = List(1,2,3), nie można zdać xssię, ponieważ jest to Listraczej niż Int, ale możesz przekazać jego elementy za pomocą sum(xs: _*).

Luigi Plinge
źródło
def sum(xs: _*)zgłasza błąd: niezwiązany typ wieloznaczny '
7kemZmani,
Twoja odpowiedź jest jasna, ale w rzeczywistości stwarza to dla mnie więcej zamieszania, zwykle w scala xs: intoznacza, że ​​typ xs to int, przechodząc przez to jest powyższą składnią w scali, gdzie xs: _*oznacza, że ​​xs jest rzutowane na poszczególne elementy.
Rpant
Skorzystałem z powyższego linku i wygląda na to, że tak właśnie jest, typ ascription to terminologia scala dla rzutowania typu Java. Proszę, popraw mnie, jeśli się mylisz.
Rpant
2
@ 7kemZmani: Musisz zdefiniować funkcję ze szczególnego rodzaju var-args: def sum(args: Int*)a ty nazywasz go z wieloznacznym typu „rodzajowe” var-args: val a = sum(xs: _*). Pomyśl o _*„Przekazuję Int *, String * lub cokolwiek *, co jest zdefiniowane w sygnaturze metody”
Alfonso Nishikawa
10

Dla osób korzystających z Pythona:

_*Operator Scali jest mniej więcej odpowiednikiem operatora * -operatora Pythona .


Przykład

Konwersja przykładu scala z linku dostarczonego przez Luigiego Plinge'a :

def echo(args: String*) = 
    for (arg <- args) println(arg)

val arr = Array("What's", "up", "doc?")
echo(arr: _*)

do Pythona wyglądałoby następująco:

def echo(*args):
    for arg in args:
        print "%s" % arg

arr = ["What's", "up", "doc?"]
echo(*arr)

i oba dają następujący wynik:

Co słychać
,
doktorze?


Różnica: rozpakowywanie parametrów pozycyjnych

Podczas gdy *operator Pythona może również zajmować się rozpakowywaniem parametrów pozycyjnych / parametrów dla funkcji o stałej arity:

def multiply (x, y):
    return x * y

operands = (2, 4)
multiply(*operands)

8

Robiąc to samo ze Scalą:

def multiply(x:Int, y:Int) = {
    x * y;
}

val operands = (2, 4)
multiply (operands : _*)

zawiedzie:

za mało argumentów do pomnożenia metody: (x: Int, y: Int) Int.
Nieokreślony parametr wartości y.

Ale można osiągnąć to samo ze scalą:

def multiply(x:Int, y:Int) = {
    x*y;
}

val operands = (2, 4)
multiply _ tupled operands

Według Lorrina Nelsona tak to działa:

Pierwsza część, f _, to składnia częściowo zastosowanej funkcji, w której żaden z argumentów nie został określony. Działa to jako mechanizm do przechwytywania obiektu funkcji. tupled zwraca nową funkcję o wartości arity-1, która przyjmuje jedną krotkę arity-n.

Dalsze czytanie:

Murmel
źródło