
(kod raytracer'a jest na końcu posta)
Na sam początek powiem że w Go pisze się całkiem wygodnie, w większości wypadków wygodniej niż w C/C++, chociaż w kilku przypadkach programiści z Google stworzyli całkiem dziwaczne potworki.
Zacznę może od tego co mi się podoba:
1. Deklarowanie i inicjowanie zmiennych za pomocą symbolu :=
W Go zmienne można deklarować i inicjować na dwa sposoby. Pierwszy z nich to:
var nazwa typ = wartosc;
var a int = 5;
Czyli w zasadzie to co znamy z C/C++/etc - wyrażenie w którym mamy jawnie podany typ, nazwę, oraz wartość (o tym sposobie inicjowania będzie jeszcze później, w wadach ;>
Druga metoda (ta która mi się tak podoba) wygląda tak:
nazwa := wartosc;
a := 5;
Jak widać nie ma tutaj nigdzie typu, a to dlatego że kompilator sam wnioskuje typ na podstawie wartości (np. stała 5 jest typu int) - okazało się to niesłychanie wygodne gdy potrzebowałem jakąś tymczasową zmienną która odebrała by od jakieś funkcji to co funkcja zwraca (ret := my_func(12,34);) (ciekawostka: w GCC możemy to częściowo wyemulować jakimś makrem typu #define VAR(a,b) typeof(b) a = b, i potem używać VAR(a,5)).
2. If i for bez ( )
Szczerze mówiąc na początku for bez ( ) wyglądał dla mnie przynajmniej dziwnie, ale jak się okazało, bardzo szybko przestawiłem się na taki zapis i wydawał mi się on intuicyjny. Natomiast if bez ( ) zaakceptowałem od razu. Oczywiście rozumiem, że jest to jedynie kwestia gustu i w Waszym przypadku możecie znienawidzić ten brak nawiasów od pierwszego wejrzenia ;>
Dodam że for się świetnie komponuje z :=, np.
for i := 0; i < 10; i++
{
...
}
3. Goroutines
Goroutines są to oddzielne wątki egzekucji, działające w obrębie zbioru threadów (przypomina mi to trochę fibery, tyle że ich przełączaniem zajmuje się biblioteka runtime) - czyli mamy natywną wielowątkowość! A jak wygodnie się tego używa! Rzućcie okiem na poniższy przykład
go moja_funkcja_dowolnego_typu(dowolne, argumenty int);
I tyle. Ani nie trzeba tworzyć funkcji wrapperów żeby pozbyć się niewygodnych typów / przekazywania argumentów w dodatkowym obiekcie/strukturze, ani nie trzeba szukać dokumentacji żeby wiedzieć ile jakiś CreateThread przyjmuje argumentów i w jakiej kolejności. Wrzuca się go przed wywołanie funkcji, i tyle.
Co ciekawe, funkcja może być zdefiniowana od razu po czasowniku go, tak więc czasami nawet nie trzeba tworzyć oddzielnej zew. funkcji (posłużę się przykładem z dokumentacji):
go func(ch chan<- bool) { for { sleep(10); ch <- true; }} (c)
4. Kanały (chan)
Kanały pojawiły się już w poprzednim przykładzie. W zasadzie taki kanał to taki lekki potok przyjmujący dane określonego typu, a korzystanie z niego przypomina korzystanie ze strumieni w C++. Nie musimy robić żadnego CreatePipe, WriteFile, czy ReadFile (z dziesiątkami parametrów), ani sprawdzania czy na pewno doszedł cały pakiet danych. Wystarczy stworzyć sobie zmienną z przymiotnikiem chan, i już mamy potok. Teraz tylko do niego pisać lub z niego czytać:
potok <- dane; // zapis
zmienna := <-potok; // odczyt (synchroniczny)
Dzięki temu że odczyt jest synchroniczny, można z tego również korzystać jako z prostej metody na poczekanie aż wątek skończy (odpalenie goroutine, i poczekanie aż przyjdą dane przez kanał).
Jest jeszcze trochę mniejszych rzeczy ;>
A teraz kilka rzeczy które mi nie podpasowało.
1. Brak jawnego zadeklarowania jaki interfejs klasa implementuje
Mamy sobie jakiś interfejs (abstrakcyjną klasę), i chcemy go zaimplementować. Jak się okazuje, wystarczy potworzyć metody które mają takie same nazwy jak metody w interfejsie. Niewątpliwie jest to wygodne, ale człowiek nieźle musi się po dokumentacji/kodzie naszperać żeby znaleźć jakąś klasę która implementuje jeden konkretny interfejs (w C++ zrobiłbym po prostu find po ": public klasa", a tutaj nici z tego, w klasie nie będzie żadnej informacji o tym co implementuje).
2. Dziwnie definiowane metody
Tym razem przykład:
func NormalnaFunkcja(argumenty typ) typ_zwracany { }
func (p *Klasa) Metoda(argumenty typ) typ_zwracany { }
To akurat może być kwestia gustu, ale Klasa::Metoda() z C++ jakoś bardziej mi pasuje niż (p *Klasa) Metoda() z Go (za dużo ozdobników). Ciekawym wyborem natomiast było pozwolenie programiście nazwania zmiennej wskazującej na 'własny' obiekt (w C++ było to this, a tutaj możemy sobie nazwać; w przykładzie na obiekt wskazuje zmienna p).
3. Inicjacja wielu zmiennych w deklaracji
Przykład:
var a,b,c,d int = 1,2,3,4;
Z jednej strony jest to wygodne - tok myślowy może lecieć jednym torem na raz - najpierw nazwy, potem wartości, nie trzeba się przestawiać (nazwa, wartość, nazwa, wartość, nazwa, wartość). Z drugiej jednak strony... nie powiem żeby czytanie tego potem było przyjemne (trzeba liczyć przecinki żeby wiedzieć co jest co). Tak w zasadzie ten przykład bardzo przypomina mi Perla - easy to write, hard to read ;>
Natomiast szczerze powiem, że prawdopodobnie przekonam się do tego zapisu w niedługim czasie.
I to w zasadzie tyle. Mimo paru rzeczy które mi się nie widzi, to bardzo prawdopodobne jest, że ten język zagości w moim edytorze na dłużej.
Na koniec dodam że język całkiem dobrze współpracuje z preprocesorem z GCC (cpp), tylko trzeba po preprocesingu wyciąć wszystkie linie zaczynające się od #. Po za tym szkoda trochę, że język jest jeszcze niedostępny na platformę Windows.
By the way...
If want to improve your binary file and protocol skills, check out the workshop I'll be running between April and June → Mastering Binary Files and Protocols: The Complete Journey
Dla zainteresowanych, kod raytracera (jest to mój drugi program w Go, zaraz po Hello World, tak więc nie spodziewajcie się cudownej czytelności kodu, czy korzystania ze wszystkich wygód które język oferuje):
t2.go (9.6 KB, go, src; btw kompilator Go jest niestety póki co dostępny tylko na *nixy)
I to chyba tyle...
UPDATE: Wygląda na to że nie byłem pierwszą osobą która napisała raytracer w Go;> Pozdrowienia dla Grammerjack ;>
Comments:
google-web-toolkit Google zrobiło fajne, ale czy nie za dużo wymyślają od nowa?
Automatyczne typowanie zmiennych na podstawie kontekstu, mamy to w C++0x ;)
Odonosnie () i {} to w tym temacie prym wiedzie Perl, gdzie wszedzie mozna uzywa, ale nie trzeba, do tego pozwala na ciekawe konstrukcje przypominajace potoczny humoidalny szyk zdania:
print "dupa" if a eq 0;
Nigdy jakos nie przepdalem za jezykami z garbage collectorem, procz skryptowych.
http://malcom.pinger.pl/m/540205
Anyway,
Ktos uzywa this w implementacjach klasy w Cpp? Jak dla mnie paskudnie to wyglada. Inna sprawa przy template, gdy w niektorych momentach po prostu trzeba... a tu oni jeszcze pozwalaja na wlasna nazwe, chyba tylko dla wytworzenia wiekszego zamieszania ;p
Przegladajac kilka artykulow, manuali, jakos nie przypada mi do gustu. Wole swoje stare, wyprobowane zabawki ;p
Ciekawe czy zdobedzie jakas nisze na rynku. Zapewne duzo w tym bedzie zawdzieczal temu, ze jest od Google. Obecnie niezaleznie co Google wydaloby, cos naprawde wartosciowego, czy istotny bubel, chetni sie zanajda... bo przeciez to od naszego nowego wielkiego monopolisty i musi byc dobre ;p
jednym słowem... MOAR!
Za dużo nowego wymyślania kół ;) Przypomina trochę Pascala i Pythona... Ja tam pozostaje przy języku C/C++ i języku D :)
Zapraszam na swoją stronkę!!
Kwestia gustu, jak zawsze przy wyborze języków ;>
Add a comment: