2010.04_The_Go_programming_language.pdf

(673 KB) Pobierz
441670120 UNPDF
Języki programowania
The Go programming
language
Język programowania Go jest językiem młodym, gdyż jego premierę
światową datuje się na 10 listopada 2009 roku. Właśnie wtedy na blogu
Google Code została zmieszczona informacja na temat upublicznienia
tego języka na licencji BSDL.
Dowiesz się:
• Czym jest język programowania go, jak rów-
nież poznasz podstawę semantyki i podsta-
wowe biblioteki owego języka;
• Jak tworzyć procedury GO (ang. goroutines )
i je obsługiwać.
Powinieneś wiedzieć:
• W celu zrozumienia niektórych elementów
artykułu przydatna jest znajomość dowol-
nego języka programowania bazującego na
składni języka C.
64-bitowych rodziny x86, który jest najdłu-
żej rozwijany, oraz arm, dla 32-bitowych pro-
cesorów ARM, jednak ten port nie jest jeszcze
kompletny. Tak więc przykładowymi zmienny-
mi globalnymi są:
GOARCH=amd64
GOBIN=/Users/aqu/bin
GOOS=darwin
GOROOT=/Users/aqu/go
kiego, bezpiecznego i łatwego języka, ze wspar-
ciem dla wielowątkowści od strony języka. Mi-
mo kilku niedogodności, które mogą z począt-
ku zdziwić programistów takich języków jak
C/C++, to myślę, że cel został w dużym stop-
niu osiągnięty.
Poziom
trudności
Przy czym warto zaznaczyć, że zmienna GOBIN
powinna znajdować się w zmiennej PATH , żeby
łatwiej było uruchamiać kompilator i linker.
Najlepiej te zmienne dodać do pliku ~/.bash_
proile , ponieważ zmienne te są potrzebne do
instalacji kompilatora, oraz kompilacji i linko-
wania jakichkolwiek programów. Sama insta-
lacja odbywa się bezstresowo i w bardzo pro-
sty sposób. Wystarczy wykonać serie następu-
jących komend:
Krótka historia języka
Sama idea stworzenia nowego języka miała
miejsce w siedzibie głównej Google, Mountain
View, California (zwana też Googleplex). Datę
tego pomysłu szacuje się na koniec roku 2007,
gdy pomysłodawcy Robert Griesemer, Rob Pike
i Ken Thompson stwierdzili, że kompilacja do-
wolnych aplikacji zajmuje zbyt dużo czasu. I od
tego momentu, aż do połowy roku 2008, język
istniał tylko na “tablicy”, a inicjatorzy projektu
poświęcali wolny czas na pracę nad nowym ję-
zykiem (polityka zatrudnienia Google oferuje
10% czasu dla deweloperów na własne projek-
ty). I tak oto powstał kompilator oraz środowi-
sko uruchomieniowe (ang. run-time ). Go zaczę-
ło żyć jako język wewnętrzny firmy stosowany
do pisania prostych oraz wielowątkowych apli-
kacji. Od tego czasu język był używany tylko
wewnątrz firmy, aż do listopada 2009, kiedy uj-
rzał światło dzienne.
Instalacja kompilatora
Wspomnieć tutaj trzeba, że na dzień dzisiejszy
niestety użytkownicy systemu Windows nie
mogą korzystać z języka go, gdyż wydane zo-
stały tylko wersje na Linuksa i Mac OS. Na ra-
zie nic nie wiadomo na temat portu na system
Microsoft’u. Autor zakłada, że czytelnik posia-
da komputer z systemem Linux lub Mac OS
z procesorem Intel. Aby móc zacząć używać ję-
zyka od firmy Google, należy pobrać jego źródła
przy pomocy mercurial’a komendą:
$ cd $GOROOT/src
$ ./all.bash
jeśli wszystko zakończy się powodzeniem,
zobaczymy komunikat
rodzaju:
hg clone -r release
https://go.googlecode.com/hg/ $GOROOT
--- cd ../test
N known bugs; 0 unexpected bugs
gdzie N to liczba
gdzie $GOROOT to całkowita ścieżka, w której
mają znaleźć się źródła. Po ściągnięciu źródeł
należy ustalić zmienne środowiskowe GOARCH ,
GOBIN , GOOS , GOROOT , z czego GOROOT już omó-
wiliśmy. GOOS określa nasz system, docelowe
wartości to linux i darwin, gdzie darwin ozna-
cza system Mac OS. Zmienna GOBIN przecho-
wuje ścieżkę, gdzie kompilator ma być zain-
stalowany. Natomiast zmienna GOARCH mówi
o tym, jaki rodzaj procesora posiadamy, akcep-
towalne wartości to: 386 dla procesorów 32-bi-
towych rodziny x86, amd64 dla procesorów
Jeżeli natomiast instalacja kompilatora zakoń-
czy się niepowodzeniem, to może to wskazy-
wać na jeden z dwóch problemów. Albo pró-
bujemy zainstalować program jako root, nie-
stety zabrania tego instalator, albo nie posia-
damy odpowiednich bibliotek. Rozwiązaniem
tego problemu jest instalacja aplikacji: kompi-
lator gcc, standardowa biblioteka C, parser ge-
nerator Bison i edytor tekstu ed. W przypadku
OS X biblioteki te zainstalowane być powin-
ny razem z pakietem XCode, w Linuksie nato-
miast wystarczy wykonać polecenie rodzaju:
Założenia projektowe
Głównym założeniem, jak już wspomniano,
była chęć przyśpieszenia etapu kompilacji, co,
jak się przekonamy później, udało się bardzo
dobrze. Jednak nie był to jedyny cel projek-
tantów, ponadto celem było stworzenie szyb-
34
04/2010
441670120.044.png 441670120.045.png 441670120.046.png 441670120.047.png 441670120.001.png 441670120.002.png 441670120.003.png 441670120.004.png 441670120.005.png 441670120.006.png
The Go programming language
$ sudo apt-get install bison gcc libc6-
dev ed
w katalogu $GOROOT/src/pkg/ . Widzimy de-
finicję funkcji main, która ze względu na swo-
ja specyfikę nie zwraca nic, brak zwracanych da-
nych można traktować trochę jak void w języku
C. Ważnym jest, że main() nie przyjmuje argu-
mentów, obsługa parametrów wprowadzanych
z konsoli odbywa się w inny sposób.
Następnie widać coś, co na pierwszy rzut oka
może wyglądać dziwnie, lecz nie jest niczym in-
nym, niż najzwyklejszą deklaracją zmiennych.
Programiści języków algolowych nie przywyk-
nęli do tego typu deklaracji, łatwo jednak “roz-
gryźć”, jak deklarować zmienne w tym języku,
semantyka jest następująca:
śli mamy kilka zmiennych, które chcemy zaini-
cjować, nie musimy tego robić jak w C:
Dodatkowo można jeszcze zainstalować na-
kładkę na pakiet gcc, który stworzy dla nas
aplikację gccgo, która sama już zajmie się kon-
solidacją i podstawową optymalizacją kodu bi-
narnego.
Gdy już zainstalujemy kompilator go, jeste-
śmy gotowi do pisania własnych programów
(oczywiście jeśli mamy poprawnie ustawione
zmienne globalne). Jednak wcześniej sprawdź-
my, na ile spełniona jest obietnica o szybkiej
kompilacji aplikacji.
int jeden = 1, dwa = 2, trzy = 3;
W go możemy zainicjować zmienne na końcu
linii, co czasem może być przydatne (związa-
nych jednak z zapisem z C zawiedzie, że zapis
znany z tego języka nie jest wspierany), powyż-
szy przykład w go można zapisać:
var jeden, dwa, trzy int = 1, 2, 3;
Rozwińmy teraz wspomniany już temat
zmiennej “samo określającej”. Tego typu
zmienne można interpretować jakby miały dy-
namiczny typ. Jednak ustalany tylko raz w ob-
rębie czasu jej życia. Przydaje się to, jeżeli nie
do końca jesteśmy pewni, jakiej zmiennej ocze-
kujemy, jaka będzie dla nas najodpowiedniej-
sza, lub po prostu jesteśmy za leniwi, żeby do-
pisywać typ i słowo var (przecież to jest całe od
5 do 10 znaków! ;).
Połączmy teraz kategorię multideklaracji
z deklarowaniem interpretowanym (samo okre-
ślającym), można przy połączeniu tych dwóch
rodzajów deklaracji zdefiniować wiele zmien-
nych, które powinny przyjmować różne typy
danych, bądź obsłużyć funkcje zwracające kilka
rodzajów danych (o nich później). Przykładem
na to może być następujący kod: Da on wynik:
Prędkość kompilacji
Najłatwiej to sprawdzić, kompilując źródła pa-
kietów, które dostajemy wraz z kompilatorem.
Znajdują się one w katalogu $GOROOT/src/
pkg , całkowita ilość kodu w tym katalogu w ję-
zyku go wynosi 128756 linii kodu(wraz z ko-
mentarzami), co łatwo można sprawdzić przy
pomocy narzędzia wc. W tym drzewie mamy
wszystkie operacje we/wy, funkcje matematycz-
ne, obsługę gniazd sieciowych, funkcje krypto-
graficzne i wiele innych. Teraz można spraw-
dzić, jak szybko wykona się kompilacja całego
tego poddrzewa, należy wykonać następujące
polecenia:
“var ” + nazwyZmiennych + typZmiennych “ =
“ wartości.
Programistom Pascala zapewne znajome jest
słowo kluczowe var, które określa miejsce,
w którym zaczyna się deklaracja zmiennych.
Później wypisujemy nazwy zmiennych uży-
tych w programie, na końcu mamy typ zmien-
nych wcześniej wypisanych. W stosunku do ję-
zyka C jest mniej wbudowanych typów, wy-
bierać możemy pomiędzy: int (typ całkowity),
loat (typ zmiennoprzecinkowy), bool (typ bo-
olowski, wartości prawda/fałsz), string (łań-
cuch znaków). Szczegóły jednak za chwilę. Na-
stępnie mamy przypisanie do zmiennej “samo
określającej”, specyiczny dla języka operator :
= mówi coś w rodzaju. Rozpoznaj typ danych
po prawej stronie i zainicjuj zmienne po lewej
z tą wartością. W kolejnej linii tworzymy tabli-
ce łańcuchów znakowych przez zwykłe rzuto-
wanie już istniejących wartości. Następnie wi-
dzimy pętle for , która w tym wypadku imi-
tuje znaną z innych języków pętlę while. Ran-
ge natomiast jest słowem kluczowym języka,
które możemy przyjąć za iterator elementów
tablicy hw. Tzn przypisuje zmiennej val war-
tości 0,1,..., i , gdzie i to ilość elementów tablicy
( range działa analogicznie, jeżeli mamy tablicę
mapowaną za pomocą dowolnego elementu).
W każdej iteracji pętli wypisujemy pojedyncze
słowo z tablicy hw przy pomocy funkcji Printf
pakietu fmt . Ostatnim krokiem jest wypisanie
znaku nowej linii przez zdeiniowaną funkcję
Println , ( Println nie przyjmuje ciągów for-
matujących) znaną z Pascal’a lub Java. Tyle jeśli
chodzi o szybki wstęp do języka. Przejdźmy do
szczegółowego opisu.
cd $GOROOT/src/pkg
make clean # czyscimy wszystkie pliki
wyjsciowe
int loat string bool
time make
Na komputerze z zainstalowanym systemem
Mac OS X 10.6 i procesorem C2D 2.4 GHz
w technologii peryn średni czas wyniósł:
8.978sec (z czego ~30% to ładowanie danych
do pamięci) przy różnym dodatkowym obcią-
żeniu procesora. Widać, że czas kompilacji ta-
kiego zestawu kodu jest dość imponujący. Jed-
nak mimo tego, czas ten uważany jest za jeden
z gorszych, ponieważ większość aplikacji użyt-
kowników kompiluje się w czasie poniżej 1sek.
czyli taki, jaki oczekiwaliśmy. Widać więc,
że multideklaracja wraz z deklarowaniem in-
terpretowalnym daje duże możliwości, nato-
miast jak się przekonamy, łącząc to z funkcja-
mi zwracającymi kilka danych, daje naprawdę
potężne możliwości programistyczne.
Dodatkowym typem są stałe, jak można się
domyślić, definiuje się je za pomocą słowa klu-
czowego const. Stałe możemy definiować na
trzy sposoby, pierwszy z nich nasuwa się z in-
nych języków programowania i ma postać: cont
nazwa typ = wartość .
Szybki wstęp do go
Przejdźmy teraz do omówienia składni go, po-
równaniem dla nas będzie język C.
Zacznijmy może od hello world, ale w nieco
zmodyfikowanej wersji.
W powyższym programie można zaobser-
wować już kilka specyficznych elementów, któ-
re pojawiają się w go, jednak dla programistów
Pythona czy Java są oczywiste. Pierwszym z ele-
mentów jest określenie nazwy pakietu. Może
on być dowolny, jeżeli pisany przez nas kod ma
być rodzajem biblioteki, jednak o tym dalej. Na-
stępne, co widzimy, to deklaracja importowa-
nia modułów zewnętrznych. W tym przypad-
ku używamy pakietu do formatowania stan-
dardowego wyjścia “fmt” (ang. ForMaT ). Listę
wszystkich pakietów można już było obejrzeć
Listing 1. hello_world.go
package main
import fmt
Zmienne, pętle, warunki, funkcje
Wyżej przedstawiony został prosty program ty-
pu hello world, i jego krótki opis, zajmijmy się
teraz bardziej szczegółowym opisem poszcze-
gólnych elementów powyższego programu.
Zacznijmy od zmiennych, powiedziane już
zostało, jak deklarować zmienne, jednak war-
to dodać, że można też skorzystać z, nazwijmy
to, multideklarowania (którą widać w przedsta-
wionej semantyce deklaracji). Chodzi o to, że je-
func main () {
var h string = Hello ;
w := world ! ;
var hw [] string = [] string { h , w } ;
for val := range hw {
fmt . Printf ( % s ”, hw [ val ]);
}
fmt . Println ();
}
www.sdjournal.org
35
441670120.007.png 441670120.008.png 441670120.009.png 441670120.010.png 441670120.011.png 441670120.012.png 441670120.013.png 441670120.014.png 441670120.015.png 441670120.016.png 441670120.017.png
Języki programowania
Co więcej, wcale nie musimy podawać typu
stałej, jaką deklarujemy, kompilator sam przypi-
sze jej typ (nie musimy używać nawet operatora :
= ), więc dopuszczalna jest deklaracja typu const
nazwa = wartość. Ponadto projektanci języka
wyszli naprzeciw programistom i jeżeli deklaru-
jemy wiele stałych w programie, możemy wyko-
nać rodzaj “rzutowania” na const. Umieszczając
wszystkie pożądane stałe w nawiasie. Przykłado-
wy zapis będzie wyglądał następująco:
var a []int = []int{1,2,3,4,5};
s := a[1:3];
przedstawiony już wariant zachowuje się jak
powszechnie znany while, ostatni wariant jest
nieskończoną pętlą,
z powyższego kodu wynika, że stworzymy ta-
blice liczb całkowitych o długości 2 (od indek-
su 1 do 3 wyłącznie), które przybiorą wartości
odpowiadające elementom tablicy a.
Długość tablicy możemy sprawdzić specjal-
ną funkcją len() .
Jeśli chodzi o zmienne, to pozostają nam do
omówienia mapy. Tak jak w innych językach
programowania, indeksy mapy mogą być do-
wolnym elementem (liczbą całkowitą, zmien-
noprzecinkową, łańcuchem znakowym czy in-
nym obiektem). Tablice mapowaną tworzymy
przy użyciu słowa kluczowego map. Tworze-
nie i używanie mapy odbywa się w następują-
cy sposób:
for { /* kod */ }
ważne jest, że po słowie kluczu for nie mo-
gą występować nawiasy, jest to sprzeczne z se-
mantyką języka. To tyle jeśli chodzi o pętle,
przejdźmy zatem do warunków.
Budowa instrukcji warunkowych w go nie
odbiega od tego typu instrukcji w innych języ-
kach programowania (może poza zakazem uży-
wania nawiasów), więc myślę, ze prosty przy-
kład wyjaśni wszystko.
Do wyjaśnienia jedynie zostaje użycie pa-
kietu time i rand, są one odpowiedzialne ko-
lejno za pobieranie/formatowanie czasu na
lokalnej maszynie oraz za generowanie liczb
pseudolosowych. Linia rand.Seed(time.S
econds()) ustawia ziarno w generatorze na
ilość sekund od daty 1 stycznia 1970 godz.
00:00:00 UTC, kolejna linia ustawia i na
wartość pseudolosową pomiędzy [0; n), resz-
ta kodu powinna być oczywista.
Drugim rodzajem instrukcji warunkowej
dostępnej w go jest switch. Tak jak w przy-
padku for składnia jest niemalże identycz-
na jak w innych językach programowania.
Pierwszą różnicą jest to, że jeżeli nie poda-
my “co porównywać”, to wstawiana jest war-
tość true, więc znów posłużymy się wyłącznie
przykładem z wykorzystaniem wyżej wymie-
nionej różnicy. Drugą jest brak słowa kluczo-
wego “break”, instrukcja switch jest automa-
tycznie kończona, gdy skończą się instrukcje
danego przypadku.
Pozostaje do omówienia jeszcze mecha-
nizm funkcji, jednak zostanie on rozwinię-
ty dodatkowo w dziale ze strukturami. Sa-
ma semantyka definicji funkcji wygląda na-
stępująco:
const ( a = 12; b = 13).
Należy się również słowo wyjaśnienia
o wskaźnikach, ponieważ te także wystę-
pują w go, i jak można się domyślić, są ozna-
czane tak samo jak w C/C++ przez znak
gwiazdki. Daje nam to możliwość przekazy-
wania zmiennych do funkcji przez wskaza-
nie. Przyda się też to do innej rzeczy przed-
stawionej później. Ciekawostką jest to, że
mimo tego, iż go posiada wskaźniki, to jed-
nak nie ma arytmetyki wskaźnikowej, co
jest, uważam, dużym plusem, jeśli chodzi
o bezpieczeństwo aplikacji. Brak arytmety-
ki wskaźnikowej oznacza, że nie możemy
mieszać w pamięci poza otrzymanym przy-
działem adresowym. Już nawet operacja od-
niesienia się do elementu z poza tablicy wy-
rzuca wyjątek i najczęściej kończy działanie
programu. Dodatkową informacją jest, że
gdy deiniujemy kilka zmiennych w linii, to
wskaźnik odnosi się do wszystkich, a nie je-
dynie do wybranej zmiennej, tak jak to ma
miejsce w C++. Więc zapis:
m := map[string]int{“jeden”:1, “dwa”:2};
Stworzy to dla nas mapę liczb całkowitych in-
deksowaną za pomocą łańcuchów znakowych,
do elementów takiej tablicy odwołujemy się
jak do zwyczajnej tablicy, tyle że przy użyciu
zdeiniowanych przez nas kluczy. Poprawny-
mi więc są odwołania:
m[“jeden”]; m[“dwa”];
Jednak należy pamiętać, że mapa jest struktu-
rą asocjacyjną, więc odwołanie się do elemen-
tu, który nie istnieje, utworzy go. Do przeglą-
dania map służy opisany już operator range .
Przejdźmy teraz do pętli. Tak naprawdę to
nie ma zbyt wiele do omawiania, gdyż wszyst-
kie dostępne pętle już widzieliśmy w programie
hello_world.go , chodzi tu o pętle for, istnieją
tylko jej warianty wprowadzające większą róż-
norodność. Pierwszą z możliwości jest klasycz-
ny for postaci
var a, b, c *int;
Utworzy trzy zmienne a, b i c, które będą
wskaźnikami na zmienne typu całkowitego.
Jest jeszcze specjalna konstrukcja zwana slice
(tłum. kawałek). Znana może ona być progra-
mistom Pythona. Slice, jak nazwa mówi, służy
do wycinania kawałka tablicy lub łańcucha zna-
kowego. Slice tworzy nową tablicę/łańcuch zna-
kowy tego samego typu, jakiego jest tablica “ro-
dzica”. Przykładem na tworzenie wykroju tabli-
cy może być następujący kod:
for init; warunek; po { /* kod */ }
Listing 3. if_statements.go
i działa identycznie jak for znany z innych języ-
ków programowania, następną postacią jest
package main
for warunek { /* kod */ }
import fmt
import rand
import time
Listing 2. vars_dec.go
package main
func main () {
rand . Seed ( time . Seconds ());
i := rand . Intn ( 51 );
if i > 25 {
fmt . Println ( i > 25 );
} else if i < 25 {
fmt . Println ( i < 25 );
} else {
fmt . Println ( i == 25 );
}
}
import fmt
func main () {
a , b , c , d := 1 , 0.5 , “ nazwa ”, true ;
// Uwaga użyjemy w funkcji Printf specyicznego dla go znacznika
// formatu %T, który mówi funkcji, ze powinna ona wydrukować typ
// zmiennej zamiast jej wartość.
fmt . Printf ( % T % T % T % T \ n ”, a , b , c , d );
}
36
04/2010
441670120.018.png 441670120.019.png 441670120.020.png 441670120.021.png 441670120.022.png 441670120.023.png 441670120.024.png 441670120.025.png
 
The Go programming language
“func ” + nazwaFunkcji + “(“ + zmienne +”)”
+ zwracanyTyp +
cialoFunkcji
Kolejnym elementem, na który chcę zwrócić
uwagę, jest to, że jeśli parametry tego samego ty-
pu, można umieścić na samym końcu listy argu-
mentów. Czyli poprawny jest zapis:
powiedniego pliku z kodem i jeżeli żadna z funk-
cji w nim zawartych nie używa funkcji z zaim-
portowanych paczek, to proces kompilacji koń-
czy się porażką, z komunikatem, że chcemy do-
łączyć bibliotekę, której nie używamy.
Przejdźmy teraz do dość ważnej części języ-
ka, jakim są struktury. Struktury w go są czymś
więcej niż te znane z C, ale czymś mniej niż kla-
sy z C++. Go samo w sobie nie posiada klasy,
jednak struktury w tem języku rekompensują
brak klas. Żeby zdefiniować dowolną strukturę,
gdzie zmienne wpisujemy podobnie jak przy
ich deklaracji, z tym że sam język nie obsługu-
je wartości domyślnych. W podanej semanty-
ce brakuje jeszcze jednego elementu, ale jak już
wspomniano, zostanie to uzupełnione w czasie
omawiania struktur.
Dowolna funkcja może zwracać dowol-
ną ilość danych, więc w ten sposób dość pro-
sto można zaimplementować złożoną obsługę
błędów. Przykładem funkcji, która zwraca kil-
ka wartości, może być np. parsowanie łańcucha
znakowego na część rzeczywistą i urojoną liczb
zespolonych. Nagłówek takiej funkcji mógłby
wyglądać następująco:
func foo(a, b string, c, d int)
Ostatnią rzeczą, jeśli chodzi o funkcję, jest jej
zasięg, czyli czy funkcja ma być widoczna po-
za pakietem. Coś na kształt static w C i public/
private w C++, choć w tym ostatnim odnosi się
to do klas. W go zasady są proste, jeśli funkcja/
zmienna/struktura/interfejs/typ zaczyna się
z dużej litery, to znaczy, że funkcja jest pu-
bliczna (widoczna poza pakietem). Jeśli nato-
miast nazwa zaczyna się z małej litery, ozna-
cza to, że funkcja lub zmienna będzie widocz-
na jedynie w obrębie pliku. Teraz powinno
mieć sens, dlaczego używane przez nas funk-
cje ( Printf , Seed , Intn ) były pisane z wiel-
kiej litery, parseComplex natomiast będzie wi-
doczna tylko w obrębie pliku, w którym zosta-
ła zdeiniowana.
Listing 4. switch_statement.go
package main
import fmt
func parseComplex(p string) (loat, loat)
func main () {
i := 3 ;
for {
switch {
case i == 3 : fmt . Println ( trzy ...” );
case i == 2 : fmt . Println ( dwa ...” );
case i == 1 : fmt . Println ( jeden ...” );
default : fmt . Println ( BUM !!! );
return ;
zapis ten oznacza, że funkcja parseComplex
przyjmuje jeden parametr typu string i zwra-
ca dwie wartości zmiennoprzecinkowe, użycie
takiej funkcji może być następujące:
Struktury, pakiety, interfejsy
Zacznijmy od czegoś, co używane było we
wszystkich programach, ale dokładne wyja-
śnienie zostało wstrzymane aż do teraz, mowa
o paczkach. Jak można się domyślić, definiuje-
my je słowem kluczowym package. We wszyst-
kich przedstawionych programach definiowali-
śmy paczkę main, i tak należy robić we wszyst-
kich swoich programach, o ile nie definiujemy
biblioteki. Gdy przekazujemy do kompilatora
program z paczką main, to kompilator wtedy
wie, gdzie znajduje się główna procedura main.
Inaczej jest, gdy piszemy jedynie bibliotekę do
szerszego użytku, wtedy nazwa pakietu jest do-
wolna. Można jeszcze wspomnieć o specjalnej
funkcji inicjalizującej, jaką może posiadać każ-
dy plik źródłowy, mowa o funkcji init() – nie
zwraca nic ani nie przyjmuje żadnych warto-
ści. Jednak jak można się domyślać w przypad-
ku użycia funkcji z kodu, w którym zdefiniowa-
no funkcje init, wywoływana jest najpierw wła-
śnie ona, a w przypadku paczki main, funkcja
init wywoływana jest nawet przed główną funk-
cją main() .
Zależności to coś, co było jednym z celów pro-
jektowych języka go, może nie same zależności,
ale ich nadmiar i dołączanie bibliotek z funkcja-
mi, które i tak nie są użyte w programie. Podczas
procesu konsolidacji programu dołączane są bi-
blioteki zewnętrzne, których nagłówki zostały
zdeklarowane w kodzie źródłowym, tak ma to
miejsce w C/C++, a co za tym idzie, powstaje
coraz większy i większy plik wykonywalny. Jed-
nak co, jeśli programista załączył nagłówek, któ-
rego i tak nie używa? Tracone jest miejsce na dys-
ku, a także czas uruchamiania takiego progra-
mu się wydłuża. Co to ma wspólnego z go? Ty-
le, że już podczas procesu kompilacji kompilator
sprawdza, jakie paczki zaimportowaliśmy do od-
re, im := parseComplex(“2+4i”);
Wtedy zostaną zainicjowane dwie zmienne typu
zmiennoprzecinkowego re i im . Co jednak, kie-
dy chcemy wyciągnąć z funkcji tylko jeden argu-
ment? Do takiego celu służy tzw zmienna anoni-
mowa, znana np. w Prologu, i w Go posiadająca
taki sam symbol _ (podkreślenie), oznacza to, że
tego rodzaju zmiennej nie można przypisać żad-
nej wartości ani używać jej w jakiejkolwiek funk-
cji. Jednak doskonale nadaje się ona właśnie do
celów “pomijania” dowolnych wartości z rezul-
tatu funkcji. Jeśli chcielibyśmy z naszej funkcji
parseComplex wyciągnąć tylko zmienną im , to
można funkcję wywoływać następująco:
}
i --;
}
}
Listing 5. interface_usage.go
package main
type Auto interface {
Odpal ();
}
type Mazda struct {
spalanie loat ;
liczba_drzwi , rocznik int ;
}
_, im := parseComplex(“2+4i”);
Deiniując funkcję, możemy także użyć zapi-
su nazwanego “nazwane parametry zwracane”,
które to parametry możemy nazwać podczas
deklaracji typu zwracanego. Zmienne takie zo-
staną zainicjowane przez zero, zyskujemy na
tym tyle, że gdy chcemy zwrócić właśnie te pa-
rametry, nie musimy podawać ich nazw (więc
zyskujemy czas i redukujemy ilość kodu). Po
raz kolejny posłużymy się naszą “funkcją” do
liczb zespolonych.
type Audi struct { }
func ( m Mazda ) Odpal () { }
func Sprzedaj ( a Auto ) {
a . Odpal ();
/* ... */
}
func main () {
var m Mazda ;
var a Auto ;
// var c Audi;
a = new ( Mazda );
Sprzedaj ( m );
Sprzedaj ( a );
// Sprzedaj(c);
}
func parseComplex(p string) (re loat, im
loat) {
// ...
return 0, 0; // bo możemy np tak reagować
na błędy
// ...
return; // tutaj zostaną zwrócone re i im
}
www.sdjournal.org
37
441670120.026.png 441670120.027.png 441670120.028.png 441670120.029.png 441670120.030.png 441670120.031.png 441670120.032.png 441670120.033.png 441670120.034.png 441670120.035.png 441670120.036.png
Języki programowania
najlepiej jest posłużyć się słowem kluczowym
type, którego semantyka jest następująca:
ciu słowa kluczowego var , lub przez stworze-
nie wskaźnika do nowoutworzonego obiek-
tu. Do tego celu służy kolejne nowe słowo klu-
czowe new() , które zwraca wskaźnik na nowo
utworzony obiekt z zainicjowanymi zerami.
Poprawnymi zapisami są więc:
nie jest może trochę chaotyczne, ale po przed-
stawieniu przykładu powinno się rozjaśnić
Widać, że funkcja Sprzedaj przyjmuje ja-
ko argument “obiekt” rodzaju Auto , który choć
sam jest interfejsem, to definiuje minimum, ja-
kie musi być dostępne w tym “obiekcie”, tutaj
jest nim funkcja Odpal (bo każde auto przed
sprzedażą trzeba sprawdzić). Jak widać, obiekt
typu Mazda posiada funkcję Odpal , jak i również
dodatkowe pola, oczywiście dodatkowe funkcje
także można dodać. Interfejs jednak nie pozwa-
la na definiowanie pól, które struktura powinna
posiadać, ponadto możemy zdefiniować zmien-
ną typu interface (w przykładzie var a Auto ),
w którą możemy “rzutować” typy bardziej złożo-
ne, ale spełniające wymagania interfejsu. Dlatego
też zakomentowane są linie Sprzedaj(c) i var c
Audi . Pierwszą linię ze względu na to, że struk-
tura Audi nie implementuje funkcji Odpal() ,
a drugą dlatego, że zadeklarowaliśmy zmien-
c , ale nigdzie w procedurze jej nie używamy.
Kompilator jest w tym przypadku tak samo re-
strykcyjny jak w przypadku odmowy kompila-
cji w przypadku nieużywania zaimportowanych
pakietów. Jeżeli jednak odkomentujemy te dwie
linie, otrzymamy od kompilatora komunikat:
“type “ + nazwaNowegoTypu + instniejący typ
natomiast strukturę tworzymy w nastepują-
cy sposób:
“struct {“ + pola + “}”
var liczba Complex;
liczba1 := new(Complex);
więc po raz kolejny odosząc się do liczb zespo-
lonych, możemy utworzyć następujący nowy
typ strukturowy:
z tą jednak różnicą, że liczba będzie typu
Complex , a liczba1 typu *Complex . Wcześniej
mówiliśmy, że struktury rekompensują brak
istnienia klas, jednak można zauważyć, że skoro
nie możemy stosować deinicji funkcji oddziel-
nie z deklaracją, to musielibyśmy zdeiniować
wszystkie funkcje wewnątrz struktury, co jed-
nak nie jest wygodne. Zamiast tego jednak mo-
żemy zdeiniować specjalną funkcję, która two-
rzy jakby rozszerzenie dowolnego typu danych.
Przykładowo dla naszej struktury zespolonej
funkcje sumy możemy zapisać następująco:
type Complex struct {
im, re loat
}
Warto zauważyć, że średnik nie jest wyma-
gany w żadnej z globalnej deinicji, gdyż w go
średnik jest separatorem, a nie terminatorem.
Nowy “obiekt” strukturowy możemy tworzyć
na dwa sposoby, tak jak wcześniej, przy uży-
Listing 6. goroutines.go
func (a *Complex) dodaj(b Complex) {
a.im += b.im;
a.re += b.re;
}
package main
import fmt
import time
missing Odpal()
który dodaje do liczby a wartość z b . Wywoły-
wanie funkcji odbywa się następująco:
Co wskazuje na to, że nie zaimplementowa-
liśmy funkcji Odpal() dla obiektu Audi . Ze
względu na to, że inferfejsy nie mogą posia-
dać wymienionych wymaganych pól, dla-
tego też nie możemy zdeiniować dla in-
terfejsu funkcji dla typu, jak to ma miejsce
w przypadku struktury czy innego dowol-
nie zdeiniowanego typu.
func () funkcja () {
time . Sleep ( 10 );
fmt . Println ( funkcja w tle );
}
var a,b Complex;
// inicjalizacja wartosc
a.dodaj(b);
func () main () {
go funkcja ();
}
i to wszystko. Dodaliśmy coś na kształt klasy
w C++. Co prawda nie tak elastyczne jak klasy,
jednak przy odrobinie gimnastyki można osią-
gnąć prawie tyle samo, co w językach obiekto-
wych. Największym problemem w tym przy-
padku jest brak możliwości tworzenia różnych
wersji funkcji, więc można powiedzieć, że mu-
simy mieć podejście trochę jak w Obj-C, znów
odnosząc się do przykładu z liczbami zespolo-
nymi, lista “konstruktorów” mogłaby wyglą-
dać następująco:
Wątki, kanały
Oddzielnym tematem w go jest współbieżność
aplikacji w nim napisanych. Ten temat także
był celem projektowym, ze względu na wciąż
powiększającą się liczbę rdzeni czy też coraz
większą ilość procesorów w komputerach biur-
kowych i serwerowych. Głównym problemem
współbieżności jest współdzielenie pamięci
i rozwiązywany jest poprzez mutexy, i tego po-
dejścia jak najbardziej możemy używać, typ
Mutex dostępny jest w paczce sync, jednak jest
to podejście dość niskopoziomowe. W go istnie-
je rozwiązanie dużo bardziej wysokopoziomo-
we zwane kanałami, o których za chwile. Ogól-
nie można przyjąć:
Nie komunikuj się przez pamięć współdzie-
loną, zamiast tego dziel pamięć przez komu-
nikację.
Można przybliżyć tę ideę poprzez przykład.
Załóżmy, że mamy jednowątkowy proces, z oczy-
wistych względów nie wymaga on synchroniza-
cji. Przy uruchomieniu kolejnej instancji progra-
mu, pamięć nie musi być synchronizowana. Jed-
nak można sprawić, żeby oba te procesy się ze so-
bą komunikowały, jeżeli teraz komunikat jest
Listing 7. chanells.go
package main
import fmt
func odliczaj ( ch chan int , from int ) {
for from >= 0 {
ch <- from ;
from --;
}
}
func Complex_initWithValues(a, b loat) (c
*Complex) { /* ...
*/ }
func Complex_initFromString(a string) (c
*Complex) { /* ...
*/ }
func main () {
ch := make ( chan int );
go odliczaj ( ch , 7 );
for {
a := <- ch ;
fmt . Println ( a );
if a == 0 {
break ;
}
}
}
Możliwości są na tyle ograniczone, na ile wy-
obraźnia programisty.
Interfejs to próba zaadoptowania objektowo-
ści w go, najprościej mówiąc, interfejsy w go mó-
wią, że jeżeli coś posiada jakąś własność, to może
być użyty w jakimś celu . Można to rozumieć ja-
ko swojego rodzaju dziedziczenie i rzutowanie
w dół/górę hierarchii obiektów, choć sama hie-
rarchia obiektów w go nie występuje. Wyjaśnie-
38
04/2010
441670120.037.png 441670120.038.png 441670120.039.png 441670120.040.png 441670120.041.png 441670120.042.png 441670120.043.png
 
Zgłoś jeśli naruszono regulamin