Edit: sorry dat dit wat rant'y is. Als je tevreden bent met Go is dat natuurlijk tof!
bart schreef: ↑ma jun 15, 2020 12:02 am
De laatste tijd doe ik een hoop programmeerwerk in go (golang). Dat programmeert lekker, overzichtelijk. Heeft een paar eigenaardigheidjes, naar over de hele linie gezien heel prettig. Kan mono niet aan tippen.
Ik heb ~2 jaar bijna al m'n programmeerwerk in Go gedaan (academische vrijheid). Had zelfs een
mild populaire binding voor Tensorflow. Ik kan de hele ervaring samenvatten als: meh. Maar ik zal positief beginnen: Go heeft een geweldige standaardbibliotheek, heel erg UNIXy, en bevat alle basisbehoeftes. En het compileert naar native code en gebruikt geen VM.
Go als taal is niet vreselijk, had je ook niet verwacht van de mensen die ondermeer achter Plan 9 zaten. Maar de Go mensen lijken ook circa 20-30 jaar in ontwikkeling in programmeertalen niet mee te hebben gekregen. Wat je krijgt is een taal dat heel erg op Java pre-generics lijkt, behalve dat het structural typing voor interfaces heeft en naar native binaries compileert. Als je Java 1.4 niks vond, dan vind je Go waarschijnlijk ook niks.
Go lost veel problemen niet op, Go heeft bijvoorbeeld nog steeds null pointers en heeft het zelfs erger gemaakt door meerdere soorten nil (null) pointers te introduceren (zeg maar fat en niet fat), waardoor je dit soort onzin krijgt:
Code: Selecteer alles
var a interface{} = nil
var b *int = nil
fmt.Println("a == b:", a == b) // Print a == b: false
Interoperabiliteit met C is matig. De overhead van C calls is heel erg groot (ondermeer wegens stack switching, andere calling conventions, extra checks). Dus even wat C functies aanroepen in een tight loops is er niet bij als performance belangrijk is. Verder is het heel beperkt wat je kunt doen in C interop. Je kunt bijvoorbeeld geen data die in Go is gealloceerd (dus niet met malloc) tussen cgo calls onderdeel maken van een C datastructuur (wat bijv. callbacks lastig maakt), omdat de Go garbage collector het geheugen zou kunnen reclaimen.
Go's 'claim to fame' is concurrent programming. Maar eigenlijk werkt dat alleen vrij redelijk als je channels gebruikt. Als je geen channels gebruikt val je gewoon weer terug op mutexes en omdat het typesysteem van Go heel erg beperkt is, voorkomt het geen data races. Channels hebben het probleem dat ze relatief langzaam zijn (vroegere Go packages vingen het gebrek van for-each style loops voor je eigen data types op door channels te gebruiken met het gevolg dat iteratie tergend langzaam was). Ook kun je met channels relatief eenvoudig
goroutines maken die blijven hangen.
Omdat Go geen generics ondersteunt zijn er een paar datastructuren die eersteklas burgers zijn, bijv. maps en slices. Die datastructuren hebben de luxe van for-each stijl loops, etc. Maar je kunt geen andere datastructuren implementeren die dezelfde luxe hebben, omdat Go geen generiek mechanisme heeft voor bijv. iterators. Ook loop je bijv. tegen problemen als dat je niet generiek met numerieke types kunt werken, waardoor veel bibliotheken bijv. alle functionaliteit apart voor 32-bit en 64-bit floating point types moet implementeren. Veel bibliotheken gebruiken daarom ad-hoc code generatie, etc. Verder eindigt veel 'generieke' code met het gebruik van
interface{} (zeg maar Go's
void *). Lelijk en vereist runtime introspection om de data later te kunnen gebruiken (yep, net als in pre-generics Java, toen alles een
Object werd en je ook achter moest uitvogelen met introspection wat je mee te maken had).
Go's compiler is gebaseerd op de Plan 9 C compiler, die op een gegeven moment semi-automatisch naar Go is geconverteerd. De compiler is geleidelijk wat beter geworden, maar ik heb een tijdje terug eens een paar uur gespendeerd aan het vergelijken van machine-code output van gcc, gc (de Go compiler), en rustc (Rust compiler met LLVM backend). Even los van het feit dat do Go compiler geen autovectorizatie doet, is de gegenereerde ronduit bedroevend. Vaak faalt basale escape analysis, vind er amper inlining plaats (waardoor allerlei andere optimalisaties ook niet mogelijk zijn), etc. Daarbij werden er ook nog opvallen vaak onnodige functieaanroepen in hot paths gedaan. Het gevolg is dat vaak code identiek herschrijven in C of Rust 2x-5x sneller is. En dan hebben we het nog niet over numerieke code ofzo, waar door beter optimalisatie + autovectorisatie het verschil nog veel groter is. Er is een gcc frontend, maar die geeft blijkbaar de gcc backend te weinig informatie, want in de praktijk produceert het geen snellere code dan gc.
Ik stop hier maar voordat het een te lang monoloog wordt (no pun intended
). Go is een best aardige taal voor UNIX dingen, met een mooie standaardbibibliotheek, een goed boek (The Go Programming Language van Donovan en Kernighan). Maar er zijn veel betere alternatieven.
---
Terug naar Mono. Mijn kritiek is op Mono eigenlijk precies het omgekeerde van mijn kritiek op Go. C# is een prima, moderne taal, wat anders zouden we van Anders Hejlsberg verwachten. Er zijn goede JIT en AOT compilers voor C# (waaronder Mono). Alleen net als bijv. Java is de standaardbibliotheek niet heel erg UNIXy. En je zeult (tenzij je AOT gebruikt) altijd een VM mee. En je krijgt DLLs op je filesystem. Bah, vies!
Als je een Windows developer bent en van Windows-conventies houdt, dan is er niets mis met C# en .NET.