Introductie ---- Dit document is een introductie voor C en C++ programmeurs in de beginselen van de Go programmeer taal, Het is geen alles omvattende beschrijving van de taal; op dit moment komt alleen de language specification in de buurt daarvan. Als je deze tutorial hebt doorgelezen wil je misschien ook kijken naar Effective Go, die dieper in gaat op hoe je de taal moet gebruiken. Ook zijn de slides van een 3-daagse cursus beschikbaar: Dag 1, Dag 2, Dag 3. Dit document zal je door een aantal bescheiden programma's leiden om de sleutel eigenschappe van de taal Go te illustreren. Op het moment van schrijven zijn alle programma's uit te voeren en aanwezig in het Go repository in de directory "/doc/progs/". Alle programmaatjes worden getoond met regel nummers; voor de leesbaarheid zijn lege regels niet genummerd. Hallo, wereld ---- Laten we op de gewoonlijke manier beginnen: --PROG progs/helloworld.go /package/ END Elk Go source bestand declareert, met een "package" statement, van welke package het een onderdeel is. Het kan ook andere packages importeren en zo gebruiken maken van faciliteiten in die packages. Het programma hierboven importeert het package "fmt" om toegang te krijgen onze oude, nu met hoofdletter en een package-qualified, vriend, "fmt.Printf". Functies worden geïntroduceerd met het keyword "func". De functie "main" in het package "main" is de plek waar het programma begint met de executie (eventueel na wat initialisatie). String constanten kunnen Unicode karakters bevatten en zijn gecodeerd in UTF-8. (Go source files zelfs zijn gedefinieerd als zijnde UTF-8.) Commentaar geef je op dezelfde manier als in C++: /* ... */ // ... Verder op gaan we dieper in op printen. Punt komma's ---- Misschien heb je gemerkt dat ons programma geen punt komma's bevat. De enige plek waar je punt komma's gebruik in Go code is in for loops en vergelijkbare constructies; ze zijn niet nodig na elk statement. Wat er onder water gebeurd is dat de formeel gezien de taal wel punt komma's gebruikt, net zoals in C of Java, maar ze worden automatisch ingevoegd na elke regel die eruit ziet als het einde van een statement. Je zult ze niet meer zelf hoeven te typen. Voor de details over hoe dit werkt verwijzen we je naar de taal specificatie. In de praktijk is de enige kennis die je nodig hebt, dat je nooit een punt komma aan het einde van een regel nodig hebt. (Je kunt er wel één gebruiken als e meerdere statements op een regel zet.) Als extra hulp kun je een punt komma óók weglaten net voor een sluit accolade. Deze aanpak zorgt voor nette, punt komma-vrije code. De enige verassing is dat het belangrijk is om de openings accolade van constructies zoals de "if" statement op dezelfde regel te zetten als de "if"; als je dat niet doet kunnen er situaties ontstaan waarin de code niet compileert of het verkeerde resultaat geeft. De taal forceert deze accolade-stijl tot op zekere hoogte. Compileren ---- Go is een gecompileerde taal. Op dit moment zijn er twee compilers. "Gccgo" is een Go compiler met GCC als back end. Er is ook een setje compilers met andere (gekke) namen voor elke architectuur: "6g" voor 64-bit x86, "8g" voor de 32-bit x86, en meer. Deze compilers zijn significant sneller, maar genereren minder efficiënte code dan "gccgo". Op het moment van schrijven (eind 2009), hebben ze ook een meer robust run-time systeem, maar "gccgo" komt snel in de buurt. Op de volgende manier compileren wij ons programma, met "6g": $ 6g helloworld.go # compileer; object code komt in helloworld.6 $ 6l helloworld.6 # link; uitvoer komt in 6.out $ 6.out Hello, world; or Καλημέρα κόσμε; or こんにちは 世界 $ Met "gccgo" ziet het er wat traditioneler uit. $ gccgo helloworld.go $ a.out Hello, world; or Καλημέρα κόσμε; or こんにちは 世界 $ Echo ---- Nu dan een versie van de Unix utility "echo(1)": --PROG progs/echo.go /package/ END Dit is een klein programma, maar het doet een aantal nieuwe dingen. In het laatste voorbeeld zagen we "func" gebruikt worden om een nieuwe functie te definiëren. De keywords "var", "const", and "type" (nog niet gebruikt) introduceren ook declaraties, zo ook "import". Je kunt declaraties van dezeflde soort ook groeperen in een met haakjes omgeven lijst, waarin de elementen zijn gescheiden door puntkomma's, zie de regel 7-10 en 14-17. Het is niet verplicht dit zo te doen, we hadden namelijk ook kunnen zeggen const Space = " " const Newline = "\n" Puntkomma's zijn hier niet verplicht. In feite zijn puntkomma's helemaal overbodig in top-level declaraties, ook al zijn ze verplicht als scheider in lijsten van declaraties. XXX spec aangepast -- nakijken MG Je kunt puntkomma's op dezelfde manier gebruiken als in C, C++ of Java, maar je kunt ze ook vaak weglaten als dat meer je stijl is. Puntkomma's scheiden statements in plaats van ze af te sluiten. Ze zijn dus niet nodig (maar je mag ze wel gebruiken) aan het einde van het laatste statement in een blok. Na een accolade zijn ze optioneel, net als in C. Vergelijk de bron code van "echo". De echt nodige puntkomma's staan op regel 8, 15 en 21 en natuurlijk tussen de elementen van de "for" lus op regel 22. The puntkomma's op regels 9, 16, 26 en 31 zijn eigenlijk overbodig, maar we hebben ze wel toegevoegd om de lijst later makkelijker uit te kunnen breiden. Dit programma importeert de ""os"" package om toegang te krijgen tot zijn "Stdout" variabele van het type "*os.File". Het "import" statement is eigenlijk een declaratie; in zijn generieke vorm, zoals gebruik in ons ``hello world` programma declareert het een identifier ("fmt") dat gebruik gaat worden om toegang te krijgen tot members van het package van de file (""fmt""), gevonden in de huidige directory of in een standaard locatie. In dit programma gebruiken we niet de expliciete naam van de imports; standaard krijg je toegang tot een package via de naam die gedefinieerd wordt door het package dat je importeert. De conventie is dat dit de naam van de file is. In ons ``hello world`` programma hadden we net zo goed "import "fmt"" kunnen gebruiken. Je kunt altijd je eigen import namen gebruiken als je dat wilt, het is alleen echt nodig als je een conflict in de package namen moet oplossen. Met "os.Stdout" kunnen we "WriteString" methode gebruiken om de string te printen. Met het geïmporteerde "flag" package kunnen op regel 12 een globale variabele creëren die de waarde van echo's "-n" vlag krijgt. De variabele "omitNewline" heeft als type "*bool", een pointer naar "bool". In "main.main" parseren we de argumenten (regel 20) en creëer dan een lokale string variabele die we gebruiken om de output in op te bouwen. De declaratie ziet er als volgt uit var s string = ""; Hier gebruiken we het "var" keyword, gevolgd door de naam van de variabele met daarachter het type, gevolgd door een =-teken en de initiële waarde van de variabele. Go probeert kort en bondig te zijn en deze declaratie kan korter. Sinds een string constante als type string heeft, hoeven we dat de compiler niet nog een keer te vertellen, we kunnen ook schrijven var s = ""; of nog korter en dan krijgen we het volgende idioom s := ""; De ":=" operator wordt veel gebruikt in Go om tegelijkertijd te declareren en te initialiseren. In de "for" clausule de volgende regel wordt er ook eentje gebruikt. --PROG progs/echo.go /for/ Het "flag" package heeft de argumenten geparseerd en alle niet vlaggen staan nu in een lijst waar je op de gebruikelijke manier doorheen kunt lopen. Het "for" statement in Go verschilt op een aantal punten van dat in C. Ten eerste, het is de enige loop conxtructie; er is geen "while" of "do". Ten tweede zijn er geen haakjes meer bij de clausule en de accolades in de body zijn verplicht. Hetzelfde geldt voor "if" en "switch" statements. Later zullen we nog andere schrijfwijzen van "for" bekijken. De body van de loop bouwt de string "s" op door steeds op (door gebruik van "+=") van de vlaggen en de tussen spatie. Na de loop, als de "-n" vlag niet is gezet, zal het programma een regel overgang toevoegen. Op het eind wordt het resultaat geschreven. Met op dat "main.main" geen argumenten aan neemt (niladic) en ok geen return type heeft. Dit is de definitie. Het beïndigen van "main.main" betekent dat het programma succesvol is afgerond. Als je een foutmelding wilt terug geven, moet het os.Exit(1) aanroepen. Het "os" package bevat andere essentiële basis ingrediënten, zoals "os.Args" welke een slice is die gebruik wordt door het "flag" package om toegang te verkrijgen tot de argumenten die meegegeven zijn om de commando prompt. Een intermezzo over types ---- Go heeft de bekende types zoals "int" en "float", welke waarde representeren van "juiste" grootte van de machine. Verder wordt er types gedefinieerd van een expliciete grootte, zoals "int8", "float64", en zo verder. Ook zijn er types zonder teken zoals "uint", "uint32", etc. Dit zijn afzonderlijke types; zelfs als zijn "int" en "int32" allebei 32 bits groot, dan nog zijn als type verschillend. Er is ook een "byte", welke synoniem is voor "uint8", dit wordt gebruikt type in string elementen. Nu het toch of "string" hebben, dat is ook een ingebouwd type. String zijn immutable values— ze zijn niet zomaar een lijst van "byte" waardes. Als een string een waarde heeft gekregen, kan deze niet meer veranderd worden. Je kunt een string variabele wel een andere waarde geven door middel van een toewijzing. Het volgende fragment is legale code: --PROG progs/strings.go /hello/ /ciao/ De volgende statement zijn niet legaal omdat hier wel een "string" waarde in wordt veranderd: s[0] = 'x'; (*p)[1] = 'y'; In C++ termen gezegd, Go strings lijken een beetje op "const strings", terwijl pointers naar string analoog zijn aan "const string" referenties. Ja, er zijn pointers. Maar Go maakt het gebruik wat simpeler; lees verder. Arrays kunnen als volgt gedeclareerd worden: var arrayOfInt [10]int; Array, net als strings, zijn waardes, maar ze kunnen niet veranderd worden. Dit is anders dan in C, waar "arrayOfInt" bruikbaar is als pointer naar "int". In Go, waar array waardes zijn, is het betekenisvol (en bruikbaar) om te praten over pointers naar arrays. De grootte van een array is onderdeel van het type, maar je kunt een slice variabele declareren, aan welke je een pointer naar een willekeurige array kunt toewijzen met hetzeflde type or—iets dat veel vaker voorkomt— een slice expressie van de vorm "a[low : high]. Dit representeert een subarray geïndexeerd vanaf "low" tot en met "high-1". Slices lijken veel op array, maar hebben geen expliciete grootte ("[]" tegen. "[10]") en ze refereren naar een segment van de onderliggende, vaak anonieme, array. Verschillende slices kunnen dezelfde data delen als ze gemaakt zijn van dezelfde array. Verschillende slices kunnen nooit data delen. Slices worden veel vaker gebruik in Go programma's dan normal arrays; ze zijn meer flexibele, gebruiken referentie semantieken en zijn efficiënt. Wat ze niet hebben is de precieze controle over hoe hun geheugen layout eruit ziet, iets dat een normale array wel kan; als je honderd elementen van een array wilt gebruiken in een structure, dan zul je een normale array moeten gebruiken. Als je een array meegeeft aan een functie, zul je als formele functie declaratie een slice willen gebruiken. Wanneer je de functie aanroept, zal Go een slice referentie creëren en deze doorgeven. Met slices kun je de volgende functie schrijven (uit "sum.go"): --PROG progs/sum.go /sum/ /^}/ en deze als volgt aanroepen: --PROG progs/sum.go /1,2,3/ Zie hoe we het return type ("int") wordt gedefiniëerd voor "sum()" door dit na de parameter lijst op te geven. De expressie "[3]int{1,2,3}"—een type gevolgd bij een accolade omgeven expressie—is een constructor voor een waarde, in dit geval een array van 3 "ints". Met een "&" ervoor verkrijg je het adres van de unieke instantie van de waarde. We geven een pointer aan "sum()" door het (impliciet) te promoveren naar een slice. Als je een normale array creëert en je wilt dat de compiler voor jou telt hoeveel elementen je gebruikt, dan met "..." gebruiken als array grootte: s := sum(&[...]int{1,2,3}); In de praktijk kun je, tenzij je precies de geheugen layout in je structure wilt definiëren, de slice zelf—met blokhaken en geen "&"— is alles wat je nodig hebt. s := sum([]int{1,2,3}); Er zijn ook maps, die je als volgt kunt initialiseren: m := map[string]int{"one":1 , "two":2} De ingebouwde functie "len()", die aan het aantal elementen retourneerd, hebben als eerste gezien "sum". Het werkt op strings, arrays, slices, maps en channels. Een ander ding dat op strings, arrays, slices, maps en challens werkt is de "range" clausule voor "for" loops. In plaats van dat je schrijft for i := 0; i < len(a); i++ { ... } om over de elementen een slice (of map of ...) te lopen, kunnen we scrhijven for i, v := range a { ... } Dit wijst de index aan "i" toe en de waarde van de elementen van a aan "v". Zie Effective Go voor meer voorbeelden. Een intermezzo over allocatie ---- De meeste types in Go zijn waardes. Als je een "int" of een "struct" of een array hebt, zal een toewijzing de inhoud van het object kopiëren. Om een nieuwe variabele te alloceren, moet je "new()" gebruiken, welke een pointer terug geeft naar het gealloceerde geheugen. type T struct { a, b int } var t *T = new(T); of meer in lijn met het Go idioom t := new(T); Sommige types—maps, slices en challens (zie hieronder)—hebben een referentie semantiek. Als je een slice of map hebt en je modificeert hun inhoud, dan zullen andere variabelen die dezelfde data refereren de verandering ook zien. Voor deze drie types zul je de ingebouwde functie "make()" moeten gebruiken: m := make(map[string]int); Dit statement initialiseerd een nieuwe map, die meteen klaar is om items in op te slaan. Als je alleen een map wilt declaren, als in var m map[string]int; zal het een "nil" referentie creëen waar je niks in kunt opslaan. Om een map te kunnen gebruiken, moet je eerste de referentie initialiseren door middel van "make()" of door een al bestaande map toe te wijzen. Merk op dat "new(T)" een type "*T" retourneert, terwijl "make(T)" het type "T" terug geeft. Als je (per ongeluk) een referentie object alloceert met "new()", dan krijg j een pointer naar een nil referentie terug. Dit is vergelijkbaar met het declareren van een niet geïnitialiseerde variabele en daar het adres van opvragen. Een intermezzo over constanten ---- Ook al hebben we in Go integers van alle soorten en maten, van integer constanten hebben we er maar één. Er zijn geen constanten àla "0LL" or "0x0UL". In plaats daarvan worden integer constanten geëvalueerd als grote precisie waardes die alleen maar overlopen als ze worden toegewezen aan een integer variabele die te klein om de waarde te representeren. const hardEight = (1 << 100) >> 97 // legaal Er zijn nuances die een verwijzing naar de droge tekst in de Go taal specificatie verdienen, maar hier zijn wat veelzeggende voorbeelden: var a uint64 = 0 // a heeft type uint64, waarde 0 a := uint64(0) // idem; gebruikt een "conversion" i := 0x1234 // i krijgt standaard type: int var j int = 1e6 // legaal - 1000000 past in een int x := 1.5 // een float i3div2 := 3/2 // integer deling - resultaat is 1 f3div2 := 3./2. // floating point deling - resultaat is 1.5 Conversion werken alleen voor simpele gevallen zoals het converteren van "ints" die van teken wisselen of van grootte en tussen "ints" en "floats", plus nog een paar andere. Er is geen automatisch numerieke conversie van welke soort dan ook in Go, er is alleen de mogelijkheid van het bebbe. XXX beter. There are no automatic numeric conversions of any kind in Go, other than that of making constants have concrete size and type when assigned to a variable. Een I/O package ---- Nu gaan we kijken naar een simple package om bestands I/O mee te doen, we gebruiken de normale open/close/read/write interface. Dit is de start van "file.go": --PROG progs/file.go /package/ /^}/ De eerste regels declareren de naam van het package—"file"—en importeren twee package. Het "os" package schermt de verschillen tussen verschillende operating systemen voor ons af en geeft een consistente blik op files en meer; hier gebruiken we het voor zijn fout afhandeling functies en her-creeëren de fundering(XX rudiments) van zijn bestands I/O. Het andere item is het laag niveau, externe "syscall" package, welke door middel van een primitieve interface toegang geeft tot de onderliggende system calls van het operating system. Hierna volgt een type definitie: het "type" sleutelwoord introduceert een type declaratie. In dit geval een een structuur genaamd "File". Om het wat interessanter te maken, gebruikt "File" de de naam van het bestand waar de file descriptor naar toe wijst. Omdat "File" met een hoofdletter begint, wordt het type geëxporteerd en is het beschikbaar voor gebruik buiten het package. In Go geldt de volgende, simpele regel: als een naam (van een top-level type, functie, methode, constante of variabele, of een veld of methode van een structure) begint met een hoofdletter, dan kunnen gebruikers van het package deze naam zien. Anders is de naam en dus ook datgene dat word genoemd alleen zichtbaar in het package waar het wordt gedeclareerd. Dit is meer dan een conventie, deze regel wordt afgedwongen door de compiler. Als in Go iets publiekelijk zichtbaar is noemen we dat ''exported''. In het geval van "Flle" zijn alle beginnen alle velden met een kleine letter en zijn dus niet zichtbaar voor gebruikers. Straks zullen "File" wel velden met een hoofdletter (en dus geëxporteerd) geven. Eerst kijken we hoe we een nieuwe "File" moeten maken: --PROG progs/file.go /newFile/ /^}/ Dit retourneerd een pointer naar een nieuwe "File" structure met een file descriptor en de naam al ingevuld. Deze code gebruikt het Go idee van een ''composite literal'', welke analoog is aan die we gebruikt hebben om maps en arrays te maken; om op de head gealloceerde objecten te maken. We kunnen schrijven: n := new(File); n.fd = fd; n.name = name; return n maar voor simpele structuren zoals "File" is het makkelijker om het adres van een ''composite literal'' te retourneren, zoals we hier doen op regel 21. We kunnen dezelfde manier gebuiken om geëxporteerde variabelen van het type "*File" te maken: --PROG progs/file.go /var/ /^.$/ De "newFile" functie was niet geëxporteerd omdat deze intern is. De juiste, geëxporteerde manier is om "Open" te gebruiken: --PROG progs/file.go /func.Open/ /^}/ Er gebeuren een aantal nieuwe zaken in deze regels. Ten eerste, "Open" geretourneerd meerdere waardes, a "File" en een foutmelding (meer over foutmeldingen over een paar momenten). We declareren een multi-value return als door haakjes omgeven lijst van declaraties; syntactisch lijkt dit op een tweede paramater lijst. De functie "syscal.Open" retourneerd ook meerdere waardes. Deze kunnen we opvangen met de multi-variabele declaratie op regel 31; dit declareert "r" en "e" als twee "int" (daarvoor hebben we in het "syscall" package moeten spieken) waardes. Uiteindelijk, op regel 35, worden twee waardes gerouteneerd: een pointer naar de nieuwe "File" en de foutmelding. Als "syscall.Open" faalt, zaal de file descriptr "r" negatief zijn een "newFile" zal dan "nil" terug geven. Over die foutmeldingen gesproken: De "os" bibliotheek heeft een generiek begrip van een foutmelding. Het is een goed idee om deze faciliteit ook in je eigen interfaces te gebruiken, zoals we dat hier doen, voor consistente fout afhandling in Go code. In "Open" converteren we tussen de Unix's integer waarde "errno" en het integer typr "os.Errno", welke "os.Error" implemteerd. Omdat we nu "Files" kunnen maken, kunnen we ook methodes gaan schrijven. Om een methode voor een type te declareren, definiëren we een functie die expliciiet dat type ontvangt. Dit doen we door voor de functie naam het type tussen haakjes te benoemen. Hieronder staan wat methodes voor "*File", die elk een een ontvang (receiver) variable "file" declareren. --PROG progs/file.go /Close/ END Er is een impliciete "this" en de receiver variabele moet worden gebruik om toegang te verkrijgen tot leden van de structure. Methodes worden niet gedeclareerd in de "struct" definitie". De "struct" declaratie definiëert alleen de data leden. Nota Bene, methodes kunnen gedefiniëerd worden voor bijna elke type je kunt noemen, bijvoorbeeld een integer of een array, niet alleen voor "structs". We zullen later een voorbeeld hier van zien. There is no implicit "this" and the receiver variable must be used to access members of the structure. Methods are not declared within the "struct" declaration itself. The "struct" declaration defines only data members. In fact, methods can be created for almost any type you name, such as an integer or array, not just for "structs". We'll see an example with arrays later. De "String" methode is zo genoemd door een nog te beschrijven print conventie. De methodes gebruiken de publieke variable "os.EINVAL" om de waarde ("os.Error") te retourneren van de Unix fout code "EINVAL". The methods use the public variable "os.EINVAL" to return the ("os.Error" version of the) Unix error code "EINVAL". The "os" library defines a standard set of such error values. De "os" bibliotheek defniëert een standaard set van zulke foutcode waardes. We kunnen nu ons nieuwe package gebruiken: --PROG progs/helloworld3.go /package/ END De ''"./"'' in de import van ''"./file"'' vertelt de compiler om ons eigen package te gebruiken in plaats van de standaard geïnstalleerde packages. Nu kunnen we eindelijk ons programma uitvoeren: % helloworld3 hello, world can't open file; err=No such file or directory % Draaiende poezen ---- Verder bouwend op het "file" package, presentere we een simpele versie van het Unix programma "cat(1)", ""progs/cat.go": --PROG progs/cat.go /package/ END Deze code moet nu redelijk te volgen zijn, maar het "switch" statement introduceert wat nieuwe zaken. Net zoals n een "for" loop, kan een "if" of "switch" een initialisatie bevatten. De "switch" op regel 18 gebruikt er één om de variabelen "nr" en "er" te creëeren die de return waardes van "f.Read()" bevatten. (De "if" op regel 25 doet het zelfde.) Het "switch" statement is generiek: het evalueert alle cases van boven naar beneden en gaat op zoek naar de eerste die de waarde matched(X). De case expressies hoeven geeen constante of zelfs integers te zijn. De enige eis is dat het type van alles cases hetzelfde moet zijn. Omdat de "switch" waarde altijd "true" is, kunnen we die ook weg laten—dit is ook zo voor het "for" statement, een missende waarde betekent "true". Eigenlijk is het zo dat een "switch" een vorm is van een "if-else" ketting. En nu we dat toch noemen, kunnen we ook zeggen dat in een "switch" elke "case" een impliciete "break" heeft. Op regel 25 wordt "Write()" aangeroepen door een slice te nemen van de inkomende buffer. Deze buffer is zelf ook weer een slice. Slices zijn de standaard manier om in Go om te gaan met I/O buffers. Laten we nu een variant maken van "cat" die optioneel een "rot13" doet op zijn input. Dit kunnen we heel makkelijk doen door direct op de bytes te werken, maar laten we een kijken hoe we dat kunnen doen door middel van de Go notie vn een interface De "cat()" subroutine gebruikt maar twee methodes van "f": "Read()" en "String()", dus laten we beginnen met het definieëren van een interface die precies die twee methodes heeft. Dit is code van "progs/cat_rot13.go": --PROG progs/cat_rot13.go /type.reader/ /^}/ Van elk type dat de twee methodes van "reader"—los van welke andere methodes het type ook mag hebben—zeggen we dat het de interface implementeerd. Omdat "file.File" deze methodes implementeerd, voldoet het aan de "reader" interface. We kunnen de "cat" subroutine zo veranderen dat het een "reader" in plaats van een "*file.File" accepteerd en hij zal blijven werken, maar laten we hier wat verder gaan door een tweede type te maken dat "reader" implementeerd. Eentje die de bestaande "reader" omvat en een "rot13" om de data uitvoert. Om dit te doen, hoeven we alleen maar een type te definiëren en de methodes te maken, er is niets anders nodig. --PROG progs/cat_rot13.go /type.rotate13/ /end.of.rotate13/ (De "rot13" functie die op regel 42 aangeroepen wordt is triviaal en wordt hier niet weer gegeven.) Om onze nieuwe functie te gebruiken, definiëren we een vlag: --PROG progs/cat_rot13.go /rot13Flag/ en gebruiken deze in onze, bijna onveranderde "cat()" functie: --PROG progs/cat_rot13.go /func.cat/ /^}/ (We konden de wrapping ook in "main" doen een "cat()" met rust laten, behalve dan om het type van het argument te veranderen; dit kun je opvatten als een oefening voor de lezer.) Regel 56 tot en met 58 bereiden alles voor: Als de "rot13" vlag gezet is, dan pakken we "reader" in tot "rotate13" en gaan we verder. Merk op, de interface variabelen zijn waardes, geen pointers: het argument heeft als type "reader", niet "*reader", ook al is het onder water een pointer naar een "struct". Hier is het in actie;
	% echo abcdefghijklmnopqrstuvwxyz | ./cat
	abcdefghijklmnopqrstuvwxyz
	% echo abcdefghijklmnopqrstuvwxyz | ./cat --rot13
	nopqrstuvwxyzabcdefghijklm
	%
Als je houdt van "dependency injection" kun je hier blij worden over hoe makkelijk interfaces het ons maken om de implementatie van een file descriptor te veranderen. Interfaces zijn een onderscheidend onderdeel van Go. Een interface wordt geïmplementeerd door een type als het alle methodes implementeerd die gedeclareerd zijn door die interface. Dit houdt in dat een type een onbepaald aantal interfaces kan implementeren(X). Er is geen type hierarchy; dingen kunnen veel meer ad hoc worden geregeld, zoals we zien hebben in "rot13". Het type "file.File" implementeert "reader"; het kan ook "writer" implementeren, of welke andere interface dan ook die gebouwd wordt voor de huidige situatie(XX). Kijk bijvoorbeeld naar de lege interface, de empty interface
	type Empty interface {}
Elk type implementeert de lege interface. Dit maakt hem bruikbaar voor zaken als containers(X). Sorteren ---- Interfaces verzorgen een simpele vorm van polymorfisme. Ze scheiden de definitie van wat een object doet van hoe hij dat doet volledig. Ze maken het daarmee mogelijk om "distinct implementation" te implementeren die wordt gerepresenteerd door dezelfde interface variabele. Kijk als voorbeeld naar het volgende sorteer algoritme van "progs/sort.go": --PROG progs/sort.go /func.Sort/ /^}/ Deze code heeft maar drie methodes nodig, die allemaal in de "Interface" van sort zitten: --PROG progs/sort.go /interface/ /^}/ We kunnen "Sort" gebruiken voor elk type dat "Len", "Less" en "Swap" implementeert. Het "sort" package heeft alle nodige methodes om arrays van integers, string, etc. te kunnen sorteren. Hier volgt de code van het sorteren van een array van "int" --PROG progs/sort.go /type.*IntArray/ /Swap/ We zien hier methode defefiniëeerd worden voor niet-"struct" types. Je kunt methodes definiëren voor welk type en naam dan ook in je package. En nu een routine om en en ander uit te testen, deze komt uit "progs/sortmain.go". Hier wordt gebruik gemaakt van een functie uit het "sort" package, die hier niet is weergeven vanwege de lengte. --PROG progs/sortmain.go /func.ints/ /^}/ Als we nu een nieuw type hebben waarvan we ook willen dat we het kunnen sorteren, dan is het enige dat we hoeven te doen er voor te zorgen dat de drie methodes voor dit type zijn geïmplementeerd, als volgt bijvoorbeeld: --PROG progs/sortmain.go /type.day/ /Swap/ Printen ---- De voorbeelden voor geformatteerd printen zijn tot nu toe nogal bescheiden geweest. In deze sectie gaan we het hebben over hoe formatteerde output op in goede manier in Go kan worden verwezenlijkt. We hebben we een paar simpele voorbeelden gezien van het gebruik van het package "fmt", welke "Printf", "Fprintf, en nog meer geïmplementeerd. In het "fmt" package heeft "Printf" de volgende signatuur: Printf(format string, v ...) (n int, errno os.Error) De "..." representeert een variabele argumenten lijst, wat in C door middel van de "stdarg.h" macros wordt geregeld. In Go wordt gewoon een lege interface variable ("interface {}") doorgegeven, welke daarna wordt uitgepakt door het gebruik van de reflection bibliotheek. Het valt buiten de scope van dit artikel, maar het gebruik van de reflection bibliotheek verklaard een paar van de mooie eigenschappen van Go's "Printf", omdat het daarmee mogelijk wordt voor "Printf" om de types van zijn argumenten dynamisch te ontdekken. In C bijvoorbeeld moet elke formaat corresponderen met het type van zijn argument. In Go is het vaak makkelijker. In plaats van "%llud" kun je gewoon "%d" gebruiken; "Printf" weet wat de grootte en het teken van de integer is en zal het juiste doen. Het stukje --PROG progs/print.go 'NR==10' 'NR==11' geeft als uitvoer 18446744073709551615 -1 Als je lui bent zal het "%v" formaat, in een simpele stijl, voor een willekeurige waarde, zelf een array of structure de juiste uitvoer genereren. De uitvoer van --PROG progs/print.go 'NR==14' 'NR==17' is 18446744073709551615 {77 Sunset Strip} [1 2 3 4] Je kunt helemaal zonder formatting string als je "Print" of "Println" gebruiken in plaats van "Printf". Deze routines zullen de string automatisch voor je formatteren. De "Print" functie print elk element met een equivalent van "%v", terwijl "Println" spaties tussen de argumenten zet en afsluit met een regel overgang. De output van de volgende twee regels is identiek aan de "Printf" van hierboven. --PROG progs/print.go 'NR==18' 'NR==19' Als je een eigen type hebt die je wilt "Printf"-en of "Print"-en, hoe je hem alleen maar een "String()" methode te geven die een string terug geeft. De print routines kijken naar de waarde en zoeken uit of het die methode heeft. Als dat zo is zullen ze die gebruiken, in plaats van de standaard formattering. Hier is een simpel voorbeeld. --PROG progs/print_string.go 'NR==9' END Omdat "*testType" een "String()" methode heeft, zal de standaard formatteerder voor dat type het gebruiken en de volgende output genereren 77 Sunset Strip Merk op dat de "String()" methode "Sprint" aanroep (de voor de hand liggende Go variant die een string terug geeft) om voor hem de formattering te regelen; speciale formatteerders kunnen de "fmt" bibliotheek recursief aanroepen. Een andere eigenschap van "Printf" is dat het formaat "%T" een string representatie zal printen van het type van de waarde, dit kan handig zijn wanneer je polymorphe code aan het debugging bent. Het is mogelijk om een volledig aangepaste print met formats en vlaggen en precisie te schrijven, maar daarmee dwalen we een beetje af van het verhaal, dus we laten het als oefening aan de lezer over. Je kunt je afvragen how "Printf" weet welke type een "String()" methode implementeert. Wat er eigenlijk gebeurd is dat "Printf" vraagt of de waarde kan worden geconverteerd naar de interface variabele die de methode implementeert. Schematisch weergegeven, gebeurt het volgende: type Stringer interface { String() string } s, ok := v.(Stringer); // Test of v "String()" geïmplementeerd is if ok { result = s.String() } else { result = defaultOutput(v) } Deze code gebruikt een ``type assertion'' ("v.(Stringer)") om te testen of de waarde in "v" voldoet aan de "Stringer" interface; als die dat doet, dan zal "s" een interface variabele worden die de methode implementeerd en "ok" zal "true" zijn. Daarna gebruiken we de interface variabele om de methode aan te roepen. (Het ''komma, ok'' patroon is Go idioom die wordt gebruikt om te testen of het lukt om operaties zoals een type conversie, een map update, communicatie en zo verder uit te voeren. Vooralsnog gebruiken we het alleen op deze plek in deze introductie.) Als de waarde niet voldoet aan de interface, zal "ok" false zijn. In dit stukje code wordt de conventie gevolgd dat ''[e]r'' wordt toegevoegd aan interfaces die simpele methodes zoals deze beschrijven. "Stringer" is daar een voorbeeld van. Er moet nog één laatste kreuk worden glad gestreken. Om de hele suite te completeren, moeten we naast "Printf" etc. en "Sprinf" etc., ook aan "Fprintf" etc. denken. Anders dan in C, is het eerste argument voor "Fprintf" geen file. In plaats daarvan is het een variabele van het type "io.Writer", welke een interface type is, gedefiniëerd in de "io" bibliotheek: type Writer interface { Write(p []byte) (n int, err os.Error); } (Deze interface is een ander voorbeeld van een conventionele naam, deze keer voor "Write"; er bestaan ook nog "io.Reader", "io.ReadWriter", en zo verder.) Je kunt dus "Fprintf" aanroepen op elke type dat de standard "Write()" methode implementeerd, niet alleen files, maar ook netwerk channels, buffers, of wat anders. Priem getallen ---- Nu zijn we aangekomen bij processen en hun communicatie—parallel programmeren. Dit is een groot onderwerp, dus vertrouwen we op wat bekendheid met het onderwerp, zodat we het hier enigzins kort kunnen houden. Een klassiek programma in deze stijl is de priem zee. (De zeef van Eratosthenes is efficiënter dan het hier gepresenteerde algoritme, maar we zijn nu meer geïnteresseerd in parallelisme dan algoritmiek(X) op dit moment.) Het programma werkt door... It works by taking a stream of all the natural numbers and introducing a sequence of filters, one for each prime, to winnow the multiples of that prime. At each step we have a sequence of filters of the primes so far, and the next number to pop out is the next prime, which triggers the creation of the next filter in the chain. Hier is een stroom diagram; elke box representeert een filter element die pas gemaakt wordt wanneer het eerste nummer binnen komt van het element direct voor hem.(X) Here's a flow diagram; each box represents a filter element whose creation is triggered by the first number that flowed from the elements before it.
     
Om een stroom van integers te maken, gebruiken we een Go channel, die, gebruik makend van een CSP afstammeling, een communicatie kanaal representeerd, waarmee we kunnen connecten tussen twee parallele berekeningen. In Go, zijn channel variabelen referenties naar een run-time object dat de communicatie coördineerd; net zoals met maps aen slices moet je "make" gebruiken om een nieuwe channel te maken. Hier is de eerste functie uit "progs/sieve.go": --PROG progs/sieve.go /Send/ /^}/ The "generate" function sends the sequence 2, 3, 4, 5, ... to its argument channel, "ch", using the binary communications operator "<-". Channel operations block, so if there's no recipient for the value on "ch", the send operation will wait until one becomes available. De "filter" functie heeft drie argumenten: een invoer channel, een uitvoer channel en een priem getal. Het kopieert waardes van de invoer naar de outvoer, ondertussen alles waardes weggooiend die deelbaar zijn door het priem getal. De unaire communicatie operatie "<-" (ontvang recieve) haalt de volgende waarde op uit het channel. --PROG progs/sieve.go /Copy.the/ /^}/ De generator en de filters executeren tegelijk (concurrent). Go heeft zijn eigen model van processen/threads/licht gewicht processen/coroutines, om dus verwarring te voorkomen noemen concurrent uitvoerende functies in Go goroutines. Om een goroutine op te starten, hoef je alleen maar een functie aanroep vooraf te laten gaan door het keyword "go"; dit start de functie parallel op met de huidige berekening binnen de huidige adres ruimte. go sum(hugeArray); // bereken de som in de achtergrond Als je wilt weten hoe de berekening in zijn werk gaat, geef dan een channel waarmee het resultaat kan worden terug gerapporteerd: ch := make(chan int); go sum(hugeArray, ch); // ... do iets anders voor een tijdje result := <-ch; // wacht op het resultaat Terug naar onze priem zeef. Kijk hoe de priem zeef werkt: --PROG progs/sieve.go /func.main/ /^}/ Regel 29 creëert het eerste channel, welke aan "generate" wordt geven. Deze zal daarna starten. Elke keer als er een priem getal uit een channel komt, zal er een nieuw "filter" toegevoegd worden aan de pijplijn en zijn uitvoer wordt dan de nieuwe waarde van "ch". Het sieve programma kan nog verder veranderd worden door een standard patroon te gebruiken. Hier is een variant van "generate", van "progs/sieve1.go": --PROG progs/sieve1.go /func.generate/ /^}/ This version does all the setup internally. It creates the output channel, launches a goroutine running a function literal, and returns the channel to the caller. It is a factory for concurrent execution, starting the goroutine and returning its connection. The function literal notation (lines 12-16) allows us to construct an anonymous function and invoke it on the spot. Notice that the local variable "ch" is available to the function literal and lives on even after "generate" returns. Dezelfde verandering kan worden doorgevoerd in "filter" --PROG progs/sieve1.go /func.filter/ /^}/ De "sieve" functie z'n hoofd lus wordt nu simpeler en netter. The "sieve" function's main loop becomes simpler and clearer as a result, and while we're at it let's turn it into a factory too: --PROG progs/sieve1.go /func.sieve/ /^}/ Nu is de primaire interface van "main" naar de priem zeef een channel van priem getallen: --PROG progs/sieve1.go /func.main/ /^}/ Multiplexing ---- With channels, it's possible to serve multiple independent client goroutines without writing an explicit multiplexer. The trick is to send the server a channel in the message, which it will then use to reply to the original sender. A realistic client-server program is a lot of code, so here is a very simple substitute to illustrate the idea. It starts by defining a "request" type, which embeds a channel that will be used for the reply. --PROG progs/server.go /type.request/ /^}/ De server is triviaal: het voert een simpele binaire operatie uit op integers. Hier is de code die de operatie opstart en antwoord geeft op aanvragen: --PROG progs/server.go /type.binOp/ /^}/ Regel 14 definiëert de naam "binOp" als een functie die twee integers wil een derde terug geeft. De "server" routine loopt eeuwig, het ontvangt requests, en om te voorkomen dat het blocked, start het een goroutine om het eigenlijk werk op te knappen. --PROG progs/server.go /func.server/ /^}/ We bouwen de server in een vertrouwde manier op, starten hem en retourneren een channel die aan hem is gekoppeld: --PROG progs/server.go /func.startServer/ /^}/ Hier is een simpele test. We starten hier een server Here's a simple test. It starts a server with an addition operator and sends out "N" requests without waiting for the replies. Only after all the requests are sent does it check the results. --PROG progs/server.go /func.main/ /^}/ Een vervelende eigenschap van dit programma is dat het de server niet netjes afsluit; wanneer "main" retourneert, blijven er een paar goroutines achter die blokkeren wanneer ze willen communiceren. Om dit op te lossen geven we een tweede, "quit" channel aan de server: --PROG progs/server1.go /func.startServer/ /^}/ Dit geeft het quit channel naar de "server" functie, die het als volgt gebruikt: --PROG progs/server1.go /func.server/ /^}/ Binnen "server" zorgt het "select" statement ervoor.. Inside "server", the "select" statement chooses which of the multiple communications listed by its cases can proceed. If all are blocked, it waits until one can proceed; if multiple can proceed, it chooses one at random. In this instance, the "select" allows the server to honor requests until it receives a quit message, at which point it returns, terminating its execution. Alles wat nog te doen is, is het "quit" channel een signaal te geven aan het einde van main: --PROG progs/server1.go /adder,.quit/ ... --PROG progs/server1.go /quit....true/ Er is nog veel meer te ontdekken met Go en in parallel programmeren in het algemeen, maar deze kort rondleiding zou je enig inzicht in basis principes moeten geven.