Przyjrzyj się takiemu zapisowi funkcji i pomyśl, jaki będzie efekt wykonania jej.
scala> def f(y: Int, x: => Int) = y f: (y: Int, x: => Int)Int scala> f(5, 1000^100000000) res2: Int = 5Jeśli dobrze rozumiem materiał, to wykonanie f powinno być równie efektywne co wykonanie f(5, 1), czyli skoro drugi argument funkcji jest wyliczany na czas użycia, a nie jest użyty w powyższym przykładzie, to do obliczenia wartości w ogóle nie dojdzie.
Na wykładzie o funkcjach wyższego rzędu pojawiła się notacja parametru wejściowego funkcji, który jest również funkcją i tutaj ponownie pojawił się symbol implikacji =>. To był ten moment, w którym kolejny raz naszło mnie na rozmyślanie o stałych jako swego rodzaju funkcji, które wyglądają jak funkcje stałe, ale różnią się tym, że nie są literałami funkcyjnymi a prymitywami.
scala> def g(h: () => Int) = h() g: (h: () => Int)Int scala> g(() => 5) res8: Int = 5Sądzę, że zrozumienie różnicy między stałymi a funkcjami stałymi ma niebagatelne znaczenie w poznawaniu zachowania języka wspierającego konstrukcje funkcyjne. W znanych mi językach - Java, Clojure, Scala - 5 jest zawsze 5 i "wykonanie" jej nie pozostawia śladu, podczas gdy wykonanie funkcji stałej zwracającej 5 może pozostawić swój ślad w środowisku.
A skąd mnie naszło na rozprawianie o tym?
Porównajmy zapis deklaracji argumentu wejściowego po nazwie x: => Int od deklaracji argumentu będącego funkcją f: Int => Int. W pierwszym przypadku definiujemy parametr wyliczany z opóźnieniem, w drugim podobnie, ale mamy dla tego specjalną nazwę - funkcja. Funkcja to obliczenie, które będzie wykonane na czas jego wykonania. Bardzo podobnie do owego opóźnionego wyliczania dla x: => Int. Jak zaznaczono w już wspominanym rozdziale Call by name w Effective Scala użycie funkcji do celów modelowania opóźnienia wykonania jest zalecane jako jawne wskazanie opóźnienia.
Zatem foruje się podejście oparte na funkcji.
scala> def f(y: Int, x: () => Int) = y f: (y: Int, x: () => Int)Int scala> f(5, () => 1000^100000000) res9: Int = 5Różnica niewielka, a jakie konsekwencje!
Ot, taka ciekawostka (para)naukowa, której objawienia mogłem doświadczyć podczas analizowania wykładu.
Sugeruję stosować radę "be applied with care" do tego typu zaleceń z effective scala; no daj spokój, deklaracja sposobu przekazania wartości parametru metody ma być znajomością Scali "na wyższym poziomie"? A podwójna strzałka w prawo występuje jeszcze w innych miejscach w Scali. Grzegorz Balcerek
OdpowiedzUsuńDla mnie użycie opóźnienia w wyliczaniu wartości jest równoznaczne z optymalizacją, której na początku mojej przygody ze Scalą pewnie nie potrzebuję. Coś mi mówi, że pewnie długo jeszcze nie będę potrzebował. Stąd twierdzę, że to znajomość na wyższym poziomie.
UsuńMożesz opisać przykład, który uzasadniałby zastosowanie tego opóźnienia? Gdzie udało Ci się z tego skorzystać? Gdzie widziałeś zastosowanie? Odpowiedzi na te pytania mogą znacząco usprawnić ocenę przydatności =>. Nie sądzisz?
Ok. Jednak "pewnie długo jeszcze nie będę potrzebował" a "znajomość na wyższym poziomie" to trochę inaczej brzmi, nie uważasz? Sposoby ewaluacji parametrów (przez wartość, przez nazwę) to jednak raczej podstawy informatyki, nie sądzisz?
OdpowiedzUsuńMożesz się z tym spotkać choćby korzystając z metody Option.getOrElse (zauważ dzielenie przez zero i brak wyjątku):
scala> Some(1).getOrElse(1/0)
res0: Int = 1
Inne przykłady sobie możesz podejrzeć choćby w mojej książce na stronach 43, 87-89, 92, 145, 147-152, 402-407. Miłej nauki. Grzegorz
Dla mnie to to samo - nie potrzebuję == wyższy poziom (znajomości tematu). Różnimy się po prostu w tym co uważamy za podstawy informatyki. Czyż to nie sprawia, że w ogóle dyskutujemy? Gdybyśmy się zgodzili już na początku, to o czym rozmawialibyśmy? :) Miłe.
UsuńNa pewno się z nimi zapoznam i pozwolę na komentarz.
Skoro tak stawiasz sprawę, to pozwalam sobie krótko uzasadnić. Otóż do podstaw informatyki zaliczam rachunek lambda. W szczególności jako podstawę programowania funkcyjnego. Natomiast ewaluacja parametrów przez wartość i przez nazwę ma bezpośredni związek ze strategiami redukcji termów rachunku lambda do postaci normalnej. Grzegorz
OdpowiedzUsuńMoje przydługie doświadczenie naukowe na UMK i MIMUW wskazuje, że rachunek lambda (pewnie jak topologia czy algebra uniwersalna) nie jest specjalnie wałkowany na studiach, więc nie oczekiwałbym obszernej wiedzy w społeczeństwie. Uważam, że to jeden z powodów, dla których nie dostrzega się piękna programowania funkcyjnego.
UsuńMoże najwyższa pora to zmienić przez praktyczne wprowadzenie do "produktów" teorii matematycznej, np. do języków funkcyjnych? Wciąż znajduję o tym mało w polskiej i zagranicznej blogosferze.