Plan jest prosty
- Jak używa się pętli w Groovy?
- Jak to jest możliwe, że to da się tak zrobić?
Pewną archaiczną konstrukcją języka odziedziczoną po Javie są pętle ;)
int i = -1;
while(i++ < 3){
System.out.println(i);
}
// lub
for(int i = 0; i < 4; i++){
System.out.println(i);
}
Groovy rzeczywiście akceptuje tak napisany kod.
groovy> for(int i = 0; i < 4; i++){ System.out.println(i); }Ja jednak preferuje styl funkcyjny.
0
1
2
3
===> null
groovy> 4.times{ it -> println(it) }
0
1
2
3
===> null
// A najbardziej lubie
groovy> (0..3).each{ println it }
0
1
2
3
===> null
Mając więc do wyboru
for(int i = 0; i < 4; i++){lub
System.out.println(i);
}
(0..3).each{ println it }
Co wybierzecie? Gdzie łatwiej stwierdzić ile razy coś się wykona, albo co właściwie się stanie?
Nie przekonani? To może inny przykład
String s = "abcde";
for(int i = 0; i < s.length(); i++){
System.out.println(i);
}
//względem
"abcde".each{ znak -> println znak }
No ale jak to jest właściwie możliwe ?
Wszystkie kolekcje oraz String i zakresy w Groovy mają przydatne funkcje jak each, find, collect, every, any itd. o których więcej w następnych częściach. Na razie wystarczy wiedzieć, że funkcje te pobierają jako parametr zmienną będącą funkcją. Funkcja ta pobiera jako parametr jeden obiekt z kolekcji.
No dobrze, ale żeby przesłać funkcję do innej funkcji najpierw trzeba ją zdefiniować.
W Java można tworzyć funkcje jedynie bezpośrednio w klasach, Groovy tak też potrafi.
// JavaW Groovy można definiować funkcję praktycznie w dowolnym miejscu. Możemy to zrobić np. za pomocą def.
class A{
public void f(Object x){
System.out.println(x);
}
}
// Groovy
groovy> class A{ public void f(Object x){ System.out.println(x);} }
===> true
// W Groovy można też pominąć wszystkie typy, pozbyć się średnika i skrócić System.out... do samego println
class B{
public f(x){
println(x)
}
}
//uruchamiam funkcję
groovy> a = new A()
===> A@1de498
groovy> a.f(1)
1
===> null
groovy> def f(x){ println(x) }
===> true
groovy> f(1)
1
===> null
// tak zdefiniowaną funkcje mogę już wysłać do innej funkcji np.:
groovy> (0..3).each(f)
0
1
2
3
===> 0..3
W językach funkcyjnych często tworzy się funkcje anonimowe, które są przypisywane zmiennym lub parametrom innych funkcji. Przykładowo możemy przypisać zmiennej g funkcję pobierającą Object i wypisującą go na ekran jako:
g = { Object it -> println(it); } // ten zapis możemy uprościć pozbywając się informacji o typie i średnika
g = { it -> println(it) } // nawiasy też nie są potrzebne ponieważ f(x) == f x - podobnie jak w Ocaml
g = { it -> println it }
// ostatecznie mamy:
groovy> g = { it -> println it }
===> groovysh_evaluate$_run_closure1@10e434d
groovy> g(1)
1
===> null
// tak stworzoną funkcję możemy ponownie wysłać do innej funkcji:
groovy> (0..3).each(g)
0
1
2
3
===> 0..3
(0..3).each(g) // możemy ponownie pozbyć się nawiasów
(0..3).each g // właściwie nie ma powodu by definiować osobną zmienną, możemy funkcję zdefiniować natychmiast
(0..3).each { i -> println i} // tutaj nazwaliśmy parametr i,
// co ciekawe parametr o nazwie it jest parametrem domyślnym i nie trzeba go w ogóle specyfikować
// więc można napisać:
(0..3).each { println it }
Ostatnia linijka kodu definiuje anonimową funkcję pobierająca jeden obiekt pod nazwą it i każe wykonać tą funkcję dla każdego obiektu z zakresu od 0 do 3 włącznie.
I tym sposobem zatoczyłem koło.
Na taki styl pozwalają języki, które traktują funkcje jak zwykłe obiekty (First-class function), więc np. JavaScript, Ruby, Python, ActionScript, Scala. Jeśli pozwalają to na pewno z niego korzystają. Jeśli komuś się taki styl nie podoba, to muszę przyznać, że ma pecha. Nawet następny standard C++ (C++0x) będzie najpewniej mieć domknięcia, C# już je ma, a Java ma 3 propozycje domknięć i prawdopodobnie w Java 1.7 domknięcia już będą. Pewnie nie tak ładnie syntaktycznie jak w Groovy, ale będą.
Innymi słowy w 2010 wszystkie główne języki programowania będą obsługiwać domknięcia. I to pełne domknięcia, nie tylko funkcje jako obiekty (a co to tak naprawdę oznacza, to może innym razem).
Brak komentarzy:
Prześlij komentarz