Table of Contents
- Parathënie
- Hyrje
- Udhëzime
- Variablat e ambientit
- Rregullat e përgjithshme
- Emërtimet e fajllave dhe identifikatorëve
- Variablat
- Konstantat
- Tipet e të dhënave
- Numrat
- Stringjet
- Degëzimi (if)
- Degëzimi (switch)
- Ciklet (for)
- Tipet kompozite: Vargjet
- Tipet kompozite: Segmentet
- Tipet kompozite: Mapat
- Tipet kompozite: Struktet
- Pointerët
- Interfejsat
- Funksionet
- Funksionet anonime
- Defer
- Error, panic & recover
- Gorutinat, kanalet, sinkronizimi
- HTTP Webserver
- Package net
- Databazat
- I/O and File Systems
- Package fmt
- Package strings
- Regular Expressions
- REST API: Konceptet bazike
- REST API: Karakteristikat
- REST API: HTTP metodat dhe status kodet
- REST API: Metoda GET
- REST API: Metodat POST, PUT, PATCH, DELETE dhe OPTIONS
- REST API: Status kodet
- HTTP Request/Headers/Response
- Web app: Web Programming Basics
- Web app: Web aplikacioni bazik
- Web app: Dizajnimi i aplikacionit
- Web app: Databases
- Web app: Forms
- Web app: Upload
- Web app: Templates
- Web app: Autentikimi
- Web app: Files
- Web app: Routing
- Web app: Middleware
- REST API (JSON and XML)
- Web app: Unit testing
- Gorilla
- Referencë koncize e funksioneve
- Pyetje
- Përgjigjet
- Shembuj
- Shembull: Krahasimi i dy vlerave të përafërta float
- Shembull: Argumentet nga konzola
- Shembull. Leximi i vlerës nga konzola
- Shembull: Shuma e numrave të lexuara nga konzola
- Shembull: Strukti me metodë
- Shembull: Thirrja e metodës së struktit nga një funksion
- Shembull: Thirrja e një metode të struktit të brendshëm
- Shembull: Konvertimi i struktit në JSON
- Shembull: Strukt në JSON, JSON në strukt
- Shembull: Leximi i JSON fajllit me parsim me strukt
- Shembull: Maps - Qytetet dhe numri postar
- Shembull: Map me listë shtetesh dhe numër të banorëve
- Shembull: Bubble Sort
- Shembull: Fizzbuzz
- Shembull: Gjenerimi i sha256 hash
- Shembull: Gjeneratori i stringut të rastësishëm
- Shembull: Fibonacci sekuenca
- Shembull: Numri prim
- Shembull: Shkrimi dhe leximi nga fajlli i formatit CSV
- Shembull: Enkodimi/dekodimi i map në JSON
- Shembull: Nga strukt në XML
- Shembull: Enkodimi/dekodimi me base64
- Shembull: Enkriptim/dekriptim me XOR
- Shembull: Bitwise shift operator me enumerated constants
- Shembull: ACL sistem me enumerated constants
- Shembull: Faqe dinamike, të dhënat nga databaza në template
- Shembull: Servimi dinamik i një aseti statik
- Shembull: Zbërthimi i një URL
- Shembull: Thirrja e funksionit anonim nëpërmes një variabli
- Literatura
- Resurse online
Parathënie
Kontakti me autorin: tahir.hoxha@gmail.com
Linkedin: https://www.linkedin.com/in/tahirhoxha/
Ky libër shërben si material ndihmës gjatë mbajtjes së kursit të Go në AUK/TDI. Përmbajtja ndryshon në vazhdimësi sepse shtohen materiale të reja në mënyrë periodike.
Hyrje
Gjuha programore Go është gjuhë programore e re e krijuar nga Google, së pari e prezantuar në vitin 2009. Autorë të Go janë Robert Griesemer, Rob Pike dhe Ken Thompson.
Go është gjuhë programore me dedikim të përgjithshëm që karakterizohet me një sintaksë të pastër dhe të thjeshtë. Është i disponueshëm në sisteme operative të ndryshme, posedon dokumentacion të shkëlqyeshëm dhe komunitet në rritje e sipër.
Go adopton koncepte të ndryshme nga gjuhët e tjera programore, gjithnjë duke i ikur kompleksitetit të panevojshëm dhe krijimit të një kodi sa më efiçent dhe të menaxhueshëm. Implementon një koncept të veçantë të OOP (object oriented programming) dhe ofron menaxhim automatik të memorjes (garbage collection).
Duke qenë më i thjeshtë se shumica e gjuhëve të tjera, është lehtë i kuptueshëm nga ana e programuesve që njohin ndonjë gjuhë tjetër programore. Përdoret për programim procedural, ka karakteristika të programimit funksional, por siç u cek më sipër - edhe një variant të thjeshtuar të programimit të bazuar në objekte.
Go po tregohet i shkëlqyeshëm në veçanti në ndërtimin e Web aplikacioneve dhe mikroserviseve, ku karakterizohet me ekzekutim të shpejtë dhe procesim të një numri të madh të HTTP kërkesave (HTTP requests) dhe HTTP përgjigjeve (HTTP responses). Megjithatë, Go nuk është i kufizuar në aplikacione të këtij lloji - ai gjithsesi mund të tregohet shumë i mirë edhe me grafikë, mësim makinerik (machine learning), aplikacione mobile, etj.
Karakteristikë tjetër e Go është se ai është open source, kështu që të gjitha mjetet e nevojshme për punë u janë në dispozicion gjithkujt dhe janë pa pagesë.
Go si gjuhë e re progamore është krijuar në radhë të parë për të qenë gjuhë e thjeshtë edhe efiçente për ndërtimin e back-end sistemeve. Go në veçanti ka fituar popullaritet si gjuhë për shkrimin e Web aplikacioneve dhe mikroserviseve.
Go një set të begatshëm dhe gjithpërfshirës të librarive standarde.
Në sintaksë është i ngjashëm me C, por ka veti unike të cilat nuk i hasim në gjuhët e tjera. Ndonëse konsiderohet si trashëgimtar i C, Go në fakt trashëgon edhe karakteristika nga gjuhë të ndryshme programore. Është i ngjashëm me C për sa i përket strukturave kontrolluese (control-flow statements), tipeve bazike të të dhënave (basic data types), sintaksën e shprehjeve (expression syntax), etj.
Ndër objektivat primare të Go është që për nga performanca të jetë i krahasueshëm me C, gjë që nuk është arritur në tërësi. Megjithatë, Go tregon performancë superiore në krahasim me gjuhët e interpretuara, në veçanti kur kemi të bëjmë me ekzekutimin konkurrent të kodit nëpërmes goroutines, me anë të të cilave mundësohet procesimi i një numri të madh të kërkesave njëkohësisht. Kjo në veçanti është e rëndësishme për Web aplikacionet, të cilat duhet t’iu nënshtrohen kërkesave të shumëfishta në të njëjtën kohë.
Tri qëllime kryesore të dizajnuesve të Go kanë qenë:
- Tipizimi statik (static type) dhe efiçenca gjatë kohës së ekzekutimit (run-time effeciency)
- Lexueshmëria dhe përdorshmëria
- Performanca e lartë në operacionet rrjetor dhe multiprocesim
Është gjuhë statically typed, veti kjo që e diferencon nga gjuhët me tipe dinamike siç janë PHP, Python, Ruby, etj. Te gjuhët programore statically typed, variablat deklarohen në mënyrë eksplicite përfshirë këtu edhe tipin e variablit (nëse është numër i plotë, numër me presje dhjetore, tekst, etj). Këtij grupimi të gjuhëve programore i përkasin edhe gjuhët: Java, C, C#, C++, Scala, etj.
Programi në Go duhet të kompajlohet para se të ekzekutohet. Kjo nënkupton që kodi shkruhet në një tekst editor, më pas thirret kompajleri i cili nga kodi burimor (source code), e krijon kodin ekzekutues në gjuhën makinerike. Kjo procedurë duhet të ndiqet pas çdo ndryshimi në kodin burimor.
Ekzektimi i programit të shkruar në Go është shumë më i shpejtë se i atyre të shkruara në gjuhët e interpretuara siç janë PHP apo Python dhe për shkak se është statically typed, shumë më rrallë mund të paraqiten gabime gjatë ekzekutimit, sepse shumë prej gabimeve mund të detektohen gjatë fazës së kompajlimit, pra para ekzekutimit.
Go mundëson gjenerimin e kodit ekzekutues për sisteme operative të ndryshme: Windows, Linux apo macOS, pa marrë parasysh në cilin sistem operativ jeni duke e shkruar kodin burimor. Pa marrë parasysh sa fajlla me kod burimor keni krijuar, gjatë procesit të kompajlimit në Go do të krijohet një fajll me kod ekzekutues.
Kompanitë që e përdorin gjuhën programore Go në projektet e tyre:
https://github.com/golang/go/wiki/GoUsers
Instalimi
Go mund të instalohet nga adresa:
ku e zgjedhni platformën e dëshiruar: Microsoft Windows, Apple macOS apo Linux. Rekomandohet të zgjedhet versioni i fundit, që në momentin e shkrimit është 1.12.1.
Për distribuimin binar të instaluesit për Windows 64 bitësh zgjedhet go1.12.1.windows-amd64.msi, ndërsa për versionin 32 bitësh zgjedhet go1.12.1.windows-386.msi.
Në mënyrë standarde, Go instalohet në folderin C:Go .
Për të kaluar në një version më të ri, shkarkohet instaluesi i versionit të ri dhe gjatë ekzekutimit ai automatikisht do ta largojë versionin ekzistues nga sistemi për ta instaluar versionin e ri, pa i prekur projektet ekzistuese. I njëjti instalues mund të përdoret edhe për deinstalimin e Go nga sistemi.
Pas instalimit, kujdesemi që variabli i ambientit GOPATH të tregojë lokacionin e dëshiruar për projektet tona. Në shembullin e më poshtëm është zgjedhur folderi go brenda folderit të përcaktuar me variablin %USERPROFILE%, që në shumicën e rasteve nënkupton C:UsersPerdoruesi , por mund ta zgjedhim cilindo folder, sipas preferencës tonë.
Editorët
Për ta shkruar kodin në Go, mund të përdoret cilido tekst editor. Megjithatë, ekzistojnë programe të cilat janë të specializuar për lehtësimin e programimit në këtë gjuhë, siç janë:
- GoLand nga JetBrains. Komercial.
- Visual Studio Code. Pa pagesë.
- Atom. Pa pagesë.
Në Visual Studio Code duhet të instalohet plugin-i për Go duke shkuar në File - Extensions - Go.
Për testimin e programeve të thjeshta, mund të përdoret Go Playground.
Hello World në Go
https://play.golang.org/p/vc4MvaHUPo2
Funksioni main() është pika hyrëse (entry point) e programit, respektivisht nga këtu fillon ekzekutimi i kodit, prej nga pastaj mund të thirren funksionet e tjera, me çka vazhdon rrjedha e ekzekutimit të programit.
Udhëzime
Nëse jeni të detyruar ta kopjoni kodin nga libri, dhe me atë rast ju dalin edhe numrat rendorë të rreshtave programorë, rreshtat mund t’i largoni automatikisht duke e kopjuar kodin dhe bartur në faqen:
http://remove-line-numbers.ruurtjan.com/
Variablat e ambientit
$GOROOT
$GOROOT_FINAL
$GOOS and $GOARCH
Sistemet operative dhe arkitekturat e kompajlimit.
$GOOS - $GOARCH kombinimet
- android / arm
- darwin / 386
- darwin / amd64
- darwin / arm
- darwin / arm64
- dragonfly / amd64
- freebsd / 386
- freebsd / amd64
- freebsd / arm
- linux / 386
- linux / amd64
- linux / arm
- linux / arm64
- linux / ppc64
- linux / ppc64le
- linux / mips
- linux / mipsle
- linux / mips64
- linux / mips64le
- linux / s390x
- netbsd / 386
- netbsd / amd64
- netbsd / arm
- openbsd / 386
- openbsd / amd64
- openbsd / arm
- plan9 / 386
- plan9 / amd64
- solaris / amd64
- windows / 386
- windows / amd64
$GOHOSTOS / $GOHOSTARCH
$GOBIN
Rregullat e përgjithshme
Sintaksa e Go kërkon përdorimin e pikëpresjes (semicolons ;) si statement terminator. Mirëpo vendosja e pikëpresjes nuk është e domosdoshme sepse këtë e bën kompajleri në mënyrë automatike në fund të rreshtave programorë.
Vendosja manuale e pikëpresjes është e domosdoshme kur dëshirojmë t’i vendosim disa statements në një rresht programor. Po ashtu edhe kur kemi të bëjmë me ciklet for
, me atë ai rresht të përfundojë me {, shenjë që përdoret për hapjen e bllokut të ciklit. Në të kundërtën, kompajleri do të vendosë automatikisht pikëpresje në fund të inkrementit (i++), me çka vie deri te gabimi gjatë kompajlimit.
https://play.golang.org/p/4sgL67gpCBh
Gabim:
Në këtë rast, lajmërohet gabimi: “syntax error: unexpected newline, expecting { after for clause
”.
Në përgjithësi, kllapa hapëse e bllokut duhet të vendoset në fund të rreshtit dhe jo në rresht të ri. Nëse kllapat hapëse i kemi vendosur në rresht të ri, në editor mund ta bëjmë ri-formatimin e kodit për të qenë konform me rregullat e Go. Në GoLand kjo bëhet me Code - Reformat Code
.
Emërtimet e fajllave dhe identifikatorëve
Programet përbëhen nga:
- Fjalët kyçe (keywords),
- Konstantat (constants),
- Variablat (variables),
- Operatorët (operators),
- Tipet (types), dhe
- Funksionet (functions).
Go i përket gjuhëve programore case-sensitive, pra është ka rëndësi nëse shkronjat janë të mëdha (uppercase) apo të vogla (lowercase).
Emërtimet e variablave fillojnë patjetër me shkronjë, ku si shkronjë konsiderohet cilido Unicode UTF-8 karakter. Në vazhdim të shkronjës së parë mund të përdorim shkronja të tjera apo numra.
Shenja _ e shënuar si e vetme ka kuptim të veçantë dhe quhet blank identifier. Përdoret në rastet kur ndonjë nga vlerat kthyese të funksionit nuk dëshirojmë t’i ruajmë si vlerë në ndonjë variabël, respektivisht kur dëshirojmë ta injorojmë një vlerë kthyese të funksionit.
Ka raste kur variablat, tipet dhe funksionet nuk kanë nevojë fare të kenë emra dhe këto quhen anonime (anonymous).
Në vijim janë të shënuara 25 fjalët kyçe (keywords) të Go:
- break
- case
- chan
- const
- continue
- default
- defer
- else
- fallthrough
- for
- func
- go
- goto
- if
- import
- interface
- map
- package
- range
- return
- select
- struct
- switch
- type
- var
Një fjalë kyçe nuk mund të përdoret si identifikator.
Përveç fjalëve kyçe, Go ka dhe 36 identifikatorë të paradefinuar, të cilët paraqesin emrat tipeve elementare dhe të funksioneve interne (built-in).
- append
- bool
- byte
- cap
- close
- complex
- complex64
- complex128
- uint16
- copy
- false
- float32
- float64
- imag
- int
- int8
- int16
- uint32
- int32
- int64
- iota
- len
- make
- new
- nil
- panic
- uint64
- println
- real
- recover
- string
- true
- uint
- uint8
- uintptr
Delimiterët
Në Go përdoren delimiterët vijues:
- Kllapat (parentheses) ( ),
- Kllapat e mesme (brackets) [ ]
- Kllapat e mëdha (braces) { }.
Shenjat e pikësimit Si shenja të pikësimit përdoren:
- .
- ,
- ;
- …
Strukturimi i kodit
Kodi strukturohet në formë të formulimeve (statements). Formulimet nuk është e domosdoshme të përfundojnë me pikëpresje (;) si në disa gjuhë të tjera, sepse kompajeli i Go e bën futjen automatike të pikëpresjes në fund të çdo formulimi. Në rastet kur i vendosim disa formulime në të njëjtin rresht, atëherë formulimet patjetër duhet të ndahen me pikëpresje.
Struktura bazike dhe komponentet e një Go programi
Struktura e përgjithshme e një programi në Go
- Bëhet importimi i pakove
- Pas importimit, deklarohen konstantat, variablat dhe tipet
- Pastaj vie funksioni init() nëse është i nevojshëm dhe ky është një funksion special që e përmban çdo pako dhe që ekzekutohet i pari.
- Më pas vie funksioni main() dhe atë vetëm në kuadër të pakos main.
- Pastaj renditen të gjitha funksionet tjera. Metodat ndaj tipeve në fillim ose funksionet me atë renditje si thirren brenda funksionit main(), ose metodat dhe funksionet të renditura sipas alfabetit nëse numri i funksioneve është i madh.
Pakot
Me anë të pakove bëhet strukturimi i kodit. Programi ndërtohet si pako që mund t’i pakot tjetra për funksione të caktuara.
Çdo Go fajll i përket një pakoje, analogjikisht me bibliotekat apo namespace në gjuhët tjera.
Shumë Go fajlla të ndryshëm mund t’i përkasin një pakoje.
Emri i pakos shënohet të rreshtin e parë të programit, për shembull: package main.
Fajlli i pavarur ekzekutabil i përket pakos main. Çdo Go aplikacion përmban një pako të quajtu main.
Një aplikacion mund të përbëhet nga pako të ndryshme, por edhe në rastet kur përdoret pakoja main, nuk është e domosdoshme që i tërë kodi të vendoset brenda një fajlli të madh. Mund të krijohen disa fajlla të vegjël, ku secili duhet ta ketë package main në rreshtin e parë.
Variablat
Deklarimi dhe inicializimi i variablave
Variabli deklarohet me var, pas të cilit shënohet emri i variablit e pas emrit shënohet tipi i variablit.
var x int
Në këtë rast është deklaruar variabli x i tipit integer, pra numër i plotë.
Shohim se ka dallim prej gjuhëve të tjera siç është për shembull Java, ku njëherë ceket tipi e pastaj emri i variablit.
Variabli nuk mund të deklarohet me var pa e cekur tipin.
Variabli nuk është inicializuar, pra nuk i është dhënë një vlerë fillestare. Megjithatë, në Go, çdo tip i të dhënave kanë vlera iniciale standarde edhe kur nuk janë të inicializuara në mënyrë eksplicite, që për integer është 0.
https://play.golang.org/p/EoS6ka-viIh
Rezultati:
Deklarimi dhe inicializimi i variablit mund të bëhet në një rresht:
var x int = 5
Në këtë rast, është deklaruar variabli x, i tipit integer, me vlerë 5.
Variabli mund të deklarohet në një rresht, e të inicializohet në një rresht tjetër më poshtë përgjatë rrjedhës së ekzekutimit të programit:
Tipe int ka disa: int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64. Nuk mund të kryhen operacione ndërmjet tipeve të ndryshme, për shembull të kryhet operacioni i mbledhjes ndërmjet numrit të tipit int8 dhe një numri tjetër të tipit int16.
https://play.golang.org/p/1SqAca5tPkC
Rezultati:
Për ta mundësuar operacionin e mbledhjes në shembullin e mësipërm, duhet ta konvertojmë vlerën int8 në int, ashtu që të dy numrat t’i përkasin tipit të njëjtë. Kjo bëhet me funksionin int().
https://play.golang.org/p/TzThfTDL0YN
Rezultati:
Pra, Go jo vetëm që nuk lejon operacione ndërmjet tipeve të ndryshme (float dhe int), por nuk lejon as ndërmjet nëntipeve të tipit të njëjtë (int8, int16,…).
Ekziston edhe një formë e shkurtër e deklarimit dhe inicializimit të një variabli:
https://play.golang.org/p/QzuRvnaXmfx
Rezultati:
Go do ta determinojë tipin e variablit në bazë të tipit të vlerës (type inference). Për shkak se 5 është tip int, edhe variabli x do të jetë i po atij tipi.
Kjo formë e deklarimit dhe inicializimit është e lejuar vetëm brenda funksioneve.
Me këtë sintaksë, Go do ta deklarojë variablin gjithmonë si int, nëse numri është i plotë dhe vlera e numrit është brenda intervalit: -2.147.483.648 deri 2.147.483.647, në platformat 32 bitëshe. Në platformat 64 bitëshe vlera duhet të jetë ndërmjet -9.223.372.036.854.775.808 dhe 9.223.372.036.854.775.807.
https://play.golang.org/p/_UU6nZazVIF
Rezultati:
Me fmt.Printf("%T", x)
kërkojmë të na tregohet tipi i vlerës së variablit x.
Nëse vlera e numrit është jashtë intervalit të cekur, në varësi të platformës (32 bit apo 64 bit), Go do ta shfaqë gabimin
constant 2147483648 overflows int
gjatë kompajlimit. Në vend të vlerës 2147483648 do të jetë cilado vlerë e shënuar si vlerë e variablit e që është jashtë intervalit të lejuar.
Pas deklarimit, variabli mund të marrë çfarëdo vlere të tipit përkatës dhe intervalit të lejuar të vlerave.
https://play.golang.org/p/MfgOksB1dHW
Rezultati:
Në Go është i lejuar deklarimi i disa variablave në rreshtin e njëjtë. Nëse është bërë inicializimi në të njëjtin rresht, mund të mos e shënojmë tipin sepse Go ia jep variablës tipin sipas vlerës (type inference).
https://play.golang.org/p/hc0WAre2L-E
Rezultati
Variablat mund të deklarohen dhe inicializohen edhe në grupe, të paraprira me var, ndërsa brenda kllapave vendosen rreshtat me deklarime/inicializime.
https://play.golang.org/p/1eWPvTFQizm
Rezultati:
Rideklarimi i variablave
Variabli nuk mund të deklarohet më shumë se njëherë brenda një blloku.
https://play.golang.org/p/E2jTAsCTBX-
Gjatë kompajlimit të këtij programi, lajmërohet gabimi:
no new variables on left side of :=
me çka na bën me dije se tashmë ekziston një variabël x i deklaruar dhe se nuk mund ta deklarojmë për së dyti.
Rideklarimi nuk mund të bëhet as me var.
https://play.golang.org/p/hZVXUR7Cw4d
Gjatë kompajlimit, lajmërohet gabimi:
që na bën me dije se variabli x nuk mund të rideklarohet në bllokun e njëjtë.
Vizibiliteti
Nëse e zhvendosim deklarimin e parë jashtë bllokut të funksionit, atëherë nuk do të lajmërohet kurrfarë gabimi, sepse në këtë rast kemi 2 variabla x me vizibilitet (visibility, scope) të ndryshëm, i pari në zonën globale prej nga e “shohin” të gjitha funksionet, ndërsa i dyti vetëm brenda funsionit main().
https://play.golang.org/p/b9lbFfLQZxg
Rezultati:
Nëse e fshijmë deklarimin e dytë (var x int = 9), atëherë funksioni main() do ta “shohë” vlerën e x që është deklaruar në rreshtin 5.
https://play.golang.org/p/TXzGGOHQIfr
Rezultati:
Variablat e deklaruara / rideklaruara brenda një blloku, nuk mund të “shihen” në blloqet që janë të nivelit më të lartë.
https://play.golang.org/p/xzKC7ACUONj
Rezultati:
Në rreshtin 5 është bërë deklarimi i variablit x në nivel “global”, respektivisht brenda package main.
Nga rreshti 7 deri në rreshtin 15 është blloku i funksionit main(), brenda të cilit është rideklaruar variabli x.
Nga rreshti 10 deri në rreshtin 13 është një bllok në vete, brenda të cilit është rideklaruar variabli x. Ky bllok në rastin konkret nuk bën asgjë të veçantë, përveç që e ndërron vizibilitetin e variablave brenda saj.
Kur kemi blloqe të strukturave, siç është për shembull struktura for, variablat e deklaruara brenda atyre blloqeve, nuk do të shihen jashtë bllokut. Gjithçka që deklarohet brenda bllokut, ngelet brenda bllokut dhe vlerat e variablave të tillë nuk mund të lexohen pas mbylljes së bllokut.
https://play.golang.org/p/_rjZMM8lJmx
Rezultati:
Për rreshtin 11, variabli x asnjëherë nuk është deklaruar, sepse deklarimi ka ndodhur brenda bllokut të strukturës for.
E njëjta ndodh edhe me vetë inkrementin e strukturës for; variabli i nuk do të jetë i aksesueshëm jashtë bllokut.
https://play.golang.org/p/f8ngsHoWbpl
Rezultati:
Ky gabim nuk do të lajmërohet nëse variablin i do ta kishim tashmë të deklaruar para bllokut të strukturës for.
https://play.golang.org/p/QSLdTzlW2h8
Rezultati:
Pra, Println në rreshtin 11 na e tregon vlerën e variablit i të deklaruar në rreshtin 6, ndërsa Println në rreshtin 8 tregon vlerat e variablit i që është iterator i ciklit të hapur në rreshtin 7.
Thënë ndryshe, variabli i në rreshtin 8 nuk është i njëjti variabël me atë në rreshtin 11, edhe pse kanë emër të njëjtë!
Nëse e ndryshojmë rreshtin 7 dhe në vend të:
for i := 1; i < 3; i++ {
shkruajmë:
for i = 1; i < 3; i++ {
atëherë rezultati do të ndryshojë, sepse variabli i i ciklit do të jetë tashmë i inicializuar dhe i padeklaruar për së dyti. Prandaj, Println(i) i rreshtit 11 do ta tregojë vlerën 3 (vlera e fundit e iteratorit), dhe jo 8.
https://play.golang.org/p/rgo1hvJDkBb
Rezultati:
Deklarimi i përsëritur brenda ciklit nuk konsiderohet rideklarim.
https://play.golang.org/p/YfjQ_0hrnWX
Rezultati:
Konvencionet mbi emërtimin e variablave
Konvertimet ndërmjet tipeve
Konstantat
Konstantat në Go emërtohen njëjtë sikurse edhe variablat, pra nuk bëhet si p.sh. në PHP ku për emërtimet e konstantave përdoret vetëm shkronjat kapitale dhe nënvizimi (sikurse që është FILTER_SANITIZE_STRING).
Duhet marrë parasysh se të gjitha variablat dhe konstantat që fillojnë me shkronjë kapitale, në Go e kanë kuptimin se ato variabla/konstanta do të eksportohen, ndërsa nëse janë me të vogla - do të jenë variabla/konstanta lokale në raport me pakon.
Kontantat deklarohen me fjalën const.
https://play.golang.org/p/VK3ar1gF60i
Rezultati:
Programi vijues nuk do të kompajlohet, për shkak se në rreshtin 11 përpiqemi t’ia ndryshojmë vlerën konstantës, me ç’rast lajmërohet gabimi cannot assign to numriPi
.
https://play.golang.org/p/oUNUv_8-Sbr
Rezultati:
Karakteristikë e konstantave është se caktimi i vlerave të tyre duhet të jetë i verifikueshëm qysh në fazën e kompajlimit. Kjo do të thotë se konstantës nuk mundemi t’i japim vlerë që kthehet nga një funksion gjatë ekzekutimit të programit (runtime).
https://play.golang.org/p/tvBSOxAdml4
Rezultati:
Edhe pse e dijmë që vlera do të ishte 3, konstanta n nuk e merr atë vlerë dhe lajmërohet gabimi const initializer math.Sqrt(9) is not a constant
, sepse ajo vlerë është rezultat kthyes i një funksioni gjatë ekzekutimit të programit dhe kompajleri nuk mund ta “dijë” paraprakisht atë vlerë.
Po ashtu, nuk lejohen as shprehjet, rezultati i të cilave mund të dihet vetëm gjatë kohës së ekzekutimit, siç është rasti me krahasimin e vlerave të dy variablave si në shembullin vijues:
https://play.golang.org/p/WdCrkF70PIB
Rezultati:
Konstantave mund t’iu caktohen vetëm vlera primitive:
- int
- float
- boolean
- string
Nuk mund të përdoren tipet më komplekse siç janë:
- vargjet
- segmentet
- struktet
- mapat
sepse këto për nga natyra janë variabile.
E përbashkëta e variablave dhe konstantave është se me të dyja mund të zbatohet “shadowing”, që do të thotë ridefinimi i një vlere të re të variablit/konstantës brenda një blloku që është brenda një blloku tjetër ku tashmë është deklaruar po ai variabël apo konstantë. Blloku që është me hiearki më të ultë mund të bëjë “shadow” (t’i zëvendësojë vlerat) një variable/konstante tashmë të definuar në bllokun me hierarki më të lartë.
https://play.golang.org/p/bv_obpquhTf
Rezultati:
E shohim që deklarimi i konstantës në rreshtin 10, “e mbulon” vlerën e konstantës së njëjtë të deklaruar në rreshtin 7. Kjo ndodh për shkak se blloku ku gjendet const n=7
me hierarki është më “sipër” prej bllokut ku gjendet const n = 5
. Me fjalë të tjera, blloku i brendshëm ka prioritet më të lartë.
Megjithëse i mundur, ndryshimi i vlerës së konstantës në bllokun e brendshëm mund të jetë burim potencial i ndonjë “bug” , nëse rastësisht në ato dy blloqe kemi deklaruar dy konstanta me emër të njëjtë por me dedikime të ndryshme.
Për më tepër, “shadowing”, përveç vlerës lejon edhe ndryshimin e tipit të konstantës! Kjo potencialisht mund të shkaktojë probleme gjatë kompajlimit ose ekzekutimit të programit.
https://play.golang.org/p/W_HO2UUq-TF
Rezultati:
Vërejmë se konstanta n, në rreshtin 7 është integer, ndërsa në rreshtin 10 është string.
Lirisht mund të kryejmë operacione ndërmjet variablave dhe konstantave nëse janë të tipit të njëjtë.
https://play.golang.org/p/2e8cbW5l-UZ
Rezultati:
Në Go, nuk lejohen operacione ndërmjet dy tipeve të ndryshme, as ndërmjet dy nëntipeve, si për shembull operacioni i mbledhjes ndërmjet int8 dhe int16.
https://play.golang.org/p/hx5eKdK0orw
Rezultati:
Gabim i ngjashëm paraqitet edhe kur lejojmë që Go ta konkludojë vetë tipin e një variabli, si në rreshtin 8 të shembullit vijues:
Rezultati:
Dy shembujt e fundit i dhamë me variabla, për të potencuar dallimin me rastin kur përdorim konkludimin e tipit (type inference) të konstantë, me ç’rast Go nuk do të “ankohet” për faktin që janë dy nëntipe të ndryshme dhe vetë do ta bëjë konvertimin e nëntipeve asisoj që të përputhen.
https://play.golang.org/p/7cr_-7Exu7d
Rezultati:
Pra, Go e sheh konstantën a si int, ndërsa variablin b si int8. Mirëpo, pas mbledhjes së vlerave të tyre, rezultati (19) është i tipit int8.
Pra, ndërmjet int dhe int8, për rezultat ka zgjedhur tipin int8, i cili ka diapazon më të ngushtë të vlerave. Kjo është në rregull për aq kohë sa vlera e rezultatit gjatë ekzekutimit të programit nuk e tejkalon vlerën 127, sa është vlera maksimale e tipit int8.
Nëse vlera e rezultatit e tejkalon vlerën maksimale të nëntipit të caktuar, ndodh overflow pa kurrfarë lajmërimi mbi tejkalimin e vlerës maksimale, që shpie drejt rezultateve të gabuara.
https://play.golang.org/p/tAkX2-YhJln
Rezultati:
Shohim se 120 + 12, në vend të 132 është -124. Kjo për shkak se është bërë një “rotacion” nëpër vlerat e int8 (-128 deri 127).
+0 | +1 | +2 | +3 | +4 | +5 | +6 | +7 | +8 | +9 | +10 | +11 | +12 |
---|---|---|---|---|---|---|---|---|---|---|---|---|
120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | -128 | -127 | -126 | -125 | -124 |
Enumerated constants
https://play.golang.org/p/Kl5LHx9Uo0o
Rezultati:
Rezultat të njëjtë na jep edhe ky kod:
https://play.golang.org/p/aKN4PxzypFD
Kjo për shkak se kompajleri nënkupton se edhe konstantat në vijim marrin vlerën iota.
Vlera iota është vlerë speciale për numërim duke filluar nga 0 dhe është e tipit int. Çdo konstantë vijuese brenda bllokut, merr vlerë për 1 më të madhe se konstanta paraprake.
Vlen vetëm për blloqe konstantash të deklaruara me:
Por nuk vlen nëse konstantat janë deklaruar si vijon:
Me këtë mënyrë te deklarimit, të tre konstantat (x, y dhe z) do të kenë vlerë 0.
Çdo bllok konstantash me vlerë iota, fillon prej zeros:
https://play.golang.org/p/ker4s4okHwW
Rezultati:
Listë të tillë konstantash të paradefinuara me vlerë inkrementale mund të përdorim kur dëshirojmë në program të bëjmë krahasime.
https://play.golang.org/p/oYSJVpMUw1l
Rezultati:
Nëse fare s’na interson vlera 0, ndërsa këto konstanta gjithmonë fillojnë nga 0, atëherë vlerën e parë e vendosim në _
, që është* write only variable*, pra variabël në të cilin mund të vendosim vlerë, por asaj vlere nuk mund t’i referohemi më pas, pra praktikisht ajo vlerë do të humbet.
https://play.golang.org/p/dsBbDsodtap
Rezultati:
Nëse numërimin dëshirojmë ta fillojmë nga një numër tjetër, atëherë konstantës së parë ia rrisim apo zvogëlojmë vlerën, në varësi prej vlerës së dëshiruar.
https://play.golang.org/p/PZ51jnTUzp7
Rezultati:
Vërejmë se kontanta a fillon nga -3, ndërsa konstanta x nga 5.
Përveç mbledhjes dhe zbritjes, mund të përdorim të gjithë operatorët që janë në dispozicion për vlerat primitive.
Tipet e të dhënave
Janë 4 kategori të veçanta të tipeve të të dhënave në Go:
- Tipet bazike (Basic Types)
- Tipet agregate (Aggregate Types) - vargjejt (arrays) dhe strukturat (structs)
- Tipet referenciale (Reference Types) - Treguesit (pointers) dhe slices
- Tipet interface (Interface Types) - Interfejsat standard
- Tipet bazike të integruara në Go
Go përdor tipet bazike të integruara si vijon:
- Një tip logjik (boolean): bool.
- 11 tipe numerike: int8, uint8, int16, uint16, int32, uint32, int64, uint64, int, uint, dhe uintptr.
- Dy tipe numerike me presje dhjetore: float32 dhe float64.
- Dy tipe numerike komplekse: complex64 dhe complex128.
- Një tip tekstual: string.
Këto tipe mund të përdoren drejtpërsëdrejti në kod, pa pasur nevojë të importohet ndonjë pako.
Tipet numerike përfshijnë numrat e plotë, numrat me presje dhjetore dhe numrat kompleks.
Numrat
Numrat ndahen në dy lloje kryesore:
- Numra të plotë (integers), dhe
- Numra me presje dhjetore (floating-point numbers).
Numrat e plotë
Numrat e plotë janë numra pa komponentën decimale. Të gjithë numrat e plotë kanë madhësi të caktuar kur ruhen apo procesohen në kompjuter.
Për shembull, një bajt (byte) përbëhet nga 8 bita, ku secili bit mund ta ketë vlerën 0 ose 1.
Kështu, në një bajt (8 bit) mund të ruhen 256 (28) vlera të ndryshme pozitive, prej 0 deri 255, respektivisht prej 00000000 deri 11111111 nëse e shprehim numrin në formë binare.
Nëse duhet ta ruajmë një numër pozitiv më të madh se 255, na nevojitet edhe një bajt tjetër, pra gjithsej 2 bajtë (16 bit). Në këtë rast, vlerat do të shkojnë nga 0000000000000000 deri në 1111111111111111, që në numra me bazë 10 do të jetë nga 0 deri në 65.535, pra gjithsej 65.536 vlera (216).
Me katër bajtë (32 bit), diapazoni i vlerave pozitive është 0 deri në 4.294.967.295, gjithsej 4.294.967.296 vlera (232), e kështu me radhë, për çdo bajt të shtuar diapazoni i vlerave zgjerohet 256 herë.
Kur duhet të ruhen numrat e plotë negativë, biti i parë i bajtit të parë (most significant bit) shfrytëzohet si indikator: vlera 0 tregon se është vlerë pozitive, ndërsa vlera 1 se është vlerë negative, prandaj diapazoni i vlerave përgjysmohet, ashtu siç është paraqitur në tabelë.
Kur ruajmë vetëm vlera pozitive, kemi të bëjmë me unsigned integers, ndërsa për pozitive dhe negative – signed integers.
Tipet e numrave të plotë, emrat e të cilëve fillojnë me u janë tipe pa parashenjë (unsigned types), të cilat përmbajnë vetëm vlera jonegative.
Numri pas emrit tregon numrin e bitave binarë (8, 16, 32, 64) që nevojiten për ruajtjen e vlerës në memorje. Për shembull uint8 zbërthehet si: numër i plotë jonegativ që okupon 8 bit në memorje, me çka mund të reprezentohen vlerat prej 0 deri 255 (28-1). Ndërsa tipi int8: numër i plotë që okupon 8 bit n memorje dhe merr vlera prej -128 (-27) deri në 127 (27-1).
Vlera jonegative (unsigned)
- Bit: 8, Bajt:1, Prej: 0, Deri: 255 (28-1)
- Bit: 16, Bajt:2, Prej: 0, Deri: 65.535 (216-1)
- Bit: 32, Bajt:4, Prej: 0, Deri: 4.294.967.295 (232-1)
- Bit: 64, Bajt:8, Prej: 0, Deri: 18.446.744.073.709.551.615 (264-1)
Vlera negative dhe jonegative (signed)
- Bit: 8, Bajt:1, Prej: -128 (-27), Deri: 127 (27-1)
- Bit: 16, Bajt:2, Prej: -32.768 (-215), Deri: 32,767 (215-1)
- Bit: 32, Bajt:4, Prej: -2.147.483.648 (-231), Deri: 2.147.483.647 (231-1)
- Bit: 64, Bajt:8, Prej: -9.223.372.036.854.775.808 (-263), Deri: 9,223,372,036,854,775,807 (263-1)
Go përdor tipet vijuese të numrave të plotë:
8 bitë unsigned
- uint8 (byte)
16 bitë unsigned
- uint16
32 bitë unsigned
- uint32
- uint
- uintptr
64 bitë unsigned
burim vështirë i detektueshëm
8 bitë signed
- int8
16 bitë signed
- int16
32 bitë signed
- int32 (rune)
- int
64 bitë signed
onjë gabim eventu
Tipi byte është alias i uint8, d.m.th. janë të njëjtë.
Tipi rune është alias i int32.
Madhësitë e tipeve uint, int dhe uintptr varen nga platforma, respektivisht arkitektura e procesorit; në platformat 32 bitëshe kanë madhësi 32 bitëshe, ndërsa në platformat 64 bitëshe - madhësi 64 bitëshe.
Madhësia e vlerës uintptr duhet të jetë mjaft e madhe që të ruajë bitat e painterpretuar të një adrese memorike.
Duhet të kemi parasysh që vlerat që ia japim një tipi t’i përgjigjen diapazonit të vlerave të lejuara për atë tip. Për shembull, tipi int8 mund të përmbajë vlera prej -128 deri 127. Nëse në program i japim një vlerë më të madhe se 127 apo më të vogël se -128, kompajleri do të lajmërojë gabim dhe programi nuk do të kompajlohet fare.
Mirëpo, nëse vlera e një tipi caktohet gjatë ekzekutimit të programit, me ç’rast kompajleri nuk e di paraprakisht vlerën, do të ndodhin situata konfuze, me ç’rast programi nuk lajmëron gabim por ndodh overflow. Për shembull, meqë vlera maksimale e int8 është 127 dhe nëse gjatë kohës së ekzekutimit merr vlerën 128, vlera rezultuese do të jetë -127! Kjo për shkak se pas arritjes së vlerës maksimale, programi fillon prej vlerës më të vogël për aq sa sa vlera e re dallon nga vlera maksimale.
Me fjalë të tjera, nëse int8 merr vlerën 150, në fakt ai do ta ketë vlerën -105 sepse 150-127=23 dhe -128+23 = -105. Ky mund të jetë burim vështirë i detektueshëm i gabimeve në rezultatet e programit sepse sistemi i kohës së ekzekutimit (runtime system) i Go nuk do të na lajmërojë për ndonjë gabim eventual.
Duhet të kemi parasysh edhe faktin që ndaj numrave të plotë të tipeve të ndryshme nuk mund të kryejmë operacione aritmetikore. Për shembull, nuk mund ta mbledhim një numër të tipit uint8 me një numër të tipit int16. Për ta realizuar operacionin e mbledhjes, të dy tipet duhet më parë t’i konvertojmë në një tip të njëjtë e më pas ta kryejmë veprimin aritmetikor. Në shembullin vijues, konvertimi bëhet në tipin int nëpërmes funksionit int(), i cili ka madhësinë 32 apo 64 bitë, në varësi prej arkitekturës, por gjithsesi është më i madh se 8 bitë (tipi uint8) dhe 16 bitë (int16).
Numrat me presje dhjetore
Numrat me presje dhjetore (floating-point numbers) janë numra që përmbajnë komponentën decimale. Paraqitja e tyre në kompjuter është më e ndërlikuar se e numrave të plotë. Kjo bën që saktësia e këtyre numrave të jetë e kufizuar dhe rrjedhimisht edhe e llogaritjeve që bëhen me këto numra.
Në memorje, të gjitha vlerat e numrave me presje dhjetore ruhen në formatin IEEE-754.
Numrat me presje dhjetore kanë madhësi (numra bitash) të caktuar, 32 bitë (single precision) apo 64 bitë (double precision), sikurse edhe numrat e plotë.
Numrat më të mëdhenj kanë saktësi më të vogël, sepse duke qenë se kanë madhësi fikse (32/64 bitë), sa më e madhe pjesa e plotë, aq më pak shifra ngelen në dispozicion djathtas prej presjes dhjetore, me çka zvogëlohet saktësia e pjesës decimale.
Mund të përmbajnë edhe vlera jonumerike siç janë:
- NaN (not a number)
- Infinit pozitiv (+∞)
- Infininit negativ (−∞)
Shembull me infinit:
https://play.golang.org/p/ZrzSGo-MnzA
Rezultati:
Go ka dy tipe për paraqitjen e numrave kompleksë:
- complex64, dhe
- complex128.
Pjesa reale dhe imagjinare e një vlere complex64 janë të dyja vlera float32, ndërsa pjesët reale dhe imagjinare të një vlere complex128 janë të dyja vlera float64.
Në Go përdoren operatorët standard vijues:
- Mbledhja +
- Zbritja -
- Shumëzimi *
- Pjestimi /
- Modulusi %
Numrat kompleks
Numrat kompleks përbëhen nga dy pjesë:
- pjesa reale, dhe
- pjesa imagjinare.
Go mundëson kryerjen e drejtpërsëdrejtë të operacioneve aritmetikore ndaj numrave kompleks. Numri kompleks është 128 bitësh, float64 për pjesën reale, float64 për pjesën imagjinare, apo 64 bitësh - float32 për pjesën reale, float32 për pjesën imagjinare.
https://play.golang.org/p/-qHz2eUw9B8
Rezultati:
Me përdorimin e pakos “math/cmplx”, mund të kryejmë edhe veprime të tjera matematikore.
- Abs(x complex128) float64
- Acos(x complex128) complex128
- Acosh(x complex128) complex128
- Asin(x complex128) complex128
- Asinh(x complex128) complex128
- Atan(x complex128) complex128
- Atanh(x complex128) complex128
- Conj(x complex128) complex128
- Cos(x complex128) complex128
- Cosh(x complex128) complex128
- Cot(x complex128) complex128
- Exp(x complex128) complex128
- Inf() complex128
- IsInf(x complex128) bool
- IsNaN(x complex128) bool
- Log(x complex128) complex128
- Log10(x complex128) complex128
- NaN() complex128
- Phase(x complex128) float64
- Polar(x complex128) (r, θ float64)
- Pow(x, y complex128) complex128
- Rect(r, θ float64) complex128
- Sin(x complex128) complex128
- Sinh(x complex128) complex128
- Sqrt(x complex128) complex128
- Tan(x complex128) complex128
- Tanh(x complex128) complex128
Rrënja katrore e numri kompleks:
https://play.golang.org/p/8aXVwGwWjW1
Rezultati:
Po të përdorej funksioni Sqrt() nga pakoja math:
do të lajmërohej gabimi:
Ngritja në fuqi e numrit kompleks
E marrim rezultatin e shembullit me rrënjë katrore të numrave kompleks, dhe e ngrisim në katror, për të verifikuar a e fitojmë rezultatin e pritur (4 + 3i).
https://play.golang.org/p/pmQpPC2q8k5
Rezultati:
Shohim që e kemi fituar rezultatin përafërsisht të njëjtë, diferenca është 0.000000000000001 apo 10-14, diferencë kjo që mund të jetë e papërfillshme në varësi prej natyrës së problemit që zgjidhet.
Pasaktësia buron nga fakti e komponentet e numrit kompleks janë float64 dhe domosdo do të ndodhin përafrime të vlerave sepse këtë e imponon vetë natyra e numrave të tipit float.
Bitwise operators
Bitwise AND operator
Kryhet operacioni AND ndaj vlerave binare të dy numrave bit për bit në pozita të njëjta. Rezultati i fituar është:
- 0 & 0 = 0
- 0 & 1 = 0
- 1 & 0 = 0
- 1 & 1 = 1
https://play.golang.org/p/fE6bLqAKff5
Rezultati:
Bitwise OR operator
Kryhet operacioni OR ndaj vlerave binare të dy numrave bit për bit në pozita të njëjta. Rezultati i fituar është:
- 0 | 0 = 0
- 0 | 1 = 1
- 1 | 0 = 1
- 1 | 1 = 1
https://play.golang.org/p/zjE0213pbil
Rezultati:
### Bitwise XOR operator
Kryhet operacioni XOR ndaj vlerave binare të dy numrave bit për bit në pozita të njëjta. Rezultati i fituar është:
- 0 | 0 = 0
- 0 | 1 = 1
- 1 | 0 = 1
- 1 | 1 = 0
https://play.golang.org/p/nmpYzMQdCWi
Rezultati:
Bitwise NOT operator
Ky operator, zerot i kthen në njësha, njëshat në zero.
- ^0 = 1
- ^1 = 0
https://play.golang.org/p/hie6IHjoRDu
Rezultati:
Bitwise AND NOT operator
Biti i numrit të parë kryen operacionin AND me NOT-in e numrit të dytë.
- 0 &^ 0 = 0
- 0 &^ 1 = 0
- 1 &^ 0 = 1
- 1 &^ 1 = 0
https://play.golang.org/p/c7UyUxnCKUz
Rezultati:
Bitwise shift operators
Operatorët për zhvendosje të bitave (bitwise shift operators) bëjnë zhvendosjen e vlerës së një objekti binar. Operandi i majtë paraqet vlerën, ndërsa i djathti numrin e pozitave për zhvendosjen e bitave të vlerës.
https://play.golang.org/p/Rknmaievs2w
Rezultati:
Në rreshtin 8 kryhet operacioni 2<< 5, që do të thotë që vlera e numrit 2 (që në formë binare është 00000010), të zhvendoset për 5 pozita majtas. Pra, njësi do të zhvendoset 5 herë majtas, me ç’rast formohet numri binar 01000000, e që është vlera 64.
Në rreshtin 9 kryhet veprim i kundërt: vlera e numrit 64 (01000000) do të zhvendoset djathtas, pra njëshi do të zhvendoset për pesë pozita më djathtas, me çka fitohet numri binar 00000010, që është numri 2.
Bitwise left shift operator
https://play.golang.org/p/yIC7d1CT5NF
Rezultati:
E vërejmë njëshin binar si zhvendoset nga një pozitë majtas, prej vlerës binare 00000001 deri në vlerën binare 10000000.
Bitwise right shift operator
https://play.golang.org/p/Yl3YULGqkdj
Rezultati:
Stringjet
Një string është sekuencë karakteresh e gjatësisë së caktuar që përdoret për ruajtjen e tekstit. Go ofron suport për karakteret Unicode, me çka mundësohet përdorimi i shkronjave të alfabeteve të ndryshme si dhe shenjave të shumta speciale.
Një string vendoset ndërmjet thonjëzave apo shenjave backtick (`):
“Hello, World”
`Hello,
World`
Nëse stringu vendoset brenda thonjëzave, stringu nuk mund të përmbajë rreshta të rinj (newlines) ndërsa special escape sequences janë të lejuara.
Shenja \n
zëvendësohet me newline ndërsa \t
me tabulator.
Nëse stringu vendoset brenda backticks, stringun mund ta ndajmë në disa rreshta.
Karaktereve individuale të stringut mund t’iu qasemi sikur të ishte varg, duke përdorur indeksin, për aq kohë sa karakteret i takojnë tabelës ASCII.
https://play.golang.org/p/W8Fbgr2NLKK
Rezultati:
Si rezultat i fmt.Println(a[0])
fitohet kodi i karakterit të parë (indeksi 0) e që është 71
. Me anë të funksionit string()
mund ta konvertojmë në karakter (G).
Nëse një karakter e vendosim brenda apostrofave, vlerë e variablit do të jetë kodi i atij karakteri. Nëse një karakter e vendosim brenda thonjëzave, atëherë vlerë do të jetë karakteri.
https://play.golang.org/p/WL392-lPQ7w
Rezultati:
Pra, kur kemi deklaruar a := 'A'
, tipi i variablit a
do të jetë int32
, gjegjësisht rune
, që do të jetë një numër, pra 65
. Për ta printuar me fmt.Printf
, përdoret formatimi “%c”, ose e konvertojmë variablin me funksionin string()
:
Mund t’iu qasemi edhe një sekuence të karakterëve duke e cekur numrin e karakterëve të dëshiruar.
https://play.golang.org/p/9I_gQlAwJw1
Rezultati:
Me a[0:3] kemi kërkuar 3 karakteret e njëpasnjëshëm duke filluar nga karakteri i parë [0], pastaj [1], pastaj [2], duke mos e përfshirë indeksin e fundit [3]. Në Go, kur ceket një diapazon i indekseve, nuk merret përfshihet indeksi i fundit, por përfshirja bëhet prej indeksit të cekur fillestar deri te ai i parafundit.
Vërejmë se në këtë rast fitojmë stringun “Gol” dhe jo kodet e karakterëve, pra nuk kemi nevojë të bëjmë konvertim eksplicit me funksionin string()
.
Karaktereve individualë mund t’iu qasemi edhe me strukturën for-range:
https://play.golang.org/p/shIDT8i9w4W
Rezultati:
Kur kemi të bëjmë me Unicod karaktere, ka dallim esencial ndërmjet përdorimit të strukturës for
dhe asaj for-range
për ekstaktimin e karaktereve. Struktura for
e zbërthen stringun bajt për bajt, ndërsa struktura for-range
do ta zbërthejë sipas Unicode karakterve.
Një Unicode karakter zë ndërmjet 1 dhe 4 bajtëve dhe përfshin pothuajse të gjitha alfabetet e botës. Karakteret nga tabela ASCII konsumojnë nga 1 bajt, kështu që për shembull për alfabetin e gjuhës angleze, si struktura for
si ajo for-range
do të japin rezultat të njëjtë. Mirëpo, në rastin e gjuhës kineze në shembullin vijues, 1 karakter zë 3 bajtë dhe struktura for
është e papërshtatshme për t’u përdorur për një string në gjuhën kineze. Në shembullin e mëposhtëm, zbërthimi i stringut në gjuhën kineze bëhet njëherë me for
, pastaj me for-range
.
https://play.golang.org/p/wRc_nHvLqKr
Rezultati:
Po ashtu, funksioni len()
për një string që përmban Unicode karaktere do të raportojë gjatësi të gabuar, sepse len()
tregon numrin e bajtëve, jo numrin e karaktereve. Një karakter ruhet si rune
, që është int32
. Pra, kur flasim për rune
, flasim për një hapësirë 32 bitëshe, respektivisht 4 bajtëshe, hapësire kjo e mjaftueshme për reprezentimin e të gjitha karaktereve të Unicode. Për ta llogaritur saktë numrin e karaktereve, do ta përdorim funksionin RuneCountInString()
nga pakoja utf8
.
https://play.golang.org/p/kJNEwwKKMHU
Rezultati:
Karakteristikë e stringjeve në Go është se janë immutable, gjegjësisht nuk mund t’iu ndryshohet përmbajtja, pra nuk mund ta ndryshojmë drejtpërsëdrejti ndonjë karakter apo sekuencë të karaktereve të stringut .
https://play.golang.org/p/j1bLHKiHiQt
Rezultati:
Pra, kur kemi tentuar ta ndryshojmë karakterin e pestë (indeksi 4), lajmërohet gabimi cannot assign to a[4]
.
Bashkangjitja e stringjeve (string concationation) kryhet me operatorin +.
https://play.golang.org/p/ZqRiAmTl7Mc
Rezultati:
Në shembullin vijues është një program i thjeshtë i cili do ta shifruar një tekst duke e përdorur “Caesar cipher”, tek i cili bëhet zhvendosja e shkronjave për disa pozita ta zëmë djathtas kur shifrojmë, pastaj për po aq pozita majtas kur deshifrojmë.
Do ta përdorim kodin e karakterit që është numër i plotë për ta kryer operacionit të mbledhjes p.sh. me 3 (për zhvendosje 3 pozita djathtas), ndërsa gjatë dekodimit do ta kryejmë operacionin e zbritjes për zhvendosje majtas.
Programi është fare i thjeshtë dhe në këtë formë funksionin vetëm me ASCII karaktere.
https://play.golang.org/p/tMwdc-NsN2R
Rezultati:
Funksionet
len()
Tregon numrin e karaktereve që i përmban një string.
https://play.golang.org/p/oUCmOQW7M5L
Rezultati:
string()
E konverton kodin e karakterit në karakter.
https://play.golang.org/p/G-gGIV08wU1
Rezultati:
Degëzimi (if)
if else
https://play.golang.org/p/yS-PKpRjRo1
Rezultati:
Shembulli i mësipërm mund të rishkruhet kështu:
https://play.golang.org/p/ehMSSCiex-l
Rezultati:
Ndryshimi ndërmjet dy shembujve të fundit qëndron në faktin se në shembullin e dytë variablat x
dhe y
janë variabla lokale brenda skopit të strukturës if
, pra nuk do të jenë të qasshme kur dilet nga kjo strukturë. Kështu, nëse e shtojmë në fund një rresht që i printon vlerat e x dhe y, do të shohim se lajmërohet gabimi ku thuhet se x dhe y nuk janë të definuar.
Kodi i modifikuar, që e përmban edhe rreshtin fmt.Println(x, y)
:
https://play.golang.org/p/3xrivR6WJ0a
Rezultati:
Degëzimi (switch)
https://play.golang.org/p/Je35FFMxj6_-
Rezultati:
Blloku i kodit pas default ekzekutohet nëse nuk është plotësuar asnjë prej kushteve me case. Urdhëri default nuk është i domosdoshëm nëse logjika e kodit nuk e kërkon. Urdhëro default mund të vendoset kudo brenda switch, nuk është e domosdoshme të jetë në fund.
Tipi i vlerës që evaluuar në switch dhe tipi i vlerës në case duhet të përputhen. Nëse nuk përputhen, do të lajmërohet gabim gjatë kompajlimit. Vlerat në case
nëse janë numerike, lejohet që në switch
ta kemi variablin të tipit qoftë float
, qoftë int
.
https://play.golang.org/p/lFEFtH7CsbQ
Rezultati:
Kjo “tolerancë” nga ana e Go ndodh për shkak se krahasimin e vlerës së x
e bëjmë ndaj vlerave numerike të cilat konsiderohen konstanta. Nëse në vend të atyre numrave vendosim variabla me të deklaruara me tip, atëherë do të kemi problem sepse Go do të kërkojë që variabli x
dhe variabli me të cilin krahasohet të jenë të tipit identik, përndryshe do të raportohet gabimi për mospërputhjen e tipeve.
https://play.golang.org/p/NtmdE9uh652
Rezultati:
Secili case
është scope
në vete, kështu që variablat e deklaruara dhe inicializuara brenda atij blloku, nuk do të jenë të qasshëm jashtë strukturës switch
.
Scope
i case
nuk ndryshon edhe në rastet kur kemi fallthrough
nga një case
në case
-in vijues.
https://play.golang.org/p/OgGBkdIS3rX
Rezultati:
Pra, secili case
është scope
në vete dhe variablat e definuara në një case
, nuk janë të qasshëm në case
tjetër edhe kur bëjmë fallthrough
.
Nëse nuk ka fare shprehje pas switch, vlera që evaluohet është boolean, prandaj dhe në case duhet të kemi shprehje boolean, siç janë operatorët e krahasimit.
https://play.golang.org/p/rtdtszn6ZrR
Rezultati:
Nëse në më tepër se një case plotësohet kushti, ekzekutohet vetëm kodi pas case të parë.
https://play.golang.org/p/jfczKTCcaSU
Rezultati:
Switch në Go nuk ka nevojë për break.
Nëse duhet që nga një case të kalohet te tjetri case pa e evaluuar kushtin, përdoret urdhëri fallthrough.
https://play.golang.org/p/elGAO1aAXjC
Rezultati:
Kllapat e mëdha (curly braces) nuk janë të domosdoshëm për definimin e bllokut të kodit pas case.
https://play.golang.org/p/qj3X-PyUAME
Rezultati:
Shprehjet në case mund të jenë vlera apo shprehje.
https://play.golang.org/p/Z147M9xT7Co
Rezultati:
Mund të përdorim çfarëdo numri të case.
Brenda case mund të vendosen disa shprehje të përputhjes, të ndarë me presje.
https://play.golang.org/p/WW4022ZHACD
Rezultati:
Switch initializer
https://play.golang.org/p/ZvvHIkLzc6p
Rezultati:
Shembulli në vijim ka të bëjë me ditët e javës. Lexohet dita e javës nga sistemi nëpërmes pakos time
dhe pastaj bëhet degëzimi në varësi prej vlerës së kthyer. Për vlerat ` time.Saturday dhe
time.Sunday` do të raportohet “Fundjavë”, ndërsa për vlerat tjera do të raportohet “Ditë pune”.
https://play.golang.org/p/U3Yu8KGbY6-
Rezultati:
Switch mund të përdoret edhe për determinimin e tipeve të variablave, në rastet kur funksioni pranon si parametër një interfejs të zbrazët. Interfejsat e zbrazët përdoren për pranimin e vlerave tipeve të ndryshme. Pas pranimit të vlerës, analizojmë me switch v := i.(type)
cilit tip konkret i përket variabli i bartur në funksion.
https://play.golang.org/p/flsKeobzBA2
Rezultati:
Ciklet (for)
for
https://play.golang.org/p/iWDx5k3TGZ8
break
https://play.golang.org/p/hkZAYJ_rVtN
continue
https://play.golang.org/p/5yyYPx2XEYs
Tipet kompozite: Vargjet
Vargjet (arrays) dhe struktet (structs) janë tipe agregate, vlerat e tyre janë lista të vlerave të tjera në memorje.
Të gjitha elementet e një vargu janë të një tipi.
Vargjet
Vargu është një sekuencë e gjatësisë fikse të vlerave të tipit të caktuar. Për shkak të gjatësisë fikse, vargjet shumë rrallë përdoren drejtpërsëdrejti. Në vend të përdorimit direkt të vargjeve, në Go rekomandohet përdorimi i segmenteve (slices), të cilave mund t’u shtohen apo largohen elementet, sipas nevojës.
Anëtarëve individualë të vargut mund t’i qasemi duke përdorur indeksin, dhe indeksi i elementit të parë është zeroja. Numrin e elementeve të një vargu e lexojmë me funksionin len()
.
https://play.golang.org/p/r_en5mgRAHS
Me përdorimin e strukturës for
mund t’iu qasemi në mënyrë sekuencionale indekseve dhe anëtarëve të një vargu.
https://play.golang.org/p/WQu60tVtTpq
Elementet e një vargu të ri kanë “zero vlerë” , që në rastin e numrave është zero, për stringjet - string i zbrazët (“”). Në shembullin vijues, anëtarit të tretë (me indeksin 2), nuk i është dhënë vlerë, prandaj x[2] ka vlerë 0.
https://play.golang.org/p/yJ3X4GPspOD
Nëse i qasemi një indeksi inekzistent, lajmërohet gabimi qysh në fazën e kompajlimit.
https://play.golang.org/p/A-rjdARpHMw
Rezultati:
Nuk është e domosdoshme që të ceket numri i elementeve, sepse duke përdorur ...
në vend të numrit, do të formohet një varg me aq vende sa vlera janë dhënë gjatë inicializimit.
https://play.golang.org/p/zGwLoriX2FI
Rezultati:
Kompajleri do ta deklarojë variablin x
si varg me 4 elemente, sepse po aq elemente kemi shënuar gjatë inicializimit.
Një varg mund të përmbajë elemente të tipit të njëjtë, por kjo nuk do të thotë se mund të përmbajë vetëm tipet primitive. Në shembullin e mëposhtëm paraqitet një varg me 3 elemente që përmban vlera të tipit struct
, i cili përmban 3 fusha të tipit string
dhe një të tipit int
.
https://play.golang.org/p/gIcygI4736H
Rezultati:
Vargu multidimensional
https://play.golang.org/p/dbDO_0YG0mo
Rezultati:
Tipet kompozite: Segmentet
Duke qenë se vargjet në Go kanë gjatësi fikse dhe vlerat mund të jenë vetëm të një tipi të njëjtë, nuk përdoren shpesh drejtpërsëdrejti. Në anën tjetër, segmentet (slices) i hasim kudo nëpër kod për shkak të fleksibilitetit të tyre.
Segmentet paraqesin një pjesë të vargut, dhe njëjtë sikurse vargu, edhe segmentet kanë indekse dhe gjatësi (numër elementesh). Për dallim nga vargjet, segmenteve mund t’ua ndryshojmë gjatësinë, respektivisht t’u shtojmë anëtarë të rinj apo ta largojmë ndonjë anëtar ekzistues.
Një segment deklarohet njëjtë sikurse një varg, me dallimin që nuk ceket numri i anëtarëve.
var x []int
Në këtë rast, është krijuar një segment me gjatësi 0.
Në shembullin vijues, fillimisht krijohet segmenti x me 0 anëtarë, e më pas me funksionin append() shtohet nga një anëtarë tri herë radhazi.
https://play.golang.org/p/DKLRlKjpW50
Rezultati:
Tipet kompozite: Mapat
Mapa është koleksion i vlerave çift indeks-vlerë. Në gjuhët tjera iu referohemi si varg asociativ (associative array), hash table apo dictionary. Vlerat në një mapë kërkohen sipas çelësit/indeksit.
Mapa e zbrazët krijohet me sintaksën:
make(map[key-type]val-type)
Për shembull, për indeks të tipit int dhe vlera të tipit string, shkruajmë:
make(map[int]string)
Si indeks mund të përdoren tipet: int, float, numër kompleks, string, pointer, ose interfejs.
Mund të krijohen edhe mapa që përmbajnë mapa të tjera, p.sh.:
map[string]map[string]string
https://play.golang.org/p/Mqk5n33s8JX
Rezultati:
Paraqitja e vlerave me një for loop:
https://play.golang.org/p/t2Q0z-4kqOe
Rezultati:
Numri i elementeve të mapës, përdoret funksioni len()
:
https://play.golang.org/p/sYZNn4OJfEf
Rezultati:
Fshirja e një anëtari të mapës. Përdoret funksioni delete()
:
https://play.golang.org/p/syj0PtA5DbH
Rezultati:
Kur kërkojmë një anëtar të mapës me indeks që nuk ekziston, vlera e kthyer është zero, kështu që mund të jemi në konfuzion: a është vlera e anëtarit zero, apo nuk u gjet anëtari me atë indeks. Në situata të këtilla, i përdorim dy variabla, ku variabla e dytë do të ketë vlerë të tipit boolean (true ose false).
Nëse është true
, anëtari me atë indeks ekziston, dhe nëse kthen false
- anëtari me atë indeks nuk ekziston.
Nëse na nevojitet vetëm verifikimi i ekzistencës së një anëtari me indeks të caktuar, atëherë si variabël të parë e përdorim shenjën _.
https://play.golang.org/p/__jke7ynjYE
Rezultati:
Deklarimi dhe inicializimi i mapës:
https://play.golang.org/p/BKwfqr7omWk
Rezultati:
Tipet kompozite: Struktet
Strukti është një tip i definuar (user-defined type) që përmban koleksion të fushave të emërtuara. Përdoret për grupimin e të dhënave brenda një njësie të vetme. Për shembull, një strukt mund të përmbajë të dhëna mbi një libër: titulli, autori, numri ISBN, numri i faqeve, etj.
Me fjalën kyçe type
e krijojmë një tip të ri të të dhënave; në vijim e shënojmë emrin për tipin e ri që në këtë rast është Libri, ndërsa në fund shënojmë struct
për të treguar se jemi duke definuar një strukt.
Mund ta rishkruajmë edhe duke i grupuar fushat që i përkasinë tipit të njëjtë:
Deklarimi dhe inicializimi i struktit
Deklarimi i një variable të tipit struct
Deklarimi i variablit bëhet sikurse edhe me tipet e tjera:
Në kodin e mësipërm kemi deklaruar një variabël lb
të jetë e tipit Libri, duke mos e inicializuar. Në këtë rast, të gjitha fushat e struktit Libri (Titulli, Autori, ISBN dhe Faqe) marrin vlerat iniciale sipas tipit:
- Fushat e tipeve numerike marrin vlerën zero (0)
- Fushat e tipit string marrin vlerën e stringut bosh (“”)
- Fushat e tipit boolean marrin vlerën
false
Një strukt mund ta deklarojmë dhe inicializojmë në mënyrën vijuese:
Duhet të kemi parasysh që renditja e vlerave të korrespondojë me renditjen e fushave të struktit.
Në këtë formëm, duhet të jenë prezente vlerat për të gjitha fushat, pra nuk mund të përdorim:
Në këtë rast, lajmërohet gabimi too few values in Libri literal
.
Cekja e fushave gjatë inicializimit të struktit
Për inicializim, mund ta përdorim fomën fusha:vlera
, me ç’rast renditja e fushave nuk luan rol.
Për ta bërë struktin më të lexueshëm, mund të shkruajmë edhe në formën vijuese:
Nëse e përdorim këtë formë, rreshti i fundit patjetër duhet të përfundojë me presje. Nëse kllapën e madhe mbyllëse }
e vendosim në fund të rreshtit të fundit, atëherë presja nuk shënohet.
Me përdorimin e emrave të fushave, mund ta bëjmë edhe inicializimin e pjesshëm të struktit, ku ato fusha që mungojnë do të marrin zero vlerat përkatëse.
Në këtë rast, fusha ISBN
merr vlerën “”, ndërsa fusha Faqe
merr vlerën 0.
Qasja vlerave të fushave të struktit
https://play.golang.org/p/lBpe2ryajLw
Rezultati:
Siç e shohim, fushave individuale të struktit i qasemi ashtu që e shënojmë variablin që e përmban struktit, pikën e pastaj emrin e fushës: lb.Autori
.
Përdorimi i pointerëve me strukt
https://play.golang.org/p/R-8D8LZZLCa
Rezultati:
Vërejmë se nuk kemi nevojë të bëjmë deferencim eksplicit me *.
Krijimi i structit me new()
https://play.golang.org/p/ien1oao99Sr
Rezultati:
Vërejmë se është krijuar pointer i struktit.
Eksportimi i strukteve dhe fushave
Struktet që fillojnë me shkronjë të madhe do të eksportohen, dmth do të jenë të qasshëm edhe nga pakot tjera.
Po ashtu edhe fushat e struktit që fillojnë me shkronjë të madhe do të eksportohen.
Atyre që emri iu fillon me shkronjë të vogël, do të jenë të qasshëm vetëm brenda pakos ku janë deklaruar.
Struktet janë tipe me vlerë, jo me referencë.
https://play.golang.org/p/8-5Ukll6Pk1
Rezultati:
Shohim se lb2
përmban kopjen e vlerave të lb
dhe jo referencë.
Krahasimi i strukteve
Dy strukte janë të barabartë nëse të gjitha fushat janë të barabarta ndërmjet vete.
https://play.golang.org/p/m9M-gGI-rLY
Rezultati:
Pointerët
https://play.golang.org/p/4kXvmZoaH72
Rezultati:
Interfejsat
Interfejsi është një set metodash. Një interfejs përshkruan sjelljen (behaviour) e një tipi.
Nëse interfejsi nuk e definon asnjë metodë, atëherë ai është interfejs i zbrazët. Të gjitha tipet e implementojnë interfejsin e zbrazët.
Në Go nuk kemi deklarim eksplicit të interfejsit me implements
, siç e kemi në gjuhët tjera. Cilido tip i të dhënave (p.sh. strukt), që është pranues (receiver) i metodave të definuara në një interfejs, në mënyrë implicite e implementon atë interfejs.
https://play.golang.org/p/OMrS3VIfpHc
Rezultati:
https://play.golang.org/p/XOdPtP4pYU4
Rezultati:
Variablat a dhe b mund t’i deklarojmë të tipit Person, me çka a bëhet variabël e tipit Student që implementon interfejsin Person, ndërsa b bëhet variabël e tipit Punetor që implementon po atë interfejs. Mirëpo, ky nuk është implementim eksplicit i interfejsit, sepse interfejsi konsiderohet i implementuar nëse një tip (në këtë rast struct) posedon metodat që janë listuar në interfejs.
Nëse tipi, në këtë rast strukti, nuk i posedon TË GJITHA metodat e cekura në interfejs dhe me po të njëjtat nënshkrime të funksioneve (function signature), pra numrin dhe tipin e argumenteve dhe të rezultateve kthyese, atëherë nuk është duke e implementuar atë interfejs.
Funksionet
- Funksionet janë njësi të ripërdorshme të kodit.
- Një funksion definohet me fjalën kyçe
func
. - Funksioni deklarohet një herë dhe mund të përdoret shumë herë.
- Funksionet në Go mund të kthejnë më tepër se një vlerë
- Funksionet në Go mund të lidhen me një tip të caktuar, me ç’rast ai tip quhet receiver (pranues)
- Parametrat barten me vlerë (pass-by-value), që do të thotë se vlerat e parametrave kopjohen.
Go kërkon kthime eksplicite (explicit returns), pra funksioni nuk do ta kthejë me return
vlerën e shprehjes së fundit në funksion. Megjithatë, nëse emri i një variabli ceket si vlerë kthyese në rreshtin e deklarimit të funksionit, funksioni do ta kthejë atë vlerë pa e cekur emrin e variablit. Në shembullin vijues, te nënshkrimi i funksionit (function signature) variabli c i tipit int është deklaruar si variabël vlerën e të cilit do ta kthejë urdhëri return.
https://play.golang.org/p/EO5SJZftjMm
Rezultati:
Closures
Recursion
Funksionet variadike
https://play.golang.org/p/W5uTt_yTsRa
Funksion/metodë me pranues
Funksioneve që janë deklaruar nga një interfejs iu referohemi si metoda dhe ato mund të implementohen nga tipet e kustomizuar.
Implementimi i një metode duket ekzaktësisht si i funksionit, me dallimin që para emrit të funksionit shënohet tipi që e implementon atë, p.sh. (r Katerkendeshi)
në shembullin vijues.
Kjo sintaksë mundëson që metoda e njëjtë t’iu përcaktohet tipeve të ndryshme.
Një tip dhe pointeri i tij e ndajnë namespace-n e njëjtë, prandaj një metodë mund të implementohet vetëm për njërin prej tyre. Në rast se metodën e përdorimin edhe për tipin edhe për pointerin, do të lajmërohet gabim gjatë kompajlimit sepse konsiderohet si rideklarim i metodës.
Metodat nuk mund të definohen për interfejsat, por vetëm për tipet konkrete. Megjithatë, interfejsët mund të përdoren në tipet kompozite, përfshirë këtu edhe parametrat e funksionit dhe vlerat kthyese.
Në Go, gjithçka bartet me vlerë (pass by value), kështu që kur thirret një funksion apo metodë, krijohet kopja e variablit në stack. Kjo nënkupton që ndryshimet që bëhen ndaj vlerë nuk reflektohen jashtë funksionit të thirrur. Edhe segmentet, mapat dhe tipet e tjerë referues barten me vlerë, por meqë struktura e brendshme e tyre përmban pointerë, ato veprojnë sikurse të ishin bartur me referencës. Nëse metoda është e definuar për një tip, nuk mund të definohet për pointerin e tij dhe anasjelltas.
https://play.golang.org/p/s85X-9RHTMF
Rezultati:
Për ta ndryshuar variablin origjinal, argumenti duhet të jetë pointer i vetë variablit. Pointeri do të kopjohet, por ai do të jetë referencë e adresës së njëjtë memorike, duke mundësuar kështu ndryshimin e vlerës.
https://play.golang.org/p/ef75lkFpp3n
Rezultati:
Kur pranuesi i metodës është tip e jo pointer, nëse vlera i ndryshohet brenda funksionit, nuk do të propagohet jashtë funksionit, pra do të trajtohet si variabël lokale.
https://play.golang.org/p/7w5u7-1WluI
Rezultati:
Nëse pranuesi i metodës është pointer, vlera që ndryshohet do të jetë e dukshme edhe jashtë funksionit.
https://play.golang.org/p/90AZHL23Yrz
Rezultati:
Built-in functions
Një numër i vogël i funksioneve është i paradefinuar, për të cilët nuk ka nevojë të importohet asnjë pako.
close
Përdoret në komunikim të kanaleve, ku shërben për mbylljen e kanalit.
delete
Përdoret për fshirjen e të dhënave në maps.
len dhe cap
Funksioni len
tregon gjatësinë e një stringu si dhe numrin e anëtarëve të segmenteve (slices) dhe vargjeve (arrays).
new
Përdoret për rezervimin e memorjes për tipet e të dhënave e definuara nga përdoruesi (user defined data types).
make
Përdoret për rezervimin e memorjes për tipet e integruara ( built-in types), siç janë: hartat (maps), segmentet (slices) dhe kanalet (channels).
copy
Përdoret për kopjimin e segmenteve.
append
Përdoret për bashkangjitjen e segmenteve.
panic dhe recover
Përdoret në mekanizmin e raportimit të gabimeve.
print dhe println
Funksione të nivelit të ultë që mund të përdoren pa përdorimin e pakos fmt
. Kryesisht përdoren për debugging.
complex, real and imag
Përdoren për punë me numrat kompleksë.
Funksionet anonime
Shembull #1:
https://play.golang.org/p/6V7fnluaye_s
Rezultati:
Shembull #2:
https://play.golang.org/p/SHzy03Bokqs
Rezultati:
Defer
Defer
Urdhëri defer mundëson që një funksion 2 i thirrur nga një funksion 1 të ekzekutohet menjëherë para udhërit return të funksionit 1. Pra, thirrjen e funksionit 2 mund ta vendosim kudo brenda bllokut të funksionit 1, por ai do të ekzekutohet fare ne fund të funksionit.
https://play.golang.org/p/URvBJ0CPxw3
Rezultati:
Kjo është e dobishme në situatat kur dëshirojmë të sigurohemi që një funksion i dytë do të ekzekutohet në fund të një funksioni, siç është rasti i mbylljes së koneksionit me databazë.
Radha e ekzekutimit të defer të shumëfishtë
Nëse kemi më tepër se një thirrje të funksioneve me defer, do të zbatohet ekzekutim revers, gjegjësisht renditja LIFO (last in, first out), d.m.th. ai funksion që bëhet defer në fund, ekzekutohet i pari.
https://play.golang.org/p/eaBVdkBaoD-
Rezultati:
Urdhërat/funksionet me defer ekzekutohen edhe kur ka panic
, d.m.th. edhe nëse gjatë ekzekutimit të programit është lajmëruar gabim i nivelit panic
, gjë që mund të jetë e dobishme në situata të caktuara ku nevojitet të ndërmirret diçka para se të shfaqet raporti i gabimit.
Error, panic & recover
Error
Në Go, gabimet dërgohen si vlerë e veçantë kthyese e një funksioni. Pra, në Go nuk kemi të bëjmë me Exceptions
me struktura try/catch
sikurse në Java, PHP, etj.
Programet në Go i përdorin vlerat e tipit error
për të dhënë indikacion mbi një problem, gabim, gjendje abnormale.
Kur brenda një funksioni ka ardhur deri te një gabim, formohet raporti tekstual i tipit Error
dhe i njëjti kthehet si një prej vlerave kthyese të funksionit, që më pas atë vlerë ta lexojmë nga variabla korrresponduese brenda funksionit thirrës, dhe nëse vlera nuk është nil
(që e verifikojmë me strukturën if), vendosim se çfarë të ndodh më tej me rrjedhën e ekzekutimit të programit.
Pakoja errors
implementon funksione për manipulimin me raportet e gabimeve.
Funksioni New
krijon gabimet përmbajtj e të cilave është mesazh tekstual.
https://play.golang.org/p/gb2YT3xhYi3
Rezultati:
Nëse funksionit i japim këto argumente: ` c, e := pjesto(9, 2)`, atëherë rezultati do të jetë:
Ndonëse jemi të lirë ta vendosim vlerën e gabimit kudo në vlerat kthyese të funksionit, me konvencion rekomandohet që të jetë vlera e fundit.
Kur raportit duam t’ia bashkangjisim edhe informata shtesë, në vend të string
do të përdorim një struct
. Kësisoj, raporti mund të ketë detaje sqaruese mbi parametra të ndryshëm që kanë sjellur deri te gabimi.
https://play.golang.org/p/hCu-E48E4bj
Rezultati:
Nëse funksionit i japim këto argumente: ` c, e := pjesto(9, 2)`, atëherë rezultati do të jetë:
Raportin në formë stringu mund ta fitojmë me:
Te funksioni pjesto()
, parametri i dytë duhet të jetë i tipit error
. Kur si vlerë kthyese duhet të kthehet se nuk ka ndodhur gabim, si vlerë të dytë kthyese e kthejmë vlerën nil
.
Çdo funksion që kthen si vlerë kthyese e ka të deklaruar tipin error
, duhet të kthejë vlera të tipit error
. Për shembull, funksioni Open
nga pakoja os
, si vlerë të dytë kthyese e ka variablin err
të tipit error
.
Prandaj, për të verifikuar nëse ka pasur gabim gjatë hapjes së fajllit me funksionin os.Open()
, rezultatin e funksionit e vendosim në dy variabla, ku variabli i parë do ta përmbajë përmbajtjen e fajllit, ndërsa i dyti do ta përmbajë raportin e gabimit nëse ka pasur gabim. Nëse nuk ka pasur, vlera e variablit të dytë do të jetë nil
.
https://play.golang.org/p/790oGr4BJuO
Rezultati:
Nëse nuk gjendet fajlli i specifikuar:
Nëse gjendet, atëherë do të shfaqet përmbajtja e fajllit.
Tipi error
i përket tipit intefejs. Një variabël i gabimit mund të përmbajë çfarëdo vlere që mund të prezantohet si string.
Tipi error
është tip i paradeklaruar dhe është i definuar në universe block
, d.m.th. në nivelin e kodit të tërësishëm dhe jo vetëm brenda një pakoje apo funksioni.
Implementimi më i shpeshtë i pakos errors
haset në përdorimin e tipit errorString
.
Konstruktimi i vlerave të tipit error bëhet me funksionin errors.New
, të cilit ia bartim vlerën e tipit string, ndërsa ky funksion kthen vlerë të tipit error
.
Shembull si mund të implementohet:
Kështu, kur e thërrasim funksionin Sqrt(), i përdorim dy variabla, një për rezultatin, tjetrin për gabimin:
Në këtë rast, variabli err, nëse kemi dhënë si argument funksionit Sqrt()
një vlerë negative, do të jetë: Rrënjë katrore e numrit negativ!
.
Pakoja fmt
e formaton vlerën e tipit error
duke e thirrur metodën Error()
që kthen tipin string.
Është detyrë e implementimit se si do të formulohet raporti i gabimit; raporti mund të jetë vetëm tekst, por edhe të përmbajë vlera të tjera për sqarim më të mirë të kontekstit të ndodhjes së gabimit.
Për shembull, tek rasti me rrënjën katrore, raporti mund të formulohet asisoj që ta tregojë edhe vlerën e argumentit për shkak të të cilit nuk mund të kryhet llogaritja:
fmt.Errorf()
merr si argumente tipa të ndryshëm, bën bashkangjitjen e tyre, ndërsa si rezultat kthen vlerë të tipit error
.
Interfejsi error
kërkon të implementohet vetëm metoda Error()
. Megjithatë, nëpër implementimi të ndryshme të tipit error
mund të kërkohen edhe metoda shtesë. Për shembull, pakoja net
kthen gabim të tipit error
, por në disa implementime të caktuara mund të kërkohen metoda shtesë të definuara në interfejsin net.Error
.
Në bazë të vlerës së Timeout()
do ta dijmë se ky gabim a ka të bëjë me kalimin e kohës së caktuar për arritjen e përgjigjes, ndërsa me Temporary()
kuptojmë nëse gabimi ka karakter të përkohshëm.
Panic
Janë disa operacione që mund të shkaktojnë panic
, siç janë:
- Pjestimi i një integeri me 0
- Qasje në një anëtar të vargut me indeks inekzistent
- Dërgimi në një kanal të mbyllur
- Dereferencimi i një nil pointeri
- Përdorimi i thirrjes rekurzive të një funksioni që e mbush stack-un
Panic duhet të përdoret për gabimet që ndodhin papritmas dhe që normalisht nuk mund të kenë rikuperim. Rikuperimi nga një panic
duhet të jetë vetëm përpjekje për të ndërmarrë diçka në lidhje me atë gabim para se të dilet nga aplikacioni. Nëse shfaqet në problem i papritur, kjo ndodh nëse gabimi nuk është menaxhuar si duhet apo mungojnë disa verifikime.
Përdorimi tipit i panic
është për ndërprerjen e programit kur një funksion kthen një vlerë të tipit error
të cilin nuk dijmë si ta menaxhojmë, apo nëse vazhdimi i ekzekutimit të programit nuk ka kuptim në rast se është shfaqur ai gabim.
https://play.golang.org/p/U3xn0TFPLvA
Rezultati:
Në rast suksesi:
Në rast dështimi:
Funksioni panic()
është funksion i Go që do ta ndërpresë rrjedhën normale të ekzekutimit të programit dhe do të lajmërojë gabim. Kur një funksion F() e thirr funksionin panic()
, çdo funksion i shtyrë (deferred functions) brenda funksionit F() do të ekzekutohet normalisht, pastaj funksioni F() i kthen rezultat thirrësit.
Ndaj thirrësit, funksioni F() sillet si thirrje për panic
. Procesi vazhdon derisa të thirren të gjitha gorutinat dhe në fund programi e ndërpret ekzekutimin. Paniku mund të inicohen dhe e thirrur drejtpërsëdrejti funksionin panic()
. Por, një panic
mund të shkaktohet edhe për shkak të gabimeve gjatë ekzekutimit (runtime errors), siç është për shembull rasti kur e kërkojmë një anëtar të vargut me indeks inekzistent.
Recover
Funksioni recover()
është funksion që rimerr kontrollin pas një gorutine që ka shkaktuar panik.
Ky funksion është i dobishëm vetëm brenda funksioneve të shtyra (deferred functions). Gjatë ekzekutimit normal, një thirrje për rikuperim (recover) do të kthejë vlerën nil
dhe nuk do të ketë efekt tjetër. Nëse gorutina aktuale është duke shkaktuar panik, një thirrje e recover()
do ta kapë vlerën e dhënë funksionit panic()
, ndërsa ekzekutimi i programit do të vazhdojë normalisht.
Shembull nga https://blog.golang.org/defer-panic-and-recover
https://play.golang.org/p/Ujf1dRatTMb
Rezultati:
Tash do ta largojmë funksionin anonim të shtyrë që bënte recover()
, dhe kur vlera e i bëhet 4, do të thirret panic()
, nga i cili nuk ka rikuperim, prandaj të gjitha funksionet e shtyra do të ekzekutohen në renditje të mbrapshtë, ndërsa funksionit main()
thirrja e funksionit f()
i cili nga funksioni g()
ka pranuar panic
do të kthehet po ashtu panic
me çka main()
do ta ndërpresë ekzekutimin prandaj edhe rreshti fmt.Println("Returned normally from f.")
nuk do të ekzekutohet fare.
https://play.golang.org/p/hLQoozxSYOC
Rezultati:
Gorutinat, kanalet, sinkronizimi
Konkurrenca
Ndër karakteristikat më të rëndësishme të Go është se ka përkrahje pët konkurrencë (concurrency). Me konkurrencë nënkuptojmë ekzekutimin e njëkohshëm të disa funksioneve, që në Go quhen gorutina (goroutines). Konkurrenca nuk nënkupton paralelizëm, por procese të ndara ku secili proces ka një detyrë të caktuar dhe mund të komunikojnë ndërmjet vete nëpërmes kanaleve.
Nëse procesori posedon vetëm një bërthamë, atëherë programi do të ekzekutohet në mënyrë sekuencionale, ku një gorutinë ekzekutohet pas gorutinës tjetër.
Suporti për konkurrencë në Go nëse rastet kur procesori ka më shumë bërthama (cores) mundëson shfrytëzimin e të gjitha bërthamave të procesorit për ekzekutimin sa më efiçent të një numri të madh të gorutinave.
Gorutinat
Gorutinat janë funksione apo metoda që ekzekutohen në mënyrë konkurrent me funksionet apo metodat tjera. Gorutinat mund të imagjinohen si threads me peshë më të vogël ( light weight threads). Kostoja e krijimit të një gorutine (në kuptim të resurseve procesorike) është më e ultë se e një thread. Prandaj, tipike për aplikacionet në Go është ekzekutimi konkurrent i mijëra gorutinave.
Gorutina, pra, është një detyrë e cila ekzekutohet në mënyrë të pavarur. Go mundëson edhe koordinimin ndërmjet gorutinave të ndryshme.
Gorutinat kundrejt threads
Gorutinat janë shumë më pak të kushtueshme në krahasim me threads. Ato zënë vetëm disa kilobajtë në stack dhe stack-u mund të rritet e zvogëlohet në varësi prej nevojave të aplikacionit, gjersa kur kemi të bëjmë me threads, madhësia e stack-ut duhet të specifikohet dhe të jetë fikse.
Gorutinat shfrytëzojnë threads, ku një thread-i i korrespondojnë shumë gorutina, në raste edhe me mijëra. Nëse për shembull një thread është i bllokuar në pritje të inputit nga ana e përdoruesit, atëherë ajo gorutinë bartet në një thread tjetër i cili krijohet aty për aty, prej nga vazhdon ekzekutimi i gorutinës.
Për gjithë procesin e menaxhimit të gorutinave dhe threads përkujdeset runtime, prandaj në kodin e aplikacionit nuk kemi nevojë të implementojmë kurrfarë logjike në lidhje me gorutinat.
Gorutinat komunikojnë duke përdorur kanalet. Kanalet mundësojnë parandalimin e ndodhjes së race conditions
në rastet kur bëhet qasje në memorjen e ndarë (shared memory) nga gorutinat.
Kur startohet një gorutinë, thirrja e atij funksioni kthen (return) menjëherë. Për dallim prej funksioneve, kontrollli nuk do të presë që gorutina ta përfundojë ekzekutimin. Kontrolli kalon menjëherë në rreshtin vijues të kodit pas thirrjes së gorutinës dhe çfarëdo vlere kthyese nga gorutina do të injorohet.
Edhe funksioni main()
\është gorutinë, dhe ai duhet të jetë duke u ekzekutuar për t’i mundësuar ekzekutimin gorutinave të tjera. Nëse main ndërpret ekzekutimin, atëherë programi do ta ndërpresë ekzekutimin dhe asnjë gorutinë tjetër nuk do të ekzekutohet.
Ta marrim shembullin e thirrjes së një funksioni nga një cikël, pra brenda një iteracioni:
https://play.golang.org/p/EIAeMTefQIc
Rezultati:
Në këtë program, 10 herë përsëritet thirrja e funksionit printo()
, i cili e shfaq në ekran vlerën e inkrementit. Të gjitha thirrjet e funksionit bëhen në mënyrë sekuencionale, d.m.th. pa e kryer ekzekutimin i funksionit brenda një cikli nuk bëhet thirrje e sërishme e atij funksioni.
Tash ta bëjmë të njëjtën por duke përdorur gorutinat:
https://play.golang.org/p/VNAXEkGGg3N
Rezultati:
Për ta ekzekutuar një funksion në formë gorutine, duhet që para thirrjes së funksionit të shënohet go
, pra: go printo(x)
. Me këtë do të krijohen 10 gorutina, ku secila ekzekutohet si thread
në vete, ku ndonjë gorutinë ekzekutohet e pavarur nga një gorutinë tjetër, e ndonjë gorutinë ekzekutohet në formë sekuencionale, krejt në varësi prej numrit të bërthamave të procesorit. I tërë ky proces menaxhohet nga runtime i Go.
Vërejmë se renditja e rezultateve nuk është në sekuencë të njëjtë rritëse nga 1 deri 10, siç ishte rasti me shembullin e mëparshëm. Kjo ndodh për shkak se secila gorutinë është thread
në vete, ku ndonjë gorutinë ekzekutohet më heret, ndonjë më vonë, dhe rezultatet shfaqen pikërisht sipas renditjes së kryerjes së ekzekutimit të gorutinave.
Pauza në fund: time.Sleep(100 * time.Millisecond)
është e domosdoshme, në mënyrë që funksioni main() të arrijë t’i pranojë rezultatet e gorutinave. Nëse e fshijmë atë rresht dhe e startojmë programin, do të shohim se nuk do të shfaqet asnjë rezultat sepse funksioni main() do të jetë i ekzekutuar ende pa arritur të kthehen rezultatet nga gorutinat.
Në secilin ekzekutim vijues të programit, renditja e rezultateve mund të ndryshojë. Këtë shembull duhet ta kompajlojmë dhe ekzekutojmë lokalisht, sepse në Go Playground mund të ekzekutohet vetëm një thread
prandaj gjithmonë do të gjenerohet sekuenca e njëjtë prej 1 deri 10, sepse të gjitha gorutinat ekzekutohen në mënyrë sekuencionale njëra pas tjetrës.
Pauzën në fund të funksionit main()
mund ta realizojmë edhe në mënyra të tjera, për shembull duke e përdorur funksionin fmt.Scanln()
që do të presë derisa përdoruesi ta shtyp tastin Enter.
Goroutinat mund t’i thërrasim edhe nga një closure.
Fillimisht, marrim shembullin e një closure që thirret nga një for iteracion, i cili e thirr një funksion por jo si gorutinë.
https://play.golang.org/p/H1lp-LSCVHW
Rezultati:
Shohim se është formuar një seri sekuencionale e numrave nga 10 deri në 50, me hap 5.
Tash e provojmë të njëjtën, tash duke e thirrur closure me go
.
https://play.golang.org/p/q2kmPapJeqI
Rezultati:
Tash vërejmë se vlerat nuk janë sekuencionale dhe kjo ndodh për shkak të vetë natyrës së gorutinave ku secila gorutinë është proces në vete dhe nuk e kemi të garantuar me çfarë radhitje do të kthehen rezultatet e secilës gorutinë veç e veç.
Në situata të këtilla, kur një closure thirret nga një cikël, mund të ndodhë që në momentin kur closure e lexo vlerën e iteratorit, ajo vlerë mos të jetë vlera e radhës, kështu që kurrë me saktësi nuk mund ta dijmë se cilën vlerë të iteratorit do ta marrë closure. Pra, mund të ndodhë që për ndonjë vlerë mos të startohet një gorutinë.
Këtë problem e zgjidhim duke e bartur vlerën e iteratorit në closure duke e dhënë si parametër:
https://play.golang.org/p/ygHdYAPS8CH
Rezultati:
Shembull nga https://golangbot.com/goroutines/
Rezultati:
Sinkronizimi
Go posedon me një pako të quajtur sync që mundëson thjeshtimin e procesit të sinkronizimit të goritunave.
Në situata të thjeshta, gorutinat mund të sinkronizohen nëpërmes kanaleve.
Pritja e një gorutine të vetme
Channels
Pritja e një gorutine të vetme mund të implementohet nëpërmes përdorimit të një kanali. Kur e përfundon ekzekutimin, gorutina dërgon mesazh gorutinës kryesore e cila është në pritje.
Shembull me funksion anonim:
https://play.golang.org/p/BaDRWB7Lqcv
Rezultati:
Shembull me funksion:
https://play.golang.org/p/Z6Dlva11tLm
Rezultati:
Channels buffering
Në mënyrën standarde, kanalet janë unbuffered
, që do të thotë se një kanal mund të pranojë dërgesë (chan <-) vetëm nëse ekziston pranuesi (<- chan) për ta pranuar vlerën e dërguar.
Kanalet që janë buffered
mund të pranojnë një numër të specifikuar të vlerave pa pranues korrespondues për këto vlera. Në shembullin vijues krijohet buffer
me 2 vlera.
https://play.golang.org/p/7xjLQxVGQsz
Rezultati:
Drejtimi i kanalit
Kur përdoren kanalet si parametra të funksionit, mund të specifikojmë nëse kanali duhet vetëm të dergojë apo vetëm të pranojë vlera.
Nëse një kanal e deklarojmë vetëm si pranues, kompajleri do të raportojë gabim gjatë kompajlimit nëse brenda kodit e përdorim si dërgues.
https://play.golang.org/p/rOvR1KQeIa6
Rezultati:
Select
Go’s select lets you wait on multiple channel operations. Combining goroutines and channels with select is a powerful feature of Go.
Select mundëson pritjen e operacioneve të shumëfishta të kanalit. Kombinimi i gorutinave dhe kanaleve me select
është karakteristikë e fuqishme e Go.
https://play.golang.org/p/agbUdEqqzZk
Rezultati:
Timeouts
Timeouts janë të rëndësishme për programet që konektohen në resurset eksterne, ku nuk e kanë të garantuar se resursi ekstern do të jetë i qasshëm brenda një intervali të paracaktuar.
https://play.golang.org/p/7y-rXJYSnZk
Rezultati:
Non-Blocking Channel Operations
Mbyllja e kanaleve
Range over Channels
Timers
Tickers
Worker Pools
WaitGroups
Rate Limiting
Atomic Counters
Mutexes
Stateful Goroutines
Sinkronizimi i kanaleve
Në shembullin vijues do të demonstrohet sinkrinozimi i gorutinës nëpërmes kanalit. Në këtë rast do të përdoret kanali me emrin perfundoi
nëpërmes të cilit gorutina do t’ia përcjellë funksionit main()
informatën se një proces ka përfunduar. Me rreshtin perfundoi <- true
shkaktohet pritje në funksionin main()
derisa të pranohet mesazhi nga gorutina.
https://play.golang.org/p/MKSUA5NVmbq
Rezultati:
Pritja e më shumë gorutinave
sync
Nëse nevojitet të presim përfundimin e gorutinave të shumëfishta, do ta përdorim pakon sync.WaitGroup
. Gorutina main e thërret metodën Add
për të specifikuar numrin e gorutinave që duhen pritur. Më pastaj, çdo gorutinë ekzekutohet dhe thërret metodën Done
kur ta përfundojnë ekzekutimin. Wait
mund të përdoret për bllokim gjersa të përfundojnë të gjitha gorutinat.
https://play.golang.org/p/p7StS5oK6nX
Rezultati:
HTTP Webserver
Go ofron suport për protokolin HTTP, me ç’rast nuk nevojitet kurrfarë serveri i jashtëm, për shembull siç është rasti me PHP.
https://play.golang.org/p/n8CKMxzIp9z
Rezultati:
Programi i mësipërm kthen përgjigje në konzolë, por në fakt ne do ta modifikojmë asisoj që përgjigjen ta kthejë si HTPP Response, pra t’ia dërgojë tekstin browserit në vend të konzolës.
Ashtu si është tani, programi startohet, shfaq tekstin “Duke e startuar serverin” në konzolë.
E hapim browserin dhe atje shënojmë si adresë:
Sa herë bëjmë refresh në browser, në konzolë do të shfaqet teksti “Pergjigja nga Web aplikacioni”.
Funksioni HandleFunc nga pakoja http, shërben si një ruter bazik.
Si parametër të parë e shënojmë rutën e që në rastin konkret është “/” që d.m.th. home page, respektivisht faqja kryesore që hapet kur e shënojmë vetëm domainin, në rastin konkret http://localhost:8080.
Si parametër të dytë e shënojmë emrin e funksionit i cili thirret, dhe i cili do ta bëjë kthimin e përgjigjes (Response), në rastin konkret homeHandler.
Funksioni homeHandler (sikurse edhe funksionet tjera që do t’i definojmë te rutat tjera), i ka dy parametra; w të tipit http.ResponseWriter dhe r të tipit pointer i http.Request :
- w http.ResponseWriter
- r *http.Request
Nëse ruta ka parametra, ato lexohen nga http.Request, ndërsa në http.ResponseWriter dërgohet përmbajtja e dëshiruar në browser, zakonisht të dhëna në formatin HTML ose JSON.
Nga http.Request lexohen edhe të dhënat e fushave të formularit, nëse ajo rutë është thirrur si “action” i një formulari.
Funksioni ListenAndServe() e starton serverin në portin e cekur. Prej momentit kur fillon ekzekutimi i këtij funksioni, ne mund t’i qasemi serverit nëpërmes browserit. Vetë programi do të ngelet duke u ekzekutuar deri sa ta ndërprejmë me Ctrl-C. Asgjë nuk do të ekzekutohet pas këtij rreshti (rreshti 11), përveç nëse paraqitet ndonjë gabim, me ç’rast ai gabim raportohet në konzolë (rreshtat 12-14).
Për këtë shkak, të gjitha definicionet e rutave duhet të bëhet para thirrjes së ListenAndServe().
Meqë në këtë program konkret ende nuk kemi shënuar funksione që dërgojnë përgjigje në http.Response(), kur e hapim adresën në browser, browseri do të shfaqë faqe të zbrazët.
r.Method
Me r.Method
mund ta determinojmë HTPP metodën e përdorur.
Konstantat e paradefinuara për secilën prej metodave me të cilat mund të krahasojmë:
* MethodGet = “GET”
* MethodPost = “POST”
* MethodPut = “PUT”
* MethodPatch = “PATCH” // RFC 5789
* MethodDelete = “DELETE”
* MethodOptions = “OPTIONS”
* MethodHead = “HEAD”
* MethodConnect = “CONNECT”
* MethodTrace = “TRACE”
Dërgimi i kërkesës me metodën POST
resp, err := http.Post("http://example.com/upload", "image/jpeg", &buf)
Dërgimi i kërkesës me metodën GET
resp, err := http.Get("http://example.com/")
Dërgimi i kërkesës me PostForm
resp, err := http.PostForm("http://example.com/form", url.Values{"key": {"Value"}, "id": {"123"}})
r.URL
Me r.URL
e lexojmë URL-në e shënuar në address bar të browserit bashkë me query string.
r.URL.Path
Me r.URL.Path
e lexojmë vetëm rutën, pa query string.
Zbërthimi i një URL
p := strings.Split(r.URL.Path, "/")
Definicioni i rutës duhet të përfundojë me /
:
http.HandleFunc("/test/", homeHandler)
Parametrit në segmentin e dytë i qasemi me p[2]
.
Nëse e duam si integer
, e konvertojmë:
id, _ := strconv.Atoi(p[2])
Pas kësaj mund ta përdorim për ta përcjellur si id në SQL query
.
Numri i segmenteve
Numri e segmenteve e determinojmë me len(p)
.
HTML escape
html.EscapeString(r.URL.Path)
Shembull i ngjashëm
https://play.golang.org/p/nBi8gB-k683
Aplikacioni i mësipërm përmban vetëm një rutë, së cilës rutë i korrespondon një funksion.
http.HandleFunc("/profile", profilePage)
Thirret në këtë formë: http://localhost:8080/profile?name=Teuta
Në funksionin http.HandleFunc
që i përket pakos net/http
, si parametër të parë e shënojmë emërtimin e rutës, ndërsa si parametër të dytë e shënojmë emrin e funksionit i cili duhet të ekzekutohet kur thirret kjo rutë.
Të gjitha funksionet të cilat do të përdoren për procesimin e Web kërkesave (Web requests) për të kthyer më pastaj përgjigje (Web response), do t’i kenë dy parametra:
http.ResponseWriter
*http.Request
http.ResponseWriter
do të përdoret për dërgimin e response, ndërsa *http.Request
për ta lexuar kërkesën e cila ka mundur të vijë me ndonjërën nga HTTP metodat.
Ndaj variablit r
në këtë shembull ku është vendosur HTTP kërkesa, respektivisht rezultati i kthyer nga strukti *http.Request
, mund të zbatojmë metoda/funksione të ndryshme specifike për Web kërkesën. Në rastin konkret, thirret metoda FormValue() me anë të së cilës do të jemi në gjendje të lexojmë vlerat e query string
të dërguar me ndonjërën nga HTTP metodat e cekura më sipër.
Dërgimin e përgjigjes (response) e bëjmë me fmt.Fprint()
, e cila metodë kërkon dy parametra:
- Variablin i cili i korrespondon struktit të response (w)
- Përmbajtjen e cila dërgohet, që në shembullin konkret është string i rëndomtë
http.ListenAndServe(":8080", nil)
e starton Web serverin në portin 8080, në mënyrë që ne të mund t’i qasemi Web aplikacionit nga browseri duke shënuar:
`localhost:8080/profile’
Në shembullin konkret, do të shtojmë edhe ?name=Golang
në fund të URL-së për të demonstruar leximin equery string
nga URL-ja. Pra, në browser shënojmë:
`localhost:8080/profile?name=Golang’
Pas kësaj, në browser do të shfaqet teksti:
This is your profile page, Golang
Ndërtimi i rutave të tjera
Tash do t’i shtojmë pak më shumë funksionalitet aplikacionit tonë: regjistrimin e një anëtari, në këtë rast duke mos përdorur fare databazën, por vetëm duke i ruajtur të dhënat në memorje.
E formojmë një strukt për anëtarin:
E formojmë një map për anëtarët:
Si çelës i mapës do të përdoret emri i anëtarit, ndërsa si vlerë do të jetë strukti, i cili i përmban të dhënat e tjera të anëtarit (në këtë shembull, vetëm emrin dhe emailin).
E ndërtojmë funksionin për regjistrimin e anëtarit, duke lexuar të dhënat e kërkuara nga query string nga browseri, pra me metodën GET.
Vlerat hyrëse nga query string (apo nga formulari), lexohen me:
Brenda Get() shënohet emri i fushës së formularit, ashtu siç është definuar me atributin name, sepse atributi name paraqitet si variabël në query string.
Funksioni i referohet ResponseWriter dhe Request nga pakoja http, prandaj nevojitet që kjo pako të importohet:
Tash e shkruajmë funksonin për listimin e anëtarëve, i cili do ta formojë një string me emrat dhe emailat e anëtarëve, duke bërë iteracion nëpër mapën anetarMap me strukturën for.
Në funksionin main() definojmë rutat për këto dy funksione:
Programi komplet:
https://play.golang.org/p/nKMyE2jgUGI
Pasi të jetë startuar programi, e hapim browserin dhe e hapim URL-në si vijon:
http://localhost:8080/regjistro?emri=Granit&emaili=granit@gmail.com
Në query string, pas variablit emri e shënojmë një emër, ndërsa pas variablit emaili e shënojmë një email, pastaj shtypim Enter. Këtë e përsërisim aq herë sa anëtarë dëshirojmë të regjistrojmë.
Të njëjtën mund ta bëjmë edhe nëpërmes një formulari, tek i cili shënojmë si vlerë të atributit “action” URL-në e regjistrimit.
Ky formular mund të hapet si fajll statik drejtpërsëdrejti në browser.
Metoda e tretë është nëpërmes ndonjë programi për testimin e API-ve, siç janë Postman apo Insomnia.
Rezultati që kthehet është:
U regjistrua: Alban
Lista e anëtarëve shfletohet duke e thirrur URL-në lista.
Rezultati:
Lista e anëtarëve:
Tahir:tahir.hoxha@gmail.com
Arta:arta@gmail.com
Granit:granit@gmail.com
Alban:alban@gmail.com
Meqë rezultati në formë tekstuale nuk është i strukturuar, ne mundemi mapën e njëjtë ta konvertojmë në JSON, duke bërë ndryshime në funksion:
Tash lista do të duket kështu:
Ky rezultat mund të procesohet më tej me cilëndo gjuhë: Go, Java, C#, PHP, JavaScript, etj.
Package net
Në thelb të komunikimit rrjetor në Go është pakoja e quajtur net
. Kjo pako ofron implementimet për HTTP klient dhe server.
Pakoja net
përmban nënpako jo vetëm për HTTP operacionet relevante, por po ashtu edhe për serverët TCP/UDP, DNS dhe IP vegla.
hello.go
https://play.golang.org/p/dSSZS2VPxYh
Rezultati shfaqet në browser, duke e thirrur me rutat /dinamike
për përmbajtjen dinamike që do të jetë shfaqja e kohës aktuale dhe /statike
për përmbajtjen statike që në rastin konkret do të jetë një HTML dokument. Kodi duhet të kompajlohet: go run hello.go
.
dokumenti.html
Si port për Web server mund ta përdorim cilindo port, ndërsa në shembuj do ta përdorim portin 8080.
http.HandleFunc
Me http.HandleFunc
i definojmë rutat e dëshiruara, duke e cekur si parametër të parë rutën, ndërsa si parametër të dytë funksionin i cili i korrespondon asaj rute.
http.ServeFile
Me http.ServeFile
mund të dërgojmë një përmbajtje statike si reagim ndaj kërkesës.
Në rreshtin http.HandleFunc("/statike", faqeStatike)
përcaktojmë që kur të kërkohet ruta /statike
të thirret funksioni faqeStatike
, i cili në këtë shembull ka për detyrë vetëm ta shfaqë një dokument statik me emrin dokumenti.html
.
Nëpërmes variablit w
do të mund të shkruajmë HTTP reagimin (response), ndërsa nëpërmes variablit r do ta pranojmë HTTP kërkesën (request).
fmt.Fprintln(w, response)
Me fmt.Fprintln
dërgojmë përmbajtjen e dëshiruar si HTTP response, pra ajo përmbajtje do të jetë e qasshme në browser nëpërmes rutës së definuar, në rastin konkret /dinamike
.
Radhitja e procesit të zhvillimit të Web aplikacionit
- E shkruajmë kodin në Go
- E krijojmë HTML dokumentin statik
- E kompajlojmë kodin dhe e ekzekutojmë
- Nëse lajmërohet anti-virusi, e lejojmë që ta skanojë .exe programin e sapokrijuar
- Nëse firewall kërkon leje për lejimin e portit, ia japim lejen
- Kalojmë në browserin e dëshiruar dhe në
address bar
shënojmë/statike
ose/dinamike
për ta parë përmbajtjen e këtyre dy rutave - Nëse bëjmë ndryshime, atëherë e ndërprejmë në terminal ekzekutimin e programit me
Ctrl-C
, e më pas e kompajlojmë dhe e ekzekutojmë sërish
Leximi i përmbajtjes së një Web faqeje
Me programin vijues do ta lexojmë përmbajtjen e një faqeje në adresë të caktuar dhe përmbajtjen e saj do ta shfaqim në terminal.
https://play.golang.org/p/sr8tQd6jBs3
http.Get
Mundëson qasjen në përmbajtjen e një resursi në URL-në e specifikuar. Përmbajtja e kthyer do të jetë e tipit *http.Response
.
ioutil.ReadAll
func ReadAll(r io.Reader) ([]byte, error)
CRUD operacionet
Në shembullin vijues, do të krijohet një REST API i thjeshtë me 4 operacionet bazike ndaj databazës (CRUD), me koneksionin e databazës si variabël globale, në mënyrë që të mos krijohet koneksioni me databazën brenda çdo funksioni.
https://play.golang.org/p/srKlazK-9_S
Leximi i librit me ID 4:
http://localhost:8082/book/4
Insertimi i një libri të ri:
http://localhost:8082/save/?book_title=Java+per+fillestare
Përditësimi i një libri ekzistues
http://localhost:8082/update/1/?book_title=Python+per+fillestare
Fshirja e një libri
http://localhost:8082/delete/?id=1
Sqarime:
Definojmë konstantat me vlerat për hostin, portin, përdoruesin, fjalëkalimin dhe emrin e databazës.
Më pas, e definojmë një variabël globale me emrin database
të tipit *sql.DB
.
Në funksionin main(), e formatojmë stringun e koneksionit dhe e ruajmë në variablin dbConn
.
Bëjmë konektimin me MySQL server dhe verifikojmë nëse është shfaqur ndonj[ gabim:
Vlerën e variablit db
e bartim në variablin database
të cilin më parë e kemi deklaruar si variabël globale. Tash e tutje, çfarëdo operacion ndaj databazës e kryejmë duke iu referuar variablit database
nëpër funksione:
ose
Struct / database query
Në shembullin vijues do të përdorim një strukt (Lajmi), të përbërë nga 4 fusha:
- Id,
- Titulli,
- Teksti, dhe
- Data.
Tabelën mund ta krijojmë duke e përdorur SQL kodin e mëposhtëm.
lajmet.sql
Programi
Për rutim më të avancuar, kësaj radhe do të përdoret pakoja github.com/gorilla/mux
.
routes.HandleFunc("/page/{id:[0-9]+}", ServePage)
Me këtë kemi definuar se ruta do të jetë /page/
, që do ta pranojë një parametër të cilit brenda funksionit do t’i referohemi si id
. Id-ja do të jetë numër prej 0 deri 9 me një apo më tepër shifra. Kështu që një lajm do të mund të hapet me /page/1
.
Nëse parametri i shënuar pas /page/
nuk është numerik, pra kur nuk i përgjigjet pattern-it të definuar {id:[0-9]+}
, do të lajmërohet gabimi:
404 page not found
Funksioni ServePage do ta pranojë id-në nga r *http.Request
me:
mux.Vars(r)
përmban të gjitha GET variablat nga request në formë të tipit
map, prej nga me vars["id"] e ekstraktojmë vetëm fushën e
id, të cilin pastaj e përdorim gjatë ndërtimit të
SQL query`.
SQL query thirret me database.QueryRow()
ku shënojmë SELECT id, titulli, teksti, data FROM lajmet WHERE id=?
, ndërsa id-në e faqes e japim si parametër të dytë. Me këtë parandalojmë SQL injection
. Rezultati me Scan bartet në fushat korresponduese të struktit Lajmi.
Në w http.ResponseWriter
me fmt.Fprint()
e dërgojmë struktin si response. Ky nuk është format i përshtatshëm për response, sepse do të duhet ta dërgojmë si JSON ose si HTML dokument.
Kodi i programit
https://play.golang.org/p/RkvQdXGqT0e
Leximi i një lajmi:
Në versionin ekzistues, nëse nuk gjendet një lajm me id të caktuar në tabelën e lajmeve, krejt çka raporton programi është një strukt i zbrazët.
Për të fituar një raport që tregon se lajmi nuk u gjet, e modifikojmë funksionin:
Nëse thirrja e QueryRow kthen gabim, atëherë në w
kthejmë statusin 404, edhe si tekst, edhe si status kod numerik:
Siç u cek më sipër, kthimi i rezultatit nga databaza u dërgua në ResponseWriter
nuk ishte i formatuar në formë të përshtatshme. Tash do ta formatojmë si JSON.
Së pari e importojmë pakon encoding/json
.
Konvertimi nga strukt në JSON kërkon që në strukt t’i definojmë JSON tags:
Konvertimi i struktit në JSON bëhet me metodën json.Marshal()
:
Tash b
përmban slice of bytes
, të cilin me funksionin string() e kthejmë në tekst të lexueshëm dhe e dërgojmë në http.ResponseWriter
nëpërmes variablit w
:
Kodi komplet:
https://play.golang.org/p/pRlmmAfKEfL
Tash nëpërmes thirrjes së rutës http://localhost:8080/page/1
do të fitojmë një JSON response sikur kjo:
Databazat
Për të punuar me MySQL, së pari duhet të shkarkohet drajveri përkatës nga Github:
go get github.com/go-sql-driver/mysql
https://play.golang.org/p/3l4xo1BS9y_s
Rezultati:
Leximi i radhëve të tabelës ne përdorimin e struct/tag
https://play.golang.org/p/1FMj5NKuGxn
Rezultati:
Drivers:
https://github.com/golang/go/wiki/SQLDrivers
I/O and File Systems
Shkrimi dhe leximi i një fajlli
https://play.golang.org/p/GwOnhuvuzo5
Leximi i një fajlli me buffering
poema.txt
https://play.golang.org/p/hDSWObsvV1f
Rezultati:
https://play.golang.org/p/M35QuSLefEc
Rezultati:
Në fajll sistem krijohet fajlli tedhenat.txt me përmbajtjen Teksti i cili do të ruhet në fajll.
.
Package fmt
Marrë nga https://golang.org/pkg/fmt/
Të përgjithshme:
- %v - vlera në formatin standard. Gjatë printimit të strukteve, shenja plus para v (%+v) i paraqet edhe emrat e fushave
- %#v - Vlera.
- %T - Tipi i vlerës.
- %% - Shenja e përqindjes, nuk konsumon vlerë.
Boolean:
- %t - Fjala true ose false.
Numrat e plotë:
- %b - Baza 2, numra binarë (0 deri 1).
- %c - Karakteri sipas Unicode kod pointit korrespondues. fmt.Printf(“%c”, 1234) shfaq Ӓ.
- %d - Baza 10, numrat decimalë (0 deri 9).
- %o - Baza 8, numrat oktalë (0 deri 7).
- %q - Unicode karakteri i futur brenda apostrofave. fmt.Printf(“%q”, 1234) shfaq ‘Ӓ’.
- %x - Baza 16, numra heksadecimalë (0-F). Shkronja të vogla (a-f).
- %X - Baza 16, numra heksadecimalë (0-F). Shkronja të mëdha (A-F).
- %U - Formati i Unicode. fmt.Printf(“%U”, 1234) shfaq U+04D2.
Numrat me presje dhjetore dhe kompleksë:
- %b - Notacion shkencor pa decimale me eksponent të 2 në fuqi, p.sh. -123456p-78.
- %e - Notacion shkencor, p.sh. -1.234456e+78
- %E - Notacion shkencor, p.sh. -1.234456E+78
- %f - Numër me presje dhjetore, por pa eksponent, p.sh. 123.456
- %F - Sinonim i %f
- %g - %e për eksponentë të mëdhenj, %f përndryshe.
- %G - %E për eksponentë të mëdhenj, %F përndryshe.
Stringjet dhe bajt segmentet (slice):
- %s - Bajtë të painterpretuar të një stringu apo segmenti.
- %q - String u futur brenda thonjëzave.
- %x - Baza 16, shkronja të vogla, dy karaktere për bajt.
- %X - Baza 16, shkronja të mëdha, dy karaktere për bajt.
Segmentet:
- %p - Adresa e elementit me indeks 0 në notacionin me bazën 16, me 0x paraprijëse.
Pointer:
- %p - Notacion me bazën 16, me 0x paraprijëse.
- %b, %d, %o, %x dhe %X punojnë edhe me pointerë, duke e formatuar vlerën njëjtë sikur të ishte numër i plotë.
Formati standard për %v është:
- bool: %t
- int, int8 etc.: %d
- uint, uint8 etj.:** %d**, %#x nëse printohet me %#v
- float32, complex64, etj.: %g
- string: %s
- chan: %p
- pointer: %p
Në kuadër të pakos fmt
është i definuar interfejsi Stringer
i cili kërkon nga implementuesi që ta ketë funksionin String()
, me çka na mundësohet që një tip kompozit si strukti të mund të paraqitet si string gjatë printimit.
E krijojmë funksionin String() ku si pranues e caktojmë një strukt (Anetarj): func (p Anetari) String() string
, ku si vlerë kthyese e funksionit do të jetë string. Brenda funksionit definojmë se si të kombinohen vlerat e fushave të ndryshme të struktit Anetari për ta formuar një string, pra që përmbajtja e struktit të prezantohet në formë të një rreshti tekstual.
https://play.golang.org/p/8EFAyafWgCy
Rezultati:
Package strings
Join
https://play.golang.org/p/m26wcBgIwJg
Rezultati:
Regular Expressions
Kërkojmë një substring brenda një stringu:
https://play.golang.org/p/7Oc6DQZQPhR
Rezultati:
Substringu “lisa” u gjet (true), ndërsa substringu “fusha” nuk u gjet (false).
—
Kërkojmë një substring që i ka 3 shkronja, e specifikojmë shkronjën e parë dhe të tretë, ndërsa shkronja e dytë le të jetë cilado shkronjë:
—
Kërkojmë një substring që fillon me m dhe mbaron me t, nuk ka rëndësi sa shkronja janë ndërmjet.
—
Kërkojmë substringun “fu” nëse është në fillim të stringut.
Kjo jep false sepse kërkimi është case-sensitive.
Kjo rezulton në true.
—
Kërkojmë substringun “luar” në fund të stringut.
—
A fillon stringu me “O”:
REST API: Konceptet bazike
Web servisi është program që ka për qëllim realizimin e komunikimit ndërmjet kompjuterëve nëpërmes rrjetit, më konkretisht nëpërmes World Wide Web.
Informatat që shkëmbehen dërgohen dhe pranohen nëpërmes protokolit HTTP, sikurse në rastin e Web aplikacioneve, por në rastin e REST API theksi është tek të dhënat dhe jo te prezantimi në formë të HTML dokumentit.
REST API është pra backend servisi i cili e furnizon me të dhëna:
- Front-end aplikacionet, siç janë aplikacionet e punuara me Angular, React, Vue, etj.
- Aplikacionet mobile
- Web serviset tjera
Tipet e Web serviseve
Web serviset kanë pësuar evolucion përgjatë historisë së Web-it.
Në të kaluarën më së shumti është përdorur SOAP protokoli (Simple Object Access Protocol), i cili për shkëmbimin e të dhënave ka përdorur formatin XML (eXtensible Markup Language). Edhe sot mund të hasen Web servise të bazuara në SOAP, mirëpo është evidente se dominon REST dhe formati JSON për të dhënat.
REST API
REST (Representational state transfer) është Web servis më i thjeshtë në krahasim me SOAP, dhe po ashtu edhe JSON formati është më i thjeshtë se formati XML.
REST API mundëson komunikimin ndërmjet sistemeve të ndryshme dhe dërgimin dhe pranimin e të dhënave në një formë më të thjeshtuar.
Çdo thirrje ndaj REST API realizohet nëpërmes HTTP metodave dhe URL-ve të caktuara. Rëndomë, një REST API përdoret për të dërguar të dhëna e më shpesh për të pranuar të dhëna nga një sistem për menaxhimin e databazave.
Çdo HTTP metodë i korrespondon një operacioni të caktuar në databazë, dhe çdo operacioni në databazë i korrespondon një URL.
Operacionet bazike ndaj databazës janë:
- Create. Insertimi i të dhënave të reja në databazë.
- Retrieve. Leximi i të dhënave nga databaza.
- Update. Përditësimi i të dhënave në databazë.
- Delete. Fshirja e të dhënave nga databaza.
REST | SQL | HTTP |
Create | INSERT | POST |
Retrieve | SELECT | GET |
Update | UPDATE | PUT |
Delete | DELETE | DELETE |
Ka edhe dy HTTP metoda të tjera: PATCH dhe OPTIONS, përdorimin e të cilave do ta diskutojmë më vonë.
REST API: Karakteristikat
Karakteristikat e REST API
Stateless
REST API mund të jetë stateless, që e ka kuptimin se nuk e ruan gjendjen. P.sh. për çdo kërkesë (request) të dërguar, serveri kthen përgjigje (response) dhe çdo kërkesë vijuese që i shkon serverit konsiderohet si kërkesë që nuk ka kurrfarë lidhje me kërkesën paraprake. Pra, ka mundur të vijë nga përdoruesi i njëjtë apo një përdorues tjetër.
Cache
Në një aplikacion që pranon shumë kërkesa dhe kthen shumë përgjigje, mund të vërehet degradim i performansave, pra ngadalësim të punës. Kjo ndodh për shkak se prej momentit të dërgimit të një kërkese në serveri te aplikacioni ynë, aplikacioni duhet të kryejë një sërë operacionesh për të ardhur deri te kompletimi i përgjigjes që do ta kthejë, ku këtu përfshihen thirrje të shumëfishta në databazë, qasjen në fajlla, veprime të ndryshme të kalkulimit të vlerave për të përfunduar në gjenerimin e HTML apo JSON fajllit i cili do të jetë “produkt final” dhe që do të kthehet si përgjigje. I tërë ky proces mund të marrë shumë kohë për t’u kompletuar, ndërkohë që rezultatet e kthyera jo gjithmonë do të jenë të ndryshme nga ato që janë gjeneruar më parë.
Prandaj, një zgjidhje shumë e mirë që mundëson ngritjen e performancës së aplikacionit është nëpërmes përdorimit të cache, ku rezultatet e gjeneruara më parë ruhen në formën e vet finale, të gatshme për t’iu kthyer përdoruesit, pa pasur nevojë të kalohet nëpër tërë procesin e gjenerimit. Për shembull, kur kemi të bëjmë me të dhëna të cilat nuk ndryshojnë brenda një periudhe të caktuar, është shumë më e përshtatshme që të ruhen në formën finale për t’u servuar të gatshme, dhe jo që në çdo kërkesë të kalohet nëpër çdo hap të ekzekutimit të programit,
Kjo është e realizueshme për faktin se përgjigja e serverit gjithmonë është fajll, qoftë ai fajll në formatin tekstual, HTML, JSON, fajll binar, etj. dhe fajllat e gjeneruar mund të ruhen thjesht në formën statike për një periudhë të caktuar.
Sistem shumështresor
Një REST API mund të servohet nga shumë server të ndryshëm, ku serverët mund ta ndajnë ngarkesën ndërmjet vete, apo që një server të merret me pranimin e kërkesave, tjetri me gjenerimin e rezultateve, e një tjetër me kthimin e përgjigjesh drejt klientit i cili e ka dërguar kërkesën. Në këtë mënyrë mund ta zhvillojmë një Web servis i cili mund të veprojë i decentralizuar në kuptimin që funksionalitete të ndryshme mund të ofrohen nga servise të veçanta, të cilat ekzekutohen veçmas por megjithatë e formojnë një tërësi logjike dhe funksionale.
Kjo do të thotë që një REST API mund të komunikojë me një tjetër REST API, qoftë në sistemin e njëjtë, qoftë duke e përdorur një REST API ekstern, por që në instancë të fundit - të gjitha bashkë e formojnë një sistem.
Platformë agnostike
REST API ofron një ndërfaqe uniforme për komunikim, ku aspak nuk është e rëndësishme në çfarë platforme apo në cilën gjuhë programore është zhvilluar REST aplikacioni. Mund ta kemi një mikroservis të ndërtuar në Java i cili lehtësisht shkëmben të dhëna me një mikroservis tjetër të ndërtuar në Golang, Python, PHP apo ndonjë gjuhë tjetër.
Në vend se të zhvillohet si një Web aplikacion monolitik, një Web servis pra mund të zbërthehet në tërësi më të vogla të cilat mund të kenë dedikime të ndryshme por edhe të zhvillohen nga ekipe të ndryshme duke përdorur teknologji të ndryshme të cilat janë më të përshtatshme për punën konkrete, e prapë ai Web servis të veprojë si një tërësi kompakte përballë klientit të cilit në radhë të parë i interesojnë të dhënat dhe jo detajet e implementimit të aplikacionit.
REST API: HTTP metodat dhe status kodet
REST API përdor HTTP metodat e caktuara për të kryer veprime të caktuar ndaj resurseve apo grupi të resurseve.
Kur bëhet një kërkesë nga ana e klientit, ajo kërkesë duhet të përmbajë informatat vijuese:
- REST verb (Metoda: GET, POST, PUT, DELETE, PATCH, OPTIONS)
- Header (Meta informata)
- Body (Informata)
Me HTTP metodën caktojmë çfarë veprimi do të kryhet ndaj një resursi specifik, p.sh. POST për insertim, GET për lexim, etj.
GET
E lexon një radhë (record) me të dhëna, apo një set radhësh nga serveri.
Në rast të suksesit, kthen status kodin 200.
Në rast të dështimit, kthen status kodin 404.
OPTIONS
Shfaq të gjitha REST operacionet që janë në dispozicion.
Në rast të suksesit, kthen status kodin 200.
POST
Krijon një resurs të ri apo set resursesh.
Në rast të suksesit, kthen status kodin 201.
Në rast të dështimit, kthen status kodet 404 ose 409.
PUT
Bën përditësimin apo zëvendësimin e radhës së zgjedhur.
Në rast të suksesit, kthen status kodin 202 ose 204.
Në rast të dështimit, kthen status kodin 404.
PATCH
Bën përditësimin/ndryshimin e radhës së zgjedhur.
Në rast të suksesit, kthen status kodin 202 ose 204.
Në rast të dështimit, kthen status kodin 404.
DELETE
E fshin resursin e zgjedhur.
Në rast të suksesit, kthen status kodin 200.
Në rast të dështimit, kthen status kodin 404.
Kodet për sukses dhe dështim janë HTTP kode, të cilat kthehen bashkë me përgjigjen. Këto kode janë informatë për klientin i cili e ka dërguar kërkesën për të ditur nëse kërkesa ka pasur sukses apo jo.
REST API: Metoda GET
Metoda GET bën leximin e një resursi të caktuar nga serveri. Për ta specifikuar resursit, metoda GET përdor disa tipe të URL kërkesave.
- Parametrat nga ruta
- Parametrat nga query string
Metoda GET përdoret sa herë e shënojmë një URL në browser, apo kur klikojmë në ndonjë link. Edhe dërgimi i të dhënave nga një formular mund të bëhet me metodën GET, por për shkak të disa kufizimeve që i imponon metoda GET, tek formularët rëndomë përdoret metoda POST.
Në rast të ekzekutimi të suksesshëm të kërkesës së bërë me metodën GET, serveri kthen status kodin 200.
Parametrat nga ruta janë parametrat që janë të shënuar si segmente të URL-së, p.sh.:
/post/102
Me këtë është specifikuar së kërkohet postimi me ID 102.
Kur aplikacioni e zbërthen këtë URL, do të kërkojë cili funksion duhet të thirret për ta shfaqur një postim, që më pas atij funksioni t’ia përcjellë ID-në e specifikuar, me çka aplikacioni do ta marrë informatën se çfarë informate të kërkojë specifikisht në databazë. Në rast se ekziston postimi me ID 102, databaza i kthen aplikacionit fushat e rekordit të caktuar nga tabela e databazës, të cilat të dhëna pastaj aplikacioni i “konverton” në të dhëna të formatit JSON apo si HTML, por nuk përjashtohen edhe formatet tjera.
Përveç parametrave të rutës, aplikacionit mund t’i dërgohen të dhëna edhe nëpërmes query string, gjegjësisht variablat e bashkangjitur në URL në formatin:
?variabli1=vlera1&variabli2=vlera2&variabli3=vlera3
Një URL mund t’i përmbajë edhe parametrat e rutës, edhe query string. Për shembull, mund të specifikojmë se na nevojiten postimet e kategorisë së caktuar nëpërmes parametrit të rutës, dhe cilën faqe të rezultateve e dëshirojmë - me anë të query string.
http://domaini.com/kategoria/5?faqja=3
Në këtë rast, aplikacioni e lexon segmentin e dytë të URL-së si ID të kategorisë, dhe nëse ajo kategori ka shumë postime, mund ta shfaqe faqe për faqe, ku variabli faqja
do t’i shërbejë për ta shfaqur faqen e caktuar të rezultateve.
REST API: Metodat POST, PUT, PATCH, DELETE dhe OPTIONS
POST
Metoda POST përdoret për krijimin e një resursi të ri në server, p.sh. një postim apo një produkt të ri. Me krijim të resursit të ri rëndomë nënkuptojmë krijimin e një rekordi të ri në një tabelë të databazës.
Me POST nuk jemi të kufizuar në krijimin e vetëm një resursi, sepse në raste mund të kemi nevojë që nëpërmes metodës POST të dërgojmë një varg të dhënash të cilat në databazë do të ruhen si rekorde të shumta.
Gjatë insertimit të të dhënave në databazë, për çdo rekord do të krijohet një ID unike, e cila ID pastaj mund të përdoret për leximin e atyre resurseve. Nga aspekti i punës në databazë, një INSERT do të krijojë një apo më tepër rekorde, ku secili rekord do të ketë ID unike, rëndomë si inkrement. Më pas, me SELECT mund të zgjedhim një apo më tepër rekorde për lexim nga ana e aplikacionit, të cilat aplikacioni më pas do t’i kthejë si të dhëna p.sh. të formatit JSON.
PUT
Nëse dëshirojmë që ta përditësojmë një resurs, gjegjësisht ta ndryshojmë vlerën e një apo më tepër fushave të një rekordi, për dërgimin e kërkesës do ta përdorim metodën PUT, e cila metodë është e ngjashme me POST, me atë se do ta përdorim vetëm në rastet kur në databazë duhet të ndodhë një UPDATE. Rëndomë, UPDATE kryhet ndaj një resursi, gjegjësisht ndaj një rekordi, por kemi edhe raste kur veprojmë ndaj një seti të rekordeve.
Të dhënat me POST/PUT/PATCH mund të dërgohen edhe thjesht në formatin gjenerik tekstual, por në rastin e REST API, këto të dhëna do të jenë zakonisht në formatin JSON.
PATCH
Dallimi ndërmjet PUT dhe PATCH qëndron në faktin se me PUT bëjmë zëvendësimin e vlerave të tërë një rekordi me të dhëna të reja, ndërkaq me PATCH do të bëjmë përditësimin e fushave të caktuara, duke ua ruajtur të tjerave përmbajtjen e mëparshme. Në praktikë, është zgjedhja e programuesit se si konkretisht do t’i përdorë këto dy metoda.
PUT dhe PATCH kthejnë status kodin 200 në rast të suksesit, 404 kur nuk gjendet resursi i kërkuar.
DELETE
Metoda DELETE përdoret për fshirjen e një resursi, gjegjësisht fshirjen e një rekordi nga databaza. Është i ngjashëm me PUT por nuk përmban asnjë informatë përveç identifikatorit unik të resursit i cili duhet të fshihet. Pasi të bëhet fshirja, kërkesat ndaj atij resursi me metodën GET duhet ta kthejnë statusin 404. Kërkesat me metodën GET nuk ruhen në cache.
OPTIONS
Metoda OPTIONS përdoret rrallë, ndërsa përdoret për të treguar se cilat metoda janë në dispozicion për të vepruar ndaj një resursi. Kështu aplikacioni mund të dijë cilat metoda janë në dispozicion ndaj një resursi dhe kështu fillimisht e verifikon nëse një metodë e caktuar ekziston, që më pas të bëjë kërkesën për ekzekutim.
Thënë ndryshme, para se aplikacioni të veprojë ta zëmë me metodën PATCH, duke e thirrur metodën OPTIONS mund të shikojë nëse metoda PATCH është në listën e metodave me të cilat mund të veprohet ndaj atij resursi.
REST API: Status kodet
Në përpunim e sipër
HTTP Request/Headers/Response
Në përpunim e sipër
Web app: Web Programming Basics
Në përpunim e sipër
Web app: Web aplikacioni bazik
Në përpunim e sipër
Web app: Dizajnimi i aplikacionit
Në përpunim e sipër
Web app: Databases
Në përpunim e sipër
Web app: Forms
Në përpunim e sipër
Web app: Upload
Në përpunim e sipër
Web app: Templates
Në përpunim e sipër
Web app: Autentikimi
Në përpunim e sipër
Web app: Files
Në përpunim e sipër
Web app: Routing
Në përpunim e sipër
Web app: Middleware
Në përpunim e sipër
REST API (JSON and XML)
Në përpunim e sipër
Web app: Unit testing
Në përpunim e sipër
Gorilla
Në përpunim e sipër
Referencë koncize e funksioneve
ioutil
ReadDir(dirname string) ([]os.FileInfo, error)
https://golang.org/pkg/io/ioutil/#ReadDir
Tregon listën e nëndirektoriumeve të direktoriumit të specifikuar.
WriteFile(filename string, data []byte, perm os.FileMode) error
https://golang.org/pkg/io/ioutil/#WriteFile
Funksion për shkrimin e përmbajtjes në fajll.
os
Chdir(dir string) error
https://golang.org/pkg/os/#Chdir
E bën aktual direktoriumin e cekur në parametër.
Chmod(name string, mode FileMode) error
https://golang.org/pkg/os/#Chmod
E ndryshon modin e fajllit të cekur në parametrin e parë në modin e cekur në parametrin e dytë.
Chown(name string, uid, gid int) error
https://golang.org/pkg/os/#Chown
E ndryshon uid dhe gid të fajllit të cekur në parametrin e parë.
Chtimes(name string, atime time.Time, mtime time.Time) error
https://golang.org/pkg/os/#Chtimes
Ndryshon kohët e aksesit dhe modifikimit të fajllit të cekur.
Clearenv()
I fshin të gjithë variablat e ambientit.
Environ() []string
https://golang.org/pkg/os/#Environ
Kthen kopjen e stringut që reprezenton ambientin, në formën “çelësi=vlera”.
Executable() (string, error)
https://golang.org/pkg/os/#Executable
Kthen shtegun e fajllit ekzekutues që ka filluar procesin aktual.
Exit(code int)
https://golang.org/pkg/os/#Exit
Bën ndërprerjen e menjëhershme të ekzekutimit të programit.
Expand(s string, mapping func(string) string) string
https://golang.org/pkg/os/#Expand
ExpandEnv(s string) string
https://golang.org/pkg/os/#ExpandEnv
Getegid() int
https://golang.org/pkg/os/#Getegid
Getenv(key string) string
https://golang.org/pkg/os/#Getenv
Lexon vlerën e variablit të ambientit sipas çelësit të cekur.
Geteuid() int
https://golang.org/pkg/os/#Geteuid
Getgid() int
https://golang.org/pkg/os/#Getgid
Getgroups() ([]int, error)
https://golang.org/pkg/os/#Getgroups
Getpagesize() int
https://golang.org/pkg/os/#Getpagesize
Getpid() int
https://golang.org/pkg/os/#Getpid
Getppid() int
https://golang.org/pkg/os/#Getppid
Getuid() int
https://golang.org/pkg/os/#Getuid
Getwd() (dir string, err error)
https://golang.org/pkg/os/#Getwd
E kthen shtegun e plotë të direktoriumit aktual.
Hostname() (name string, err error)
https://golang.org/pkg/os/#Hostname
Kthen emrin e hostit të raportuar nga kerneli.
IsExist(err error) bool
https://golang.org/pkg/os/#IsExist
IsNotExist(err error) bool
https://golang.org/pkg/os/#IsNotExist
IsPathSeparator(c uint8) bool
https://golang.org/pkg/os/#IsPathSeparator
IsPermission(err error) bool
https://golang.org/pkg/os/#IsPermission
IsTimeout(err error) bool
https://golang.org/pkg/os/#IsTimeout
Lchown(name string, uid, gid int) error
https://golang.org/pkg/os/#Lchown
Link(oldname, newname string) error
https://golang.org/pkg/os/#Link
LookupEnv(key string) (string, bool)
https://golang.org/pkg/os/#LookupEnv
Mkdir(name string, perm FileMode) error
https://golang.org/pkg/os/#Mkdir
MkdirAll(path string, perm FileMode) error
https://golang.org/pkg/os/#MkdirAll
NewSyscallError(syscall string, err error) error
https://golang.org/pkg/os/#NewSyscallError
Pipe() (r *File, w *File, err error)
https://golang.org/pkg/os/#Pipe
Readlink(name string) (string, error)
https://golang.org/pkg/os/#Readlink
Remove(name string) error
https://golang.org/pkg/os/#Remove
OpenFile(name string, flag int, perm FileMode) (*File, error)
https://golang.org/pkg/os/#OpenFile
Hapja e një fajlli për I/O operacione.
Pyetje
Variablat
- Tipi
rune
është alias për tipin: int8, int16, int32 apo int64? - Çfarë paraqet vlera e funksionit
len()
kur e përdorim me një string? - Deklarimi i variablit në formën
a := 5
bëhet: jashtë apo brenda një funksioni? - Variablit të deklaruar brenda strukturës
for
, a mund t’i qasemi jashtë strukturës?
Strukturat
- Ku përdoret urdhëri
fallthrough
? - Në cilat raste e përdorim urdhërin
goto
?
Përgjigjet
…
Shembuj
Shembuj të programeve në Golang.
Shembull: Krahasimi i dy vlerave të përafërta float
https://play.golang.org/p/u_1gGij1C3J
Rezultati:
Shembull: Argumentet nga konzola
https://play.golang.org/p/SRY8024ZgKC
Programi kompajlohet, startohet duke i dhënë disa argumente të ndara me space.
args 12 54 7 23 9
Rezultati:
Shembull. Leximi i vlerës nga konzola
https://play.golang.org/p/oFrQnPdFP-Y
Rezultati:
Shembull: Shuma e numrave të lexuara nga konzola
https://play.golang.org/p/195fJpQ-x1V
Rezultati:
Shembull: Strukti me metodë
https://play.golang.org/p/mSC5yid0ea3
Rezultati:
Nëse rezultatin e një funksioni nuk dëshirojmë ta lidhim me një mjet periferik dalës të caktuar, siç është rasti këtu me dërgimin e tekstit në konzolë, ne mund të bëjmë që një funksion të kthejë rezultat, e më pas thirrësi i atij funksioni (në këtë rast funksioni main()) të caktojë ku do të dërgohet: në konzolë, në databazë, si HTTP response, në fajll, etj.
https://play.golang.org/p/FOw4vowVtmk
Rezultati:
Shembull: Thirrja e metodës së struktit nga një funksion
https://play.golang.org/p/R83F_FaMDtE
Rezultati:
Shembull: Thirrja e një metode të struktit të brendshëm
https://play.golang.org/p/DNnFHzimbOj
Rezultati:
Shembull: Konvertimi i struktit në JSON
https://play.golang.org/p/azPg-eIDB7P
Rezultati:
Shembull: Strukt në JSON, JSON në strukt
https://play.golang.org/p/6J19r3TT7KP
Rezultati:
Shembull: Leximi i JSON fajllit me parsim me strukt
anetaret.json
https://play.golang.org/p/a5H7kzLvC_4
Rezultati:
Shembull: Maps - Qytetet dhe numri postar
https://play.golang.org/p/yBRomHWw_6L
Rezultati:
Shembull: Map me listë shtetesh dhe numër të banorëve
https://play.golang.org/p/F9nR6m6gSdy
Rezultati:
Mapën e njëjtë mund ta konvertojmë në JSON.
https://play.golang.org/p/wSf9iD-Xu2D
Aplikacioni thirret nga http://localhost:8080/lista
Rezultati:
Shembull: Bubble Sort
https://play.golang.org/p/T_igbGHGwL4
Rezultati:
Shembull: Fizzbuzz
https://play.golang.org/p/2u78tjXvVYP
Rezultati:
Shembull: Gjenerimi i sha256 hash
https://play.golang.org/p/sGbSxbLLTVS
Rezultati:
Shembull: Gjeneratori i stringut të rastësishëm
Të dy shembujt të testohen lokalisht sepse në Playground do të fitohen gjithmonë rezultate të njëjta.
https://play.golang.org/p/IAjuawbb-Cf
Rezultati:
Detyrë:
Të rishkruhet si funksion.
https://play.golang.org/p/5FsV-l1_dyJ
Rezultati:
Shembull: Fibonacci sekuenca
https://play.golang.org/p/mSt9Ezn5E7G
Rezultati:
Shembull: Numri prim
Ky program tregon numrin prim të caktuar, në rastin konkret numrin e 20-të, i cili është 71. Duke e ndryshuar vlerën e argumentit të funksionit Prime, mund ta zgjedhim cilindo numër tjetër prim sipas renditjes.
https://play.golang.org/p/sGsuM-Ujijs
Rezultati:
Shembull: Shkrimi dhe leximi nga fajlli i formatit CSV
https://play.golang.org/p/VUqfU1UwK73
Rezultati:
Shembull: Enkodimi/dekodimi i map në JSON
https://play.golang.org/p/8SF21flSep5
Rezultati:
Shembull: Nga strukt në XML
https://play.golang.org/p/dWlReYOqbhE
Rezultati:
Shembull: Enkodimi/dekodimi me base64
https://play.golang.org/p/HsbNHCEHuO0
Rezultati:
Shembull: Enkriptim/dekriptim me XOR
https://play.golang.org/p/jvpwzyzjsCl
Rezultati:
Shembull: Bitwise shift operator me enumerated constants
Në programin vijues, fillimisht formohet një listë konstantash nëpërmes vlerës speciale iota, e cila by default gjeneron serinë e vlerave: 0, 1, 2, 3,… Mirëpo, në këtë rast, ne do ta manipulojmë vlerën e iota me bitwise shift operators. Me këtë do ta fitojmë një seri prej: 0, 1, 2, 4, pra 0, 20, 21 dhe 22.
Qëllimi është që të fitohen vlera të kombinuara të 0, 1, 2 dhe 4, njëjtë sikurse e bën chmod në Linux. Kombinimin e dy vlerave në nivel të bitave do ta bëjmë me operatorin OR (|), me çka në fakt do të fitojmë numër.
Për shembull, operacioni OR ndaj Read (binar 100) dhe Write (binar 010), është 110, apo 6 në sistemin decimal. Kësisoj do të jemi në gjendje të ndërtojmë struktura degëzuese me if ose switch, ku do të mund t’i përdorim emrat e konstantave dhe operatorin OR për të verifikuar nëse përdoruesit i takon e drejta ekzekutimit, shkrimit apo leximit.
https://play.golang.org/p/KtQ-XWX4mBg
Rezultati:
Bitwise left shift për 1 pozitë e ngrit iota-n në eksponentin për një më të lartë të numrit 2. Pra, nëse vlera binare është 10, bitwise left shift e bën 100. Bitwise right shift e bën të kundërtën - p.sh. numrin binar 100 e kthen në 10.
Meqenëse iota me bitwise left shift fiton vlerë fillestare 1, ndërsa ne dëshirojmë të fillojë nga zeroja (p.sh. për përdoruesit që s’kanë asnjë privilegj), atëherë rezultatit të bitwise left shift i bëjmë bitwise right shift, gjë që do të mundësojë që vlera e parë e iota të jetë zero, por vlerat në vijim të jenë eksponente të numrit 2. Pra, kjo është arsyeja e përdorimit të shprehjes:
e cila në vend të serisë: 0, 1, 2, 3,…, që do të fitohej me:
apo serisë: 1, 2, 4, 8,…, që do të fitohej me:
na e gjeneron serinë: 0, 1, 2, 4,…, për çdo konstantë vijuese në listë.
Duke e përdorur serinë 0, 1, 2, 4, jemi të siguruar se nuk do të ketë dy kombinime me rezultat të njëjtë të dy apo tri vlerave të ndryshme, sepse në çfarëdo mënyre që t’i mbledhim këta numra, gjithmonë kemi rezultat unik për secilin kombinim.
ndërsa Go, operacionin canExecute|canRead në mënyrë interne do ta shohë si numër 6, por kjo për neve nuk ka rëndësi, sepse edhe krahasimin do ta bëjmë në mënyrë të njëjtë, pa iu referuar numrit konkret:
Shembull: ACL sistem me enumerated constants
https://play.golang.org/p/ukaaV5ggRdQ
Rezultati:
Me këtë program, do t’i shfrytëzojmë bitat e konstantave të enumeruara si flag për privilegje të caktuara:
Në rreshtin 16, variablit acl i japim vlerë asisoj që i cekim privilegjet e dëshiruara duke i ndarë me operatorin OR (|), me çka në fakt formohet një numër me bita të vlerës 1 në pozitat që i korrrespondojnë një privilegji të caktuar.
Kështu, për t’i caktuar një përdoruesi privilegjet rView | rDelete | rLogs | rStats formohet numri binar 01110001 apo 113 decimal.
Kur diku në aplikacion na duhet të verifikojmë nëse përdoruesi e ka NDONJËRIN prej privilegjeve, atëherë variablit që përmban privilegjet e tij (variabli Acl në këtë rast) duhet në njëfarë mënyre ta krahasojmë me privilegjin individual që na intereson, p.sh. a ka të drejtë ai përdorues të fshijë postime.
Për ta realizuar këtë, ndaj variablit Acl veprojmë me operatorin & dhe konstantën e privilegjit që na intereson, pastaj shikojmë nëse nuk është zero:
Çka në fakt ndodh këtu? Nëse variabli Acl përmban bit me vlerë 1 për privilegjin e fshirjes, edhe konstanta për privilegjin e fshirjes (rDelete) përmban bit me vlerë 1 në po atë pozitë.
Për Acl := rView | rDelete | rLogs | rStats
, vlera binare është 01110001
, ndërsa për rDelete është 00010000
. Shohim se biti i pestë nga e djathta ka vlerën 1 edhe te variabli Acl edhe te konstanta rDelete.
Meqenëse variabli Acl dhe konstanta rDelete në bitin e njëjtë kanë 1, rezultati nuk mund të jetë baras me zero; prandaj dhe verifikojmë nëse rezultati i shprehjes Acl&rDelete
nuk është zero:
Pra, me këtë shprehje konstatojmë nëse privilegji për fshirje është i përfshirë në privilegjet e caktuara për këtë përdorues.
Shprehjen e krahasimit e bartim në një funksion në vete, variablin Acl e deklarojmë jashtë blloqeve të funksioneve ashtu që të jetë i qasshëm për funksionet, dhe kështu e fitojmë një kod më elegant:
https://play.golang.org/p/_SFceaBThf2
Rezultati:
Shembull: Faqe dinamike, të dhënat nga databaza në template
https://play.golang.org/p/AVJ8l65uLpm
page.html
Rezultati (në browser):
Sqarim:
root:@tcp(127.0.0.1:3306)/markdown
Username është root, nuk ka password (pas dypikëshit), TCP protokol, hosti lokal 127.0.0.1, porti i MySQL 3306 emri i databazës është “markdown”
Shembull: Servimi dinamik i një aseti statik
Duke e hapur adresën:
do të shfaqet fotografia e cekur.
Shembull: Zbërthimi i një URL
https://play.golang.org/p/vftkwqxsS-L
Rezultati:
Shembull: Thirrja e funksionit anonim nëpërmes një variabli
https://play.golang.org/p/RYaTuySZ184
Rezultati:
Literatura
- The Go programming language phrasebook / David Chisnall. Copyright © 2012 Pearson Education, Inc
- Head First Go / Jay McGavren. Copyright © 2019 Jay McGavren.
- Go Programming by Example / Agus Kurniawan. Copyright © 2015 Agus Kurniawan
- Hands-On System Programming with Go / Alex Guerrieri. Copyright © 2019 Packt Publishing
- An Introduction to Programming in Go / Caleb Doxsey. Copyright © 2012 by Caleb Doxsey
- Go in Action / William Kennedy with Brian Ketelsen & Erik St. Martin. ©2016 by Manning Publications Co.
- Building Microservices with Go / Nic Jackson. Copyright © 2017 Packt Publishing
- Building RESTful Web Services with Go / Naren Yellavula. Copyright © 2017 Packt Publishing
- Build Web Application with Golang / ???
- Cloud Native Go / Kevin Hoffman & Dan Nemeth. Copyright © 2017 Pearson Education, Inc
- Concurrency in Go, Tools and Techniques for Developers / Katherine Cox-Buday. Copyright © 2017 Katherine Cox-Buday.
- Data Structures & Algorithms In Go / Hemant Jain. Copyright © Hemant Jain 2017
- Distributed Computing with Go / V.N. Nikhil Anurag. Copyright © 2018 Packt Publishing
- Go Programming Blueprints (Second Edition) / Mat Ryer. Copyright © 2016 Packt Publishing
- The Little Go Book / Karl Seguin.
- Programming in Go - Creating Applications for the 21st Century / Mark Summerfield. Copyright © 2012 Qtrac Ltd.
- Go 101 / Tapir Liu.
- Go Bootcamp / Matt Aimonetti.
- Go: Building Web Applications / Nathan Kozyra & Mat Ryer. Copyright © 2016 Packt Publishing
- Go Cookbook - Build modular, readable, and testable applications in Go / Aaron Torres. Copyright © 2017 Packt Publishing
- Go Design Patterns / Mario Castro Contreras. Copyright © 2017 Packt Publishing
- Go: Design Patterns for Real-World Projects. Copyright © 2017 Packt Publishing
- Go in Practice / Matt Butcher & Matt Farina Copyright © 2017 Packt Publishing
- Go Programming / John P. Baugh. © 2010 John P. Baugh
- Go Recipes: A Problem-Solution Approach / Shiju Varghese. Copyright © 2016 by Shiju Varghese
- Go Systems Programming - Master Linux and Unix system level programming with Go / Mihalis Tsoukalos. Copyright © 2017 Packt Publishing
- Go Recipes - A Problem-Solution Approach / Shiju Varghese. © Shiju Varghese 2016
- Go Web Programming / Sau Sheong Chang. ©2016 by Manning Publications Co.
- Introducing Go - Build Reliable, Scalable Programs / Caleb Doxsey. Copyright © 2016 Caleb Doxsey
- The Way to Go - A Thorough Introduction to the Go Programming Language / Ivo Balbaert . Copyright © 2012 by Ivo Balbaert
- Network Programming with Go - Essential Skills for Using and Securing Networks / Jan Newmarch. Copyright © 2017 by Jan Newmarch
- Learning Functional Programming in Go / Lex Sheehan. Copyright © 2017 Packt Publishing
- Learning Go / Miek Gieben. Copyright ©2010, 2011 Miek Gieben
- Learning Go Programming - An insightful guide to learning the Go programming language / Vladimir Vivien. Copyright © 2016 Packt Publishing
- Learning Go Web Development / Nathan Kozyra. Copyright © 2016 Packt Publishing
- Machine Learning With Go / Daniel Whitenack. Copyright © 2017 Packt Publishing
- Mastering Concurrency in Go / Nathan Kozyra. Copyright © 2014 Packt Publishing
- Mastering Go Web Services / Nathan Kozyra. Copyright © 2015 Packt Publishing
- Mastering Go - Create Golang production applications using network libraries, concurrency, and advanced Go data structures / Mihalis Tsoukalos. Copyright © 2018 Packt Publishing
- The Go Programming Language / Alan A. A. Donovan & Brian W. Kernighan. Copyright © 2016 Alan A. A. Donovan & Brian W. Kernighan
- Web Development with Go Learn to Create Real World Web Applications using Go / Jonathan Calhoun. Copyright © 2016 by Jon Calhoun
- Webapps in Go the anti textbook / Suraj Patil. © 2016 - 2019 Suraj Patil
- Go Standard Library Cookbook / Radomír Sohlich. Copyright © 2018 Packt Publishing
Resurse online
Dokumentacion
https://golang.org/
https://golang.org/doc/effective_go.html
Blog
Tutoriale
https://go101.org
https://gophercises.com/
Libra gratis
http://www.golangbootcamp.com/
https://www.openmymind.net/The-Little-Go-Book/
http://www.golang-book.com/
https://www.miek.nl/go/
https://github.com/pazams/go-for-javascript-developers
https://gobyexample.com
http://www.pazams.com/Go-for-Javascript-Developers
https://legacy.gitbook.com/download/pdf/book/codegangsta/building-web-apps-with-go
https://www.programming-books.io/essential/go
Forume
https://forum.golangbridge.org/
https://www.reddit.com/r/golang/