Skip to content

Commit

Permalink
Generics talk
Browse files Browse the repository at this point in the history
  • Loading branch information
SchumacherFM committed Feb 4, 2022
1 parent 13cfdac commit e0a85c4
Show file tree
Hide file tree
Showing 7 changed files with 294 additions and 0 deletions.
124 changes: 124 additions & 0 deletions static/talks/2021-02-04-go-meetup.slide
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# The Globus BB Gophers Meetup

Cyrill Schumacher
Magazine zum Globus AG

## Welcome

Thank you for joining!

.image 2017-08-10/GoCommunity.png _ 700
.caption _Gopher_ by [[http://www.reneefrench.com][Renée French]]
.caption _Graphics_ by [[https://github.com/ashleymcnamara][Ashley McNamara]]

## Let's go!

.image 2017-08-10/Unicorn_Gopher.png _ 400
.caption _Gopher_ by [[http://www.reneefrench.com][Renée French]]
.caption _Graphics_ by [[https://github.com/ashleymcnamara][Ashley McNamara]]

## Go 1.18 main new features

- new support for generic code using parameterized types.
- adds built-in support for writing fuzzing-based tests, to automatically find
inputs that cause your program to crash or return invalid answers.
- adds a new “Go workspace mode”, which lets you work with multiple Go modules
simultaneously, an important use case for larger projects.
- contains an expanded go version -m command, which now records build details
such as compiler flags. A program can query its own build details using
debug.ReadBuildInfo, and it can now read build details from other binaries using
the new debug/buildinfo package. This functionality is meant to be the foundation
for any tool that needs to produce a software bill of materials (SBOM) for Go
binaries.

## Fuzzing

.link https://go.dev/blog/fuzz-beta
Fuzzing is a type of automated testing which continuously manipulates inputs to a
program to find issues such as panics or bugs. These semi-random data mutations
can discover new code coverage that existing unit tests may miss, and uncover
edge case bugs which would otherwise go unnoticed.

.code 2021-02-04/parse_fuzz.go


## Workspace mode

.link https://go.googlesource.com/proposal/+/master/design/45713-workspace.md
A new workspace mode in the go command for editing multiple modules has been
added. The presence of a go.work file in the working directory or a containing
directory will put the go command into workspace mode. The go.work file specifies
a set of local modules that comprise a workspace. When invoked in workspace mode,
the go command will always select these modules and a consistent set of
dependencies.

## Parameterized types

.link https://go.dev/doc/tutorial/generics

Generics are the most significant change to Go since the release of Go 1, and
certainly the largest single language change the Go team have ever made.

## Generics gotchas "return"

.play -edit -numbers 2021-02-04/generic_gotcha_return.go

## Generics gotchas "fmt" 1/2

.play -edit -numbers 2021-02-04/generic_gotcha_fmt.go

## Generics gotchas "fmt" 2/2

the go vet error results in:

$ go vet generic_gotcha_fmt.go
./generic_gotcha_fmt.go:12:20: fmt.Errorf format %q has arg key of wrong type K

Solution is...

## Generics Interface with type constraints (Problem)

.play -numbers 2021-02-04/generic_gotcha_interface_type_constraint01.go

## Generics Interface with type constraints (Solution)

.play -numbers 2021-02-04/generic_gotcha_interface_type_constraint02.go

## Unusual Generics

// Ptr is a simple function that helps to get literal pointer in one-line.
func Ptr[T any](v T) *T { return &v }

Invoked as:

pInt := unusual_generics.Ptr(10)
pStr := unusual_generics.Ptr("test")
pUint64 := unusual_generics.Ptr[uint64](24)
fmt.Printf("%[1]T %[1]d\n", *pInt)
fmt.Printf("%[1]T %[1]s\n", *pStr)
fmt.Printf("%[1]T %[1]d\n", *pUint64)
// Output:
// int 10
// string test
// uint64 24

Attention: future version Go can call directly `&10` or `&"test"`.

## Generic constraints for checks during compile time

.play -edit -numbers 2021-02-04/generic_avoidnil.go

## Generics: bad patterns

pointing out that:
```
func Something[R io.Reader](r R) error { ... }
```
is a useless use of generics and that is correctly written as:
```
func Something(r io.Reader) error { ... }
```

## When to use Generics?

Use them wisely where multiple types occur and not just one.
33 changes: 33 additions & 0 deletions static/talks/2021-02-04/generic_avoidnil.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package main

import (
"fmt"
"time"
)

// https://github.com/golang/go/issues/45346
// Section: Combining embedded non-interfaces with methods
type TimeLayoutProvider interface {
~struct{} // constrain base type to avoid runtime errors on zero-value method calls
TimeLayout() string
}

type TimeFormatted[T TimeLayoutProvider] time.Time

func (tf TimeFormatted[T]) MarshalText() ([]byte, error) {
var p T
t := time.Time(tf)
return []byte(t.Format(p.TimeLayout())), nil
}

type formatXYZProvider struct{}

func (formatXYZProvider) TimeLayout() string { return "2006 Jan _2 15:04:05 -0700" }

func main() {
now := time.Now()
//layout := TimeFormatted[formatXYZProvider](now)
tf := TimeFormatted[formatXYZProvider](now)
raw, _ := tf.MarshalText()
fmt.Println(string(raw))
}
20 changes: 20 additions & 0 deletions static/talks/2021-02-04/generic_gotcha_fmt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package main

import "fmt"

type FooBar[K comparable, V any] struct {
cache map[K]V
}

func (fb FooBar[K, V]) getKey(key K) (emptyVal V, _ error) {
val, ok := fb.cache[key]
if !ok {
return emptyVal, fmt.Errorf("key %q not found", key)
}
return val, nil
}
func main() {
fb := FooBar[int, []byte]{cache: make(map[int][]byte)} // OMIT
_, err := fb.getKey(3) // OMIT
fmt.Println(err.Error()) // OMIT
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package main

//OMIT
import "fmt"

//OMIT
func main() {
c := Car{Name: "Ferrari"}
Move(c, 100, 20)
}

//OMIT
// Subtractable is a type constraint that defines subtractable datatypes to be
// used in generic functions.
type Subtractable interface {
int | int32 | int64 | float32 | float64 | uint | uint32 | uint64
}

//OMIT
// Moveable is a interface that is used to handle many objects that are moveable
type Moveable interface {
Move(Subtractable)
}

//OMIT
// Car is a test struct for cars, implements Moveable
type Car struct{ Name string }

//OMIT
func (c Car) Move(meters Subtractable) { fmt.Printf("%s moved %d meters\n", c.Name, meters) }

//OMIT
// Move is a generic function that takes in a Moveable and moves it
func Move[V Moveable, D Subtractable](v V, distance D, meters D) D {
v.Move(meters)
return distance - meters
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package main

//OMIT
import "fmt"

//OMIT
func main() {
c := Car[int]{Name: "Ferrari"}
Move(c, 100, 20)
}

//OMIT
// Subtractable is a type constraint that defines subtractable datatypes to be
// used in generic functions.
type Subtractable interface {
~int | ~int32 | ~int64 | float32 | float64 | uint | uint32 | uint64
}

//OMIT
// Moveable is a interface that is used to handle many objects that are moveable
type Moveable[S Subtractable] interface {
Move(S)
}

//OMIT
// Car is a test struct for cars, implements Moveable
type Car[S Subtractable] struct{ Name string }

//OMIT
func (c Car[S]) Move(meters S) { fmt.Printf("%s moved %v meters\n", c.Name, meters) }

//OMIT
// Move is a generic function that takes in a Moveable and moves it
func Move[D Subtractable, V Moveable[D]](v V, distance D, meters D) D {
v.Move(meters)
return distance - meters
}
18 changes: 18 additions & 0 deletions static/talks/2021-02-04/generic_gotcha_return.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package main

import (
"errors"
)

type FooBar2[K comparable, V any] struct {
cache map[K]V
}

func (fb FooBar2[K, V]) getKey(key K) (V, error) {
val, ok := fb.cache[key]
if !ok {
return nil, errors.New("key not found")
}
return val, nil
}
func main() {}
25 changes: 25 additions & 0 deletions static/talks/2021-02-04/parse_fuzz.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package main // OMIT

import ( // OMIT
"net/url" // OMIT
"reflect" // OMIT
"testing" // OMIT
) // OMIT
// OMIT
func FuzzParseQuery(f *testing.F) {
f.Add("x=1&y=2")
f.Fuzz(func(t *testing.T, queryStr string) {
query, err := url.ParseQuery(queryStr)
if err != nil {
t.Skip()
}
queryStr2 := query.Encode()
query2, err := url.ParseQuery(queryStr2)
if err != nil {
t.Fatalf("ParseQuery failed to decode a valid encoded query %s: %v", queryStr2, err)
}
if !reflect.DeepEqual(query, query2) {
t.Errorf("ParseQuery gave different query after being encoded\nbefore: %v\nafter: %v", query, query2)
}
})
}

0 comments on commit e0a85c4

Please sign in to comment.