Talk about the programming language Go and the feature of generics. Those have been added to the language with the version 1.18. To have more control about allowed generics Go also got type constraints as careful extension of the interfaces. Get insight into this powerful extension and how it can help you.
4. • Generics sind ein Feature, welches die Nutzung von
Typenvariablen ermöglicht
• Sie erleichtern die Wiederverwendung von Quelltext
• Die Typsicherheit in statischen Sprachen wird dennoch
ermöglicht
• Je nach Sprache werden generische Funktionen, Strukturen und
Interfaces ermöglicht
• Die Lesbarkeit wird durch weniger Overhead im Code erhöht
Generics in Programmiersprachen Frank Müller
6. • Entwicklung bei Google seit Ende 2007, erstes Release
November 2009
• Initial durch Robert Griesemer, Rob Pike und Ken Thompson
• Heute große Community
• Fokus liegt auf Geschwindigkeit, Nebenläufigkeit und Spaß
• Bis heute ist Go abwärtskompatibel
• Einsatz rund um Cloud-Native und Microservices
Die Programmiersprache Go Frank Müller
7. ❞
Go hat das Ziel, die Sicherheit und
Geschwindigkeit einer statisch typisierten
kompilierten Sprache mit der Ausdrucksstärke
und dem Komfort einer dynamisch typisierten
interpretierten Sprache zu verbinden.
Zudem soll die Sprache für moderne,
stark skalierende Systeme
geeignet sein.
–Rob Pike
8. • Syntax passt auf eine HTML-Seite
• Klare und wenig mehrdeutige Struktur der Sprache
• Type Inference und Garbage Collection erlauben ein Arbeiten
ähnlich wie in vielen dynamisch typisierten Sprachen
• Nebenläufigkeit unterstützt elastische Server-Anwendungen
• Compiling ist sehr schnell, Single Binary und Cross Compiling
erleichtern die Arbeit in heterogenen Umgebungen
Was Spaß macht Frank Müller
10. • Doppelter Code für unterschiedliche Typen
‣ Z.B. StringSlice, IntSlice und Float64Slice im
Package sort
• Aufgabe der Typsicherheit
‣ Einsatz des Empty Interface interface{}
Go vor den Generics Frank Müller
11. • Type Assertions auf Empty Interfaces
‣ Risiko von Laufzeitfehlern
• Reflection auf Empty Interfaces
‣ Niedrigere Geschwindigkeit und Risiko von Laufzeitfehlern
• Code-Generierung mit Tools wie go generate
‣ Erhöhte Build-Zeit und Code-Komplexität
Workarounds Frank Müller
13. • Einführung mit Version 1.18
• Einsetzbar bei Funktionen, Structs und Interfaces
• Type Constraints erlauben Eingrenzung der erlaubten Typen
• So sind einfache, abgeleitete und komplexe Typen als Type
Constraints möglich
• Vorsichtige Erweiterungen der Syntax
Go mit Generics Frank Müller
14. •Ein oder mehrere Typen-
Parameter
•Typen-Parameter sind any,
comparable, Type Sets oder
Type Constraints
•Spezifikation bei der Nutzung
explizit oder implizit
Funktionen mit Generics – Allgemein Frank Müller
15. // Type constraint for numbers, constraints is "golang.org/exp/constraints".
type Number interface {
constraints.Integer | constraints.Float | constraints.Complex
}
// Add adds any two numbers of the same type.
func Add[N Number](i, j N) N {
return i+j
}
// Usage.
explicit := Add[byte](47, 11)
implicit := Add(47.11, 8.15)
Funktionen mit Generics – Beispiel Frank Müller
16. •Ein oder mehrere Typen-
Parameter
•Typen-Parameter sind bei
Nutzung zu spezifizieren
•Methoden müssen in der
Typenangabe die Anzahl der
Typen-Parameter wiederholen
Structs mit Generics – Allgemein Frank Müller
17. // Stack defines a simple generic stack data structure.
type Stack[T any] struct {
data []T
}
func (s *Stack[T]) Push(item T) int {
s.data = append(s.data, item)
return len(s.data)
}
// Usage.
s := &Stack[string]{} // Create empty string stack.
l := s.Push("Hello, JAX") // Returns 1
Structs mit Generics – Beispiel Frank Müller
18. •Ein oder mehrere Typen-
Parameter
•Implementierender Typ
benötigt die gleiche Typen-
Parameter
•Methoden in der
Implementierung analog zu
struct
Interfaces mit Generics – Allgemein Frank Müller
19. // KeyValueStore defines the interface to store keys with their values.
type KeyValueStore[K comparable, V any] interface {
Set(key K, value V) error
Get(key K) (V, error)
Delete(key K) (V, error)
}
// Map implements KeyValueStore in-memory using a map.
type Map[K comparable, V any] struct {
store map[K]V
}
func (m *Map[K, V]) Set(key K, value V) error { ... }
...
Interfaces mit Generics – Definition Frank Müller
20. // StoreDatas identifies and stores a number of data in any key/value store for
// string and *Data. The key is retrieved from the data with a helper.
func StoreDatas(kvs KeyValueStore[string, *Data], datas ...*Data) error {
for _, data := range datas {
if err := kvs.Put(GetKey(data), data); err != nil { return err }
}
return nil
}
// store uses the in-memory map to store its data.
store := NewMap[string, *Data]()
// Store takes lots of data to put it into the store.
err := StoreDatas(store, lotsOfData...)
Interfaces mit Generics – Nutzung Frank Müller
22. • Eingrenzung erlaubter Typen für Typen-Parameter
• Alternative in Type Sets werden über die Pipe (|) abgebildet
• Für abgeleitete Typen wird die Tilde (~) vorangestellt
• Type Constraints können kombiniert werden
• Im Package golang.org/exp/constraints sind bereits
einige Type Constraints definiert
Type Constraints Frank Müller
23. // Signed permits any signed integer types.
type Signed interface {
~int | ~int8 | ~int16 | ~int32 | ~int64
}
// Unsigned permits any unsigned integer types.
type Unsigned interface {
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}
// Integer permits any integer types. It just combines signed and unsigned
// integers.
type Integer interface {
Signed | Unsigned
}
Einfache Type Constraints Frank Müller
24. // Stringer is now a simple type constraint.
type Stringer interface {
String() string
}
// Scaler permits types which can scale scalable, which are type constraints too.
type Scaler[S Scalable] interface {
Scale(S) S
}
// NumberStringer permits any own number type able to return itself as string.
type NumberStringer interface {
Number
Stringer
}
Type Constraints mit Methoden Frank Müller
25. // Different roles in a system.
type Administrator struct { ... }
func (a *Administrator) ID() string
type User struct { ... } ...
type Guest struct { ... } ...
// Role permits one of the roles and allows to call its ID method.
type Role interface {
Administrator | User | Guest
ID() string
}
Semantische Type Constraints Frank Müller
27. • Typsichere Datenstrukturen wie Listen, Queues, Stacks, Sets
oder Trees, aber auch Caches
• Allgemeine Algorithmen wie Sort, Filter, Map oder Reduce
auf Strukturen wie Slices und Maps
• Generelle Hilfsfunktionen wie Swap
Einsatzmöglichkeiten von Generics Frank Müller
28. // Age implements NumberStringer.
type Age uint
func (a Age) String() string { return fmt.Sprintf("%d year(s)", a) }
// Add two numbers.
func Add[N Number](i, j N) N { return i+j }
// main now uses Age and Add.
func main() {
var i, j Age = 57, 10
k := Add(i, j)
fmt.Printf("Age: %v", k) // Output: "Age: 67 year(s)"
}
Einfaches Zusammenspiel Frank Müller
29. // FilterMap creates a slice from of new values created by fun() where
// it also returns true.
func FilterMap[I, O any](ivs []I, fun func(I) (O, bool)) []O {
var ovs []O
for _, iv := range ivs {
if ov, ok := fun(iv); ok {
ovs = append(ovs, ov)
}
}
return ovs
}
Arbeit mit Slices Frank Müller
30. // CacheID is a type alias for string making the meaning clearer.
type CacheID = string
// cacheItem is an internal wrapper for a cached data.
type cacheItem[D any] struct {
eol time.Time
data D
}
// Cache stores any data with a cache ID until not used for ttl.
type Cache[D any] struct {
mu sync.Mutex
ttl time.Duration
cache map[CacheID]*cacheItem[D]
}
Ein einfacher Cache – Typen Frank Müller
31. // NewCache creates a cache for any D with the given time to live.
func NewCache[D any](ttl time.Duration) *Cache[D] {
c := &Cache[D]{
ttl: ttl,
cache: make(map[string]*cacheItem[D]),
}
return c
}
Ein einfacher Cache – Constructor Frank Müller
32. // Put adds the given data with its ID to the cache.
func (c *Cache[D]) Put(id CacheID, data D) {
c.mu.Lock()
defer c.mu.Unlock()
ci := &cacheItem[D]{
eol: time.Now().Add(c.ttl),
data: data,
}
c.cache[id] = ci
}
Ein einfacher Cache – Put Data Frank Müller
33. // User is a user of the system.
type User struct { ... }
func (u *User) ID() string { ... }
// Create a new user cache.
userCache := NewCache[*User](30 * time.Minute)
// Create a user and put it in the cache.
pooh := NewUser("Pooh", "Bear")
userCache.Put(pooh.ID(), pooh)
Ein einfacher Cache – Nutzung Frank Müller
35. • Jedes zusätzliche Sprachmittel erhöht die Komplexität
• Die Lernkurve der Sprache hat sich mit Generics erhöht
• Go erlaubt kein Operator Overloading, also auch nicht für Type
Constraints
• Der Binärcode kann bei Generics unter Umständen nicht
optimiert werden
• Zu starker Einsatz kann übergroße Binaries erzeugen
Kritik und Einschränkungen Frank Müller
37. • Bedarf bei Code Duplications ermitteln und prüfen
• Schrittweise mit kleineren, geschlossenen Packages starten
• Allgemeine Hilfsfunktionen und Datenstrukturen überarbeiten
• Code kontinuierlich testen und Performance prüfen
• Monitoring der Anwendung
• Nicht übermäßig einsetzen und eventuell neue Fehler einführen
Schritt für Schritt Frank Müller
39. • Mit Version 1.18 erhielt Go Generics wie andere Sprachen zuvor
• Sie passen zum geradlinigen Stil der Sprache bis Version 1.17
• Erlaubte Typen können über Type Constraints definiert werden
• Die Notation der Interfaces wurde hierfür behutsam erweitert
• Generischer Code kann helfen, semantische Duplikate zu
vermeiden
• Übereifriger Einsatz sollte jedoch vermieden werden
Zusammenfassung Frank Müller