Molte slide

main
Antonio De Lucreziis 2 years ago
parent c00e86d97d
commit d6d2f717df

Binary file not shown.

@ -430,6 +430,11 @@ type DatabaseTable[T any] struct {
GetIdPtr func(entry T) *string
}
type DatabaseTableColumn[T any, C any] struct {
Table string
ColumnName string
}
func (t DatabaseTable[T]) RefForId(id string) DatabaseRef[T] {
return DatabaseRef[T]{id}
}
@ -440,6 +445,7 @@ func (t DatabaseTable[T]) RefForValue(v T) DatabaseRef[T] {
func DatabaseRead[T any](db Database, table DatabaseTable[T], ref DatabaseRef[T]) (*T, error) {
query := fmt.Sprintf(`SELECT * FROM %s WHERE %s = ?`, table.Table, table.IdKey)
// SELECT * FROM users WHERE username = ?
result := db.Get(query, ref.Id)
@ -494,7 +500,7 @@ che potremo utilizzare ad esempio
user1 := &User{"j.smith", "John", "Smith"}
// ref1 :: DatabaseRef[User]
ref1, _ := DatabaseWrite(db, UsersTable, u1)
ref1, _ := DatabaseWrite(db, UsersTable, user1)
// user2 :: *User
user2, _ := DatabaseRead(db, UsersTable, ref1)

@ -4,3 +4,10 @@ Preview del post da cui è derivato il talk
[https://dev.to/aziis98/introduzione-alle-generics-in-go-15p-temp-slug-4472888](https://dev.to/aziis98/introduzione-alle-generics-in-go-15p-temp-slug-4472888?preview=973a2f7297ea145eed3f6655bc5f5d85f6215c621ff992e0ac60b2df691d1f65c4cf1a1d498c017abc4c2c540312ea989424865bcc5c7adb0f826f41)
## Usage
These slides are made using Marp, to preview them use
```go
$ marp -p slides.md
```

Binary file not shown.

Binary file not shown.

@ -0,0 +1,60 @@
package genericmethods_test
type Cons[L, R any] struct {
Left L
Right L
}
// interface cast type check
func _[T any]() LiftLower[BoxT, T] { return &Box[T]{} }
type BoxT struct{}
type Box[T any] struct {
Content T
}
func (c Box[T]) Lift() V[BoxT, T] {
return c
}
func (c *Box[T]) Lower(v V[BoxT, T]) {
*c = v.(Box[T])
}
// interface cast type check
func _[T any]() LiftLower[ChestT, T] { return &Chest[T]{} }
type ChestT struct{}
type Chest[T any] struct {
Treasure T
}
func (c Chest[T]) Lift() V[ChestT, T] {
return c
}
func (c *Chest[T]) Lower(v V[ChestT, T]) {
*c = v.(Chest[T])
}
type LiftLower[F ~struct{}, T any] interface {
Lift() V[F, T]
Lower(v V[F, T])
}
func BoxToChest[T any](b Box[T]) Chest[T] {
return Chest[T]{b.Content}
}
func BoxToChestLifted[T any](b V[BoxT, T]) V[ChestT, T] {
var bb Box[T]
bb.Lower(b)
return Chest[T]{bb.Content}.Lift()
}
type V[F ~struct{}, T any] any
type ConsF[F, T, R any] struct{}
func _() {
// l1 := Cons[Box[int], Box[string]]{}
}

@ -0,0 +1,134 @@
package genericmethods_test
import (
"database/sql"
"encoding/json"
"fmt"
"net/http"
"strings"
"testing"
)
type Validator interface {
Validate() error
}
type FooRequest struct {
A int `json:"a"`
B string `json:"b"`
}
func (foo FooRequest) Validate() error {
if foo.A < 0 {
return fmt.Errorf(`parameter "a" cannot be lesser than zero`)
}
if !strings.HasPrefix(foo.B, "baz-") {
return fmt.Errorf(`parameter "b" has wrong prefix`)
}
return nil
}
func DecodeAndValidateJSON_Generic[T Validator](r *http.Request) (T, error) {
var value T
err := json.NewDecoder(r.Body).Decode(&value)
if err != nil {
var zero T
return zero, err
}
if err := value.Validate(); err != nil {
var zero T
return zero, err
}
return value, nil
}
func TestDecodeAndValidateJSON_Generic(t *testing.T) {
m := http.NewServeMux()
m.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) {
foo, err := DecodeAndValidateJSON_Generic[FooRequest](r)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(foo)
})
}
func DecodeAndValidateJSON_Interface(r *http.Request, target *Validator) error {
err := json.NewDecoder(r.Body).Decode(target)
if err != nil {
return err
}
if err := (*target).Validate(); err != nil {
return err
}
return nil
}
func TestDecodeAndValidateJSON_Interface(t *testing.T) {
m := http.NewServeMux()
m.HandleFunc("/foo", func(w http.ResponseWriter, r *http.Request) {
var foo Validator = FooRequest{}
if err := DecodeAndValidateJSON_Interface(r, &foo); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
json.NewEncoder(w).Encode(foo)
})
}
type WithPrimaryKey interface {
PrimaryKeyPtr() *string
}
type Ref[T WithPrimaryKey] string
type Table[T WithPrimaryKey] struct {
Name string
PkColumn string
}
func IdToRef[T WithPrimaryKey](table Table[T], id string) (Ref[T], error) {
if !strings.HasPrefix(id, table.Name+":") {
var zero Ref[T]
return zero, fmt.Errorf(`invalid reference %v for table %v`, id, table)
}
return Ref[T](id), nil
}
func Read[T WithPrimaryKey](db *sql.DB, table Table[T], ref Ref[T]) (*T, error) {
result := db.QueryRow(
fmt.Sprintf(
`SELECT * FROM %s WHERE %s = ?`,
table.Name, table.PkColumn,
),
string(ref),
)
var value T
if err := result.Scan(&value); err != nil {
return nil, err
}
return &value, nil
}
type User struct {
Username string
FirstName string
LastName string
}
var _ WithPrimaryKey = &User{}
func (u *User) PrimaryKeyPtr() *string {
return &u.Username
}

@ -1,55 +0,0 @@
package main
import (
"log"
"golang.org/x/exp/constraints"
)
//
// The old way
//
func MinInt(x, y int) int {
if x < y {
return x
}
return y
}
func MinInt32(x, y int32) int32 {
if x < y {
return x
}
return y
}
func MinInt64(x, y int64) int64 {
if x < y {
return x
}
return y
}
func MinFloat32(x, y float32) float32 {
if x < y {
return x
}
return y
}
//
// Generics
//
func Min[T constraints.Ordered](x, y T) T {
if x < y {
return x
}
return y
}
func main() {
shortMin := Min[int16] // func(int16, int16) int16
log.Printf(`min(2, 3) = %v`, shortMin(2, 3))
}

@ -0,0 +1,31 @@
package genericmethods_test
import (
"testing"
"golang.org/x/exp/constraints"
)
func Min[T constraints.Ordered](x, y T) T {
if x < y {
return x
}
return y
}
type Liter int
func TestInt(t *testing.T) {
{
var a, b int = 1, 2
Min(a, b)
}
{
var a, b float64 = 3.14, 2.71
Min(a, b)
}
{
var a, b Liter = 1, 2
Min(a, b)
}
}

@ -0,0 +1,29 @@
package genericmethods_test
type Stack[T any] []T
func (s *Stack[T]) Push(value T) {
*s = append(*s, value)
}
func (s Stack[T]) Peek() T {
return s[len(s)-1]
}
func (s Stack[T]) Len() int {
return len(s)
}
func (s *Stack[T]) Pop() (T, bool) {
items := *s
if len(items) == 0 {
var zero T
return zero, false
}
newStack, poppedValue := items[:len(items)-1], items[len(items)-1]
*s = newStack
return poppedValue, true
}

@ -0,0 +1,61 @@
package example5_test
import (
"bytes"
"io"
"testing"
)
func WriteSingleWithInterface(w io.Writer, data byte) {
w.Write([]byte{data})
}
//go:noinline
func WriteSingleWithInterfaceNoInline(w io.Writer, data byte) {
w.Write([]byte{data})
}
func WriteSingleWithGeneric[T io.Writer](w T, data byte) {
w.Write([]byte{data})
}
//go:noinline
func WriteSingleWithGenericNoInline[T io.Writer](w T, data byte) {
w.Write([]byte{data})
}
func Test1(t *testing.T) {
t.Log(`Ok!`)
}
func BenchmarkInterface(b *testing.B) {
d := &bytes.Buffer{}
for i := 0; i < b.N; i++ {
WriteSingleWithInterface(d, 42)
}
}
func BenchmarkInterfaceNoInline(b *testing.B) {
d := &bytes.Buffer{}
for i := 0; i < b.N; i++ {
WriteSingleWithInterfaceNoInline(d, 42)
}
}
func BenchmarkGeneric(b *testing.B) {
d := &bytes.Buffer{}
for i := 0; i < b.N; i++ {
WriteSingleWithGeneric(d, 42)
}
}
func BenchmarkGenericNoInline(b *testing.B) {
d := &bytes.Buffer{}
for i := 0; i < b.N; i++ {
WriteSingleWithGenericNoInline(d, 42)
}
}

@ -0,0 +1,31 @@
package main
import (
"fmt"
"golang.org/x/exp/constraints"
)
func Min[T constraints.Ordered](x, y T) T {
if x < y {
return x
}
return y
}
type Liter int
func main() {
{
var a, b int = 1, 2
fmt.Println(Min(a, b))
}
{
var a, b float64 = 3.14, 2.71
fmt.Println(Min(a, b))
}
{
var a, b Liter = 1, 2
fmt.Println(Min(a, b))
}
}

@ -0,0 +1,50 @@
package main
import (
"database/sql"
"fmt"
)
type IdTable interface {
PrimaryKey() *string
Columns() []any
}
type Ref[T IdTable] string
type Table[T IdTable] struct {
Name string
PkColumn string
// Columns []string
}
type DB = *sql.DB
func Create[T IdTable](d DB, t Table[T], row T) (Ref[T], error) {
var zero Ref[T]
return zero, nil
}
func Insert[T IdTable](d DB, t Table[T], row T) (Ref[T], error) {
var zero Ref[T]
return zero, nil
}
func Read[T IdTable](d DB, t Table[T], ref Ref[T]) (*T, error) {
result := d.QueryRow(fmt.Sprintf(`SELECT * FROM %s WHERE %s = ?`, t.Name, t.PkColumn), string(ref))
var value T
if err := result.Scan(value.Columns()...); err != nil {
return nil, err
}
return &value, nil
}
func Update[T IdTable](d DB, t Table[T], row T) error {
return nil
}
func Delete[T IdTable](d DB, t Table[T], id string) error {
return nil
}

@ -0,0 +1,7 @@
{
"scripts": {
"dev": "slidev",
"build": "slidev build",
"export": "slidev export"
}
}

@ -6,14 +6,35 @@ size: 4:3
<style>
:root {
font-family: 'Inter', sans-serif;
font-family: 'Open Sans', sans-serif;
font-size: 175%;
letter-spacing: 1px;
color: #222;
}
section.chapter {
background: #00acd7;
color: #ecfbff;
}
code {
font-size: 100%;
line-height: 1.4;
border-radius: 4px;
}
code.language-go {
font-family: 'Go Mono', monospace;
}
section.chapter code {
background: #00809f;
color: #ecfbff;
}
@import "https://unpkg.com/@highlightjs/cdn-assets@11.7.0/styles/github.min.css";
</style>
<!-- _class: chapter -->
@ -22,8 +43,6 @@ section.chapter {
---
## Chi sono?
Antonio De Lucreziis, studente di Matematica e macchinista del PHC
@ -36,36 +55,81 @@ Il PHC è un gruppo di studenti di Matematica con interessi per, open source, Li
_The Go 1.18 release adds support for generics. Generics are the biggest change weve made to Go since the first open source release_
&nbsp;
Fonte: https://go.dev/blog/intro-generics
---
Ad esempio una delle grandi mancanze della _stdlib_ del Go è stata l'assenza delle funzioni `Min(x, y)` e `Max(x, y)`.
## Il Problema
Non c'è mai stati modo di definirle in modo generico.
---
<style scoped>
code { font-size: 150% }
</style>
```go
func MinInt(x, y int) int {
func Min(x, y int) int {
if x < y {
return x
}
return y
}
```
---
```go
func MinInt8(x, y int8) int8 {
if x < y {
return x
}
return y
}
func MinInt16(x, y int16) int8 {
if x < y {
return x
}
return y
}
func MinFloat32(x, y float32) float32 {
if x < y {
return x
}
return y
}
```
---
Invece ora con le nuove generics possiamo scrivere
<style scoped>
code { font-size: 150% }
</style>
```go
...
if x < y {
return x
}
return y
...
```
---
## La Soluzione
---
#### Type Parameters & Type Sets
```go
import "golang.org/x/exp/constraints"
@ -78,11 +142,41 @@ func Min[T constraints.Ordered](x, y T) T {
}
```
#### Type Inference
```go
var a, b int = 0, 1
Min(a, b)
```
```go
var a, b float32 = 3.14, 2.71
Min(a, b)
```
---
Cos'è `constraints.Ordered`?
<style scoped>
code { font-size: 150% }
</style>
```go
func Min[T constraints.Ordered](x, y T) T {
if x < y {
return x
}
return y
}
```
---
```go
package constraints
...
type Ordered interface {
Integer | Float | ~string
}
@ -102,13 +196,576 @@ type Signed interface {
type Unsigned interface {
~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
}
...
```
<!-- Le interfacce possono introdurre questo type-set per limitare i tipi a cui vengono applicate, l'unico tipo che non possiamo utilizzare nei type-sets sono le interfacce con metodi. -->
---
```go
type Liter float64
type Meter float64
type Kilogram float64
```
---
<style scoped>
code { font-size: 150% }
</style>
## Tipi Generici
```go
type Stack[T interface{}] []T
```
<!-- In realtà non serve usare "interface{}" tutte le volte -->
---
<style scoped>
code { font-size: 150% }
</style>
## Tipi Generici
```go
type Stack[T any] []T
```
---
```go
func (s *Stack[T]) Push(value T) {
*s = append(*s, value)
}
func (s Stack[T]) Peek() T {
return s[len(s)-1]
}
func (s Stack[T]) Len() int {
return len(s)
}
```
---
# Prova
```go
func (s *Stack[T]) Pop() (T, bool) {
items := *s
if len(items) == 0 {
var zero T
return zero, false
}
newStack, poppedValue := items[:len(items)-1], items[len(items)-1]
*s = newStack
return poppedValue, true
}
```
---
Lorem ipsum dolor sit amet consectetur adipisicing elit. Perferendis, voluptas. Doloribus ea consectetur fugiat quaerat eum magni eos earum placeat dolorum. Nesciunt nostrum tenetur magnam facere magni sapiente illo pariatur?
```go
func Zero[T any]() T {
var zero T
return zero
}
```
---
<!-- _class: chapter -->
# Pattern (1)
Quando usare le generics?
---
### Tipi "Contenitore"
- `[n]T`
Array di `n` elementi per il tipo `T`
- `[]T`
Slice per il tipo `T`
- `map[K]V`
Mappe con chiavi `K` e valori `V`
- `chan T`
Canali per elementi di tipo `T`
---
In Go sono sempre esistite queste strutture dati "generiche"
Solo che prima delle generics non era possibile definire algoritmi generali per questi tipi di container, ora invece possiamo ed infatti alcune di questi sono "già in prova"
---
<style scoped>
section {
font-size: 140%;
line-height: 1.75;
}
</style>
## `golang.org/x/exp/slices`
- `func Index[E comparable](s []E, v E) int`
- `func Equal[E comparable](s1, s2 []E) bool`
- `func Sort[E constraints.Ordered](x []E)`
- `func SortFunc[E any](x []E, less func(a, b E) bool)`
- e molte altre...
---
<style scoped>
section {
font-size: 140%;
line-height: 1.75;
}
</style>
## `golang.org/x/exp/maps`
- `func Keys[M ~map[K]V, K comparable, V any](m M) []K`
- `func Values[M ~map[K]V, K comparable, V any](m M) []V`
- e molte altre...
---
<style scoped>
section {
font-size: 140%;
line-height: 1.75;
}
</style>
## Strutture Dati Generiche
Esempio notevole: <https://github.com/zyedidia/generic> (1K:star: su GitHub)
- `mapset.Set[T comparable]`, set basato su un dizionario.
- `multimap.MultiMap[K, V]`, dizionario con anche più di un valore per chiave.
- `stack.Stack[T]`, slice ma con un'interfaccia più simpatica rispetto al modo idiomatico del Go.
- `cache.Cache[K comparable, V any]`, dizionario basato su `map[K]V` con una taglia massima e rimuove gli elementi usando la strategia LRU.
- `bimap.Bimap[K, V comparable]`, dizionario bi-direzionale.
- `hashmap.Map[K, V any]`, implementazione alternativa di `map[K]V` con supporto per _copy-on-write_.
- e molte altre...
---
<!-- _class: chapter -->
# Anti-Pattern (1)
Generics vs Interfacce
---
## Momento Quiz
```go
func WriteOneByte(w io.Writer, data byte) {
w.Write([]byte{data})
}
...
d := &bytes.Buffer{}
WriteOneByte(d, 42)
```
```go
func WriteOneByte[T io.Writer](w T, data byte) {
w.Write([]byte{data})
}
...
d := &bytes.Buffer{}
WriteOneByte(d, 42)
```
---
```
BenchmarkInterface
BenchmarkInterface-4 135735110 9.017 ns/op
BenchmarkGeneric
BenchmarkGeneric-4 50947912 22.26 ns/op
```
---
```go
//go:noinline
func WriteOneByte(w io.Writer, data byte) {
w.Write([]byte{data})
}
...
d := &bytes.Buffer{}
WriteOneByte(d, 42)
```
---
```
BenchmarkInterface
BenchmarkInterface-4 135735110 9.017 ns/op
BenchmarkInterfaceNoInline
BenchmarkInterfaceNoInline-4 46183813 23.64 ns/op
BenchmarkGeneric
BenchmarkGeneric-4 50947912 22.26 ns/op
```
---
```go
d := &bytes.Buffer{} /* (*bytes.Buffer) */
WriteOneByte(d /* (io.Writer) */, 42)
```
```go
d := &bytes.Buffer{} /* (*bytes.Buffer) */
(io.Writer).Write(d /* (io.Writer) */, []byte{ 42 })
```
```go
d := &bytes.Buffer{} /* (*bytes.Buffer) */
(*bytes.Buffer).Write(d /* (*bytes.Buffer) */, []byte{ 42 })
```
---
<!-- _class: chapter -->
# Anti-Pattern (2)
Utility HTTP
---
```go
type Validator interface {
Validate() error
}
func DecodeAndValidateJSON[T Validator](r *http.Request) (T, error) {
var value T
err := json.NewDecoder(r.Body).Decode(&value)
if err != nil {
var zero T
return zero, err
}
if err := value.Validate(); err != nil {
var zero T
return zero, err
}
return value, nil
}
```
---
```go
type FooRequest struct {
A int `json:"a"`
B string `json:"b"`
}
func (foo FooRequest) Validate() error {
if foo.A < 0 {
return fmt.Errorf(`parameter "a" cannot be lesser than zero`)
}
if !strings.HasPrefix(foo.B, "baz-") {
return fmt.Errorf(`parameter "b" has wrong prefix`)
}
return nil
}
...
foo, err := DecodeAndValidateJSON[FooRequest](r)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
```
---
```go
func DecodeAndValidateJSON(r *http.Request, target *Validator) error {
err := json.NewDecoder(r.Body).Decode(target)
if err != nil {
return err
}
if err := (*target).Validate(); err != nil {
return err
}
return nil
}
...
var foo Validator = FooRequest{}
if err := DecodeAndValidateJSON(r, &foo); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
```
In realtà anche in questo caso non serviva introdurre necessariamente delle generics
---
<style scoped>
code { font-size: 150% }
</style>
L'unico problema è che siamo obbligati a fare questo cast che non è molto estetico
```go
var foo Validator = FooRequest{}
```
---
<!-- _class: chapter -->
# Pattern (2)
Vediamo un analogo di `PhantomData<T>` dal Rust per rendere _type-safe_ l'interfaccia di una libreria
---
In Rust è obbligatorio utilizzare tutte le generics usate.
```rust
// Ok
struct Foo<T> { a: String, value: T }
// Errore
struct Foo<T> { a: String }
```
In certi casi però vogliamo introdurle solo per rendere _type-safe_ un'interfaccia o per lavorare con le _lifetime_.
```go
// Ok
use std::marker::PhantomData;
struct Foo<T> { a: String, foo_type: PhantomData<T> }
```
---
Proviamo ad usare questa tecnica per rendere _type-safe_ l'interfaccia con `*sql.DB`
```go
package database
type IdTable interface {
PrimaryKey() *string
Columns() []any
}
type Ref[T IdTable] string
type Table[T IdTable] struct {
Name string
PkColumn string
}
```
---
```go
// a random db library
// type DB = *sql.DB
func Create[T IdTable](d DB, t Table[T], row T) (Ref[T], error)
func Insert[T IdTable](d DB, t Table[T], row T) (Ref[T], error)
func Read[T IdTable](d DB, t Table[T], ref Ref[T]) (*T, error)
func Update[T IdTable](d DB, t Table[T], row T) error
func Delete[T IdTable](d DB, t Table[T], id string) error
```
---
```go
func Read[T IdTable](d DB, t Table[T], ref Ref[T]) (*T, error) {
result := d.QueryRow(
fmt.Sprintf(
`SELECT * FROM %s WHERE %s = ?`,
t.Name, t.PkColumn,
),
string(ref),
)
var value T
if err := result.Scan(value.Columns()...); err != nil {
return nil, err
}
return &value, nil
}
```
---
```go
type User struct {
Username string
FullName string
Age int
}
func (u *User) PrimaryKey() *string {
return &u.Username
}
func (u User) Columns() []any {
return []any{ &u.Username, &u.FullName, &u.Age }
}
var UsersTable = Table[User]{
Name: "users",
PkColumn: "username",
}
```
---
```go
db := ...
user1 := &User{ "aziis98", "Antonio De Lucreziis", 24 }
ref1, _ := database.Insert(db, UsersTable, user1)
...
user1, _ := database.Read(db, UsersTable, ref1)
```
---
<!-- _class: chapter -->
# Altro esempio caotico
Vediamo come implementare le promise in Go con le generics
---
```go
type Promise[T any] struct {
value T
err error
done <-chan struct{}
}
func (p Promise[T]) Await() (T, error) {
<-p.done
return p.value, p.err
}
type Waiter { Wait() error }
func (p Promise[T]) Wait() error {
<-p.done
return p.err
}
```
---
```go
type PromiseFunc[T any] func(resolve func(T), reject func(error))
func Start[T any](f PromiseFunc[T]) *Promise[T] {
done := make(chan struct{})
p := Promise{ done: done }
f(
func(value T) { p.value = value; done <- struct{} },
func(err error) { p.err = err; done <- struct{} }
)
return &p
}
```
---
```go
func AwaitAll(ws ...Waiter) error {
var wg sync.WaitGroup
wg.Add(len(ws))
errChan := make(chan error, len(ws))
for _, w := range ws {
go func(w Waiter) {
defer wg.Done()
err := w.Wait()
if err != nil {
errChan <- err
}
}(w)
}
...
```
---
```go
...
done := make(chan struct{})
go func() {
defer close(done)
wg.Wait()
}()
select {
case err := <-errChan:
return err
case <-done:
return nil
}
}
```
---
# Fine :C

Loading…
Cancel
Save