Call-by-Name: => Wpisz
=> Type
Notacja oznacza wywołanie według nazwy, która jest jednym z wielu sposobów, parametry mogą być przekazywane. Jeśli ich nie znasz, radzę poświęcić trochę czasu na przeczytanie tego artykułu na Wikipedii, mimo że obecnie jest to głównie wezwanie według wartości i wezwanie przez odniesienie.
Oznacza to, że to, co jest przekazywane, jest zastępowane nazwą wartości wewnątrz funkcji. Na przykład weź tę funkcję:
def f(x: => Int) = x * x
Jeśli tak to nazywam
var y = 0
f { y += 1; y }
Następnie kod zostanie wykonany w ten sposób
{ y += 1; y } * { y += 1; y }
Chociaż to podnosi punkt widzenia, co się stanie, jeśli wystąpi konflikt nazw identyfikatorów. W tradycyjnym zawołaniu według nazwy stosuje się mechanizm zwany zastępowaniem unikającym przechwytywania, aby uniknąć kolizji nazw. W Scali jest to jednak zaimplementowane w inny sposób z tym samym wynikiem - nazwy identyfikatorów wewnątrz parametru nie mogą się odwoływać ani identyfikatorów cienia w wywołanej funkcji.
Jest kilka innych punktów związanych z wezwaniem po imieniu, o których opowiem po wyjaśnieniu pozostałych dwóch.
Funkcje 0-arity: () => Typ
Składnia () => Type
oznacza typ pliku Function0
. To znaczy funkcja, która nie przyjmuje parametrów i coś zwraca. Jest to równoznaczne z, powiedzmy, wywołując metodę size()
- bierze żadnych parametrów i zwraca liczbę.
Ciekawe jest jednak to, że składnia ta jest bardzo podobna do składni dla literału funkcji anonimowej , co jest przyczyną pewnych nieporozumień. Na przykład,
() => println("I'm an anonymous function")
jest anonimową funkcją, literałem arity 0, której typ to
() => Unit
Moglibyśmy więc napisać:
val f: () => Unit = () => println("I'm an anonymous function")
Ważne jest jednak, aby nie mylić typu z wartością.
Jednostka => Typ
W rzeczywistości jest to tylko a Function1
, którego pierwszy parametr jest typu Unit
. Innymi sposobami zapisu byłyby (Unit) => Type
lub Function1[Unit, Type]
. Rzecz w tym, że ... jest mało prawdopodobne, że kiedykolwiek będzie to, czego się chce. Na Unit
głównym celem jest Type wskazuje na jedną wartość nie jest zainteresowany, więc nie ma sensu, aby otrzymać tę wartość.
Rozważmy na przykład
def f(x: Unit) = ...
Co można by zrobić x
? Może mieć tylko jedną wartość, więc nie trzeba jej odbierać. Jednym z możliwych zastosowań byłoby łączenie funkcji zwracających Unit
:
val f = (x: Unit) => println("I'm f")
val g = (x: Unit) => println("I'm g")
val h = f andThen g
Ponieważ andThen
jest zdefiniowany tylko w Function1
, a funkcje, które łączymy w łańcuch Unit
, zwracają , musieliśmy zdefiniować je jako typu, Function1[Unit, Unit]
aby móc je łączyć w łańcuch.
Źródła nieporozumień
Pierwszym źródłem nieporozumień jest myślenie, że podobieństwo między typem a literałem, które istnieje w przypadku funkcji 0-arity, istnieje również w przypadku wywołania według nazwy. Innymi słowy, myśląc tak, ponieważ
() => { println("Hi!") }
jest dosłownym przez () => Unit
, a następnie
{ println("Hi!") }
byłoby dosłowne dla => Unit
. Nie jest. To jest blok kodu , a nie literał.
Innym źródłem nieporozumień jest zapisanie wartości tego Unit
typu , która wygląda jak lista parametrów o zerowej wartości (ale tak nie jest).()
case class Scheduled(time: Int)(callback: => Unit)
. Działa to, ponieważ lista parametrów pomocniczych nie jest ujawniana publicznie ani nie jest uwzględniona w generowanychequals
/hashCode
metodach.