Próbuję przedstawić funkcję, która nie przyjmuje argumentów i nie zwraca wartości (symuluję funkcję setTimeout w JavaScript, jeśli musisz wiedzieć).
case class Scheduled(time : Int, callback : => Unit)
nie kompiluje się, mówiąc, że „parametry val mogą nie być wywołane przez nazwę”
case class Scheduled(time : Int, callback : () => Unit)
kompiluje się, ale zamiast tego musi być dziwnie wywołany
Scheduled(40, { println("x") } )
muszę to zrobić
Scheduled(40, { () => println("x") } )
Co też działa
class Scheduled(time : Int, callback : Unit => Unit)
ale jest przywoływany w jeszcze mniej rozsądny sposób
Scheduled(40, { x : Unit => println("x") } )
(Jaka byłaby zmienna typu Unit?) To, czego chcę, oczywiście, to konstruktor, który można wywołać w taki sam sposób, w jaki wywołałby go, gdyby była to zwykła funkcja:
Scheduled(40, println("x") )
Daj dziecku butelkę!
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.Odpowiedzi:
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ę:
Jeśli tak to nazywam
Następnie kod zostanie wykonany w ten sposób
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 plikuFunction0
. 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,
jest anonimową funkcją, literałem arity 0, której typ to
Moglibyśmy więc napisać:
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 typuUnit
. Innymi sposobami zapisu byłyby(Unit) => Type
lubFunction1[Unit, Type]
. Rzecz w tym, że ... jest mało prawdopodobne, że kiedykolwiek będzie to, czego się chce. NaUnit
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
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ącychUnit
:Ponieważ
andThen
jest zdefiniowany tylko wFunction1
, a funkcje, które łączymy w łańcuchUnit
, 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ż
jest dosłownym przez
() => Unit
, a następniebył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).()
źródło
case ... =>
, więc nie wspomniałem. Smutne ale prawdziwe. :-)1
, znak'a'
, ciąg znaków"abc"
lub funkcja() => println("here")
). Może być przekazywany jako argument, przechowywany w zmiennych itp. „Blok kodu” to składniowe rozgraniczenie instrukcji - nie jest wartością, nie można jej przekazywać, ani nic w tym stylu.(Unit) => Type
vs() => Type
- pierwsza to aFunction1[Unit, Type]
, a druga to aFunction0[Type]
.case
Modyfikatora umożliwia ukryteval
OUT każdy argument konstruktora. Dlatego (jak ktoś zauważył), jeśli usunieszcase
, możesz użyć parametru wezwania według nazwy. Kompilator prawdopodobnie i tak by na to pozwolił, ale może zaskoczyć ludzi, gdyby utworzyłval callback
zamiast przekształcać się wlazy val callback
.Kiedy zmieniasz na
callback: () => Unit
teraz, twoja sprawa przyjmuje po prostu funkcję, a nie parametr call-by-name. Oczywiście funkcja może być przechowywana w,val callback
więc nie ma problemu.Najłatwiejszym sposobem uzyskania tego, czego chcesz (
Scheduled(40, println("x") )
gdzie parametr call-by-name jest używany do przekazania lambda) jest prawdopodobnie pominięciecase
i jawne utworzenie tegoapply
, czego nie możesz uzyskać w pierwszej kolejności:W użyciu:
źródło
W pytaniu chcesz zasymulować funkcję SetTimeOut w JavaScript. Na podstawie wcześniejszych odpowiedzi piszę następujący kod:
W REPL możemy uzyskać coś takiego:
Nasza symulacja nie zachowuje się dokładnie tak samo jak SetTimeOut, ponieważ nasza symulacja jest funkcją blokującą, ale SetTimeOut nie blokuje.
źródło
Robię to w ten sposób (po prostu nie chcę przerywać aplikacji):
i nazwij to
źródło