(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...
On 22nd Nov'24 we're running a webinar called "CVEs of SSH" – it's free, but requires sign up: https://hexarcana.ch/workshops/cves-of-ssh (Dan from HexArcana is the speaker).
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: