diff --git a/examples/chans/main.go b/examples/chans/main.go new file mode 100644 index 0000000..d693612 --- /dev/null +++ b/examples/chans/main.go @@ -0,0 +1,132 @@ +package main + +import ( + "context" + "fmt" + "time" +) + +func Aggregate[T any](cs ...<-chan T) <-chan T { + agg := make(chan T) + + go func() { + defer close(agg) + for { + closed := 0 + for _, c := range cs { + select { + case value, more := <-c: + if more { + agg <- value + } else { + closed++ + } + default: + } + } + + if closed == len(cs) { + break + } + } + }() + + return agg +} + +type TargetChan[T any] struct { + c <-chan T + target *T +} + +type TryReceiver interface { + TryReceive() bool +} + +func (tc TargetChan[T]) TryReceive() bool { + select { + case value := <-tc.c: + *tc.target = value + return true + default: + return false + } +} + +func AwaitFirst(cancel context.CancelFunc, ws ...TryReceiver) { + done := make(chan struct{}) + go func() { + defer close(done) + for { + for _, w := range ws { + if w.TryReceive() { + done <- struct{}{} + return + } + } + } + }() + + <-done + cancel() + return +} + +func main() { + c1 := make(chan string) + c2 := make(chan int) + c3 := make(chan float64) + + c := context.Background() + c, cancel := context.WithCancel(c) + + go func() { + defer close(c1) + for { + select { + case <-c.Done(): + return + case <-time.After(300 * time.Millisecond): + } + + c1 <- "1" + } + }() + go func() { + defer close(c2) + for { + select { + case <-c.Done(): + return + case <-time.After(400 * time.Millisecond): + } + + c2 <- 2 + } + }() + go func() { + defer close(c3) + for { + select { + case <-c.Done(): + return + case <-time.After(200 * time.Millisecond): + } + + c3 <- 3.0 + } + }() + + var result1 string + var result2 int + var result3 float64 + + AwaitFirst( + cancel, + TargetChan[string]{c1, &result1}, + TargetChan[int]{c2, &result2}, + TargetChan[float64]{c3, &result3}, + ) + + fmt.Println(result1, result2, result3) +} diff --git a/extra-slides.md b/extra-slides.md new file mode 100644 index 0000000..fb18552 --- /dev/null +++ b/extra-slides.md @@ -0,0 +1,186 @@ + +--- + + + +# Confronto con altri linguaggi +Vediamo come funzionano le generics in Go confrontandole con altri linguaggi + +--- + +## C + +```c +// versione semplificata da linux/minmax.h +#define min(x, y) ({ \ // block expr (GCC extension) + typeof(x) _min1 = (x); \ // eval x once + typeof(y) _min2 = (y); \ // eval y once + (void) (&_min1 == &_min2); \ // check same type + _min1 < _min2 ? _min1 : _min2; \ // do comparison +}) +``` + +Impl. ⇝ _Sostituzione testuale post-tokenizzazione_ + +--- + +## C++ + +```cpp +template +T min(T const& a, T const& b) +{ + return (a < b) ? a : b; +} +``` + +Impl. ⇝ _se funziona allora ok_ + +--- + +## Rust + +```rust +pub fn min(a: T, b: T) -> T { + if a < b { + a + } else { + b + } +} +``` + +Impl. ⇝ _Monomorfizzazione_ + + + + + + + + + + + +--- + +In Rust è obbligatorio utilizzare tutte le generics usate. + +```rust +// Ok +struct Foo { a: String, value: T } + +// Errore +struct Foo { 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 { a: String, foo_type: PhantomData } +``` + + + + + + +--- + +```go +func Resolve[T](value T) *Promise[T] { + return &Promise{ value: value } +} + +func Reject[T](err error) *Promise[T] { + return &Promise{ error: err } +} +``` + + + + + + +--- + +```go +type Waiter interface { Wait() error } + +func (p Promise[T]) Wait() error { + <-p.done + return p.err +} + +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 + } +} +``` + + + + + + + + +--- + +```go +func ResolveInto[T any](p *Promise[T], target *T) *Promise[T] { + return Run[T](func(resolve func(T), reject func(error)) { + value, err := p.Await() + if err != nil { + reject(err) + return + } + + *target = value + resolve(value) + }) +} +``` + +```go +err := AwaitAll( + ResolveInto(httpRequest1, &result1), // :: *Promise[int] + ResolveInto(httpRequest2, &result2), // :: *Promise[struct{ ... }] + ResolveInto(httpRequest3, &result3), // :: *Promise[any] + timer1, // :: *Promise[struct{}] +) +... +``` \ No newline at end of file diff --git a/post-v2.md b/post-v2.md new file mode 100644 index 0000000..e1dd572 --- /dev/null +++ b/post-v2.md @@ -0,0 +1,934 @@ + +# Introduzione alle Generics in Go + +Dalla versione 1.18 del Go è stata aggiunta la possibilità di definire funzioni e strutture parametrizzate da tipi con i cosiddetti _type parameters_ o anche dette semplicemente _generics_. Lo scopo principale è che ci permettono di scrivere codice indipendente dai tipi specifici che utilizzano. + +Più precisamente le tre novità relative alle _generics_ sono + +- Sia funzioni che tipi possono essere parametrizzati rispetto a dei tipi (_type parameters_) + +- In un modo ristretto le interfacce possono essere utilizzare per definire "insiemi di tipi" (_type sets_) + +- Un minimo di _type inference_ che ci permette di omettere i _type parameters_ quando si riescono a dedurre dal contesto. + +## Il problema + +Uno degli esempi più lampanti della necessità di aggiungere le _generics_ al Go è che ad esempio manca la funzione `Min` per interi nella libreria standard del linguaggio e bisogna scriversi ogni volta un'implementazione speciale di `Min(x, y)` per il tipo numerico che vogliamo utilizzare (al momento c'è solo `math.Min(float64, float64) float64` che però necessita di conversioni se la vogliamo usare per interi o anche solo `float32`) + +```go +func Min(x, y int) int { + if x < y { + return x + } + + return y +} +``` +e quindi siamo costretti o a ricopiare tante volte la stessa funzione specializzandola a mano per i vari tipi per cui la vogliamo usare + +```go +func MinInt8(x, y int8) int8 { + if x < y { + return x + } + + return y +} + +func MinInt16(x, y int16) int16 { + if x < y { + return x + } + + return y +} + +func MinFloat32(x, y float32) float32 { + if x < y { + return x + } + + return y +} +``` + +o ad esempio scrivere una funzione che prende input `interface{}` (ora c'è un alias ad `any`) ed utilizzare uno switch sul tipo per decidere cosa fare. + +Alternativamente ci sono anche tecniche per risolvere questo problema che utilizzano `go generate`. + +## Type Parameters + +Dalla versione 1.18 come già detto sono stati introdotti i _type parameters_ che possiamo ad esempio applicare ad una funzione come segue per introdurre una _generics_ alla funzione. + +```go +import "golang.org/x/exp/constraints" + +func Min[T constraints.Ordered](x, y T) T { + if x < y { + return x + } + return y +} +``` + +Questa funzione generica può essere utilizzata come segue + +```go +var a, b int = 0, 1 +Min[int](a, b) +... +var a, b float32 = 3.14, 2.71 +Min[float32](a, b) +``` + +o anche omettendo la chi + +```go +var a, b int = 0, 1 +Min(a, b) +... +var a, b float32 = 3.14, 2.71 +Min(a, b) +``` + +Più precisamente la sintassi per introdurre un _type parameter_ è la seguente, tra quadre indichiamo il nome del tipo che vogliamo introdurre seguito da un'interfaccia che indica il vincolo che il parametro deve rispettare. + +``` +[T Vincolo1, R interface{ Method(), ... }, ...] +``` + +Vediamo meglio come definire questi vincoli. + +## Type Sets + +Fino ad ora quando in Go si parla di interfacce si prende in considerazione il _method set_ per quell'interfaccia e dato un tipo esso implementerà l'interfaccia se ha tutti i metodi del _method set_. + + + +Un modo duale di vedere la cosa è di pensare al _type set_ generato da un'interfaccia ovvero l'insieme di tutti i tipi che rispettano un'interfaccia come nel diagramma che segue. + + + +Seguendo questa linea di pensiero è stata estesa la sintassi delle interfacce per ammettere una dichiarazione esplicita del _type set_ sotto forma di un'unione di tipi che l'interfaccia accetta (se omessa allora accetterà tutti i tipi). + + + +Sono state inoltre aggiunte delle semplificazioni nella sintassi per cui quando scriviamo il vincolo di un tipo e vogliamo usare un'interfaccia senza metodi possiamo scrivere direttamente l'unione di tipi. + +- Se vogliamo scrivere `[T interface{ int | float32 }]` può anche essere scritto come `[T int | float32]` + +- Inoltre è stato aggiunto (finalmente) un alias per il tipo `interface{}` detto `any` + +### Tipi con la tilde + +Consideriamo ad esempio il seguente frammento di codice. + +```go +func SumTwoIntegers[T int](x, y int) T { + if x < y { return x } + return y +} +``` + +Però se abbiamo un tipo "sinonimo" di `int` ma non suo alias allora non potremo usarlo nella chiamata generica perché di per sé `Liter` e `int` non sono compatibili. + +```go +type Liter int +... + +var a, b int = 1, 2 +SumTwoIntegers(a, b) // Ok + +var a, b Liter = 1, 2 +SumTwoIntegers(a, b) // Errore +``` + +È stata però inserita la seguente sintassi con la `~` prima di un tipo per intendere anche tutti i suoi _type alias_. + +```go +func SumTwoIntegers[T ~int](x, y int) T { + if x < y { return x } + return y +} +``` + +ed infatti poi il seguente frammento compila + +```go +type Liter int +... + +var a, b int = 1, 2 +SumTwoIntegers(a, b) // Ok + +var a, b Liter = 1, 2 +SumTwoIntegers(a, b) // Ok +``` + +Utilizzando la stessa tecnica possiamo considerare un caso più utile definito direttamente la modulo `constraints`. + +```go +package constraints + +... + +type Float interface { + ~float32 | ~float64 +} + +... +``` + +e sulla stessa riga possiamo finalmente vedere com'è definita l'interfaccia `constraints.Ordered` vista in precedenza. + +```go +package constraints + +... + +type Ordered interface { + Integer | Float | ~string +} + +type Float interface { + ~float32 | ~float64 +} + +type Integer interface { + Signed | Unsigned +} + +type Signed interface { + ~int | ~int8 | ~int16 | ~int32 | ~int64 +} + +type Unsigned interface { + ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr +} + +... +``` + +## Tipi Generici + +Possiamo ad esempio definire uno _stack_ generico come segue + +```go +type Stack[T any] []T +``` + +Questo ci permette di vedere come possono essere definiti metodi per tipi generici + +```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) +} +``` + +Il metodo _pop_ è un po' più interessante, decidiamo che ritornerà il valore tolto dallo stack seguito da `true` se lo stack non era vuoto e "`0.(T)`" e `false` se lo stack era vuoto. + +```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 +} +``` + +Per ora per ottenere un valore rappresentante il valore di default per un tipo serve introdurre una nuova variabile e poi ritornarla. + +Alternativamente possiamo definire questa funzione di _utility_ + +```go +func zero[T any]() T { + var zero T + return zero +} +``` + +e diciamo che questo è un primo _pattern_ che spesso può essere utile quando lavoriamo con le generics. + +In realtà [anche il resto del mondo](https://go.googlesource.com/proposal/+/refs/heads/master/design/43651-type-parameters.md#the-zero-value) si è accorto di questo trick e già si sta pensando a delle soluzioni come utilizzare `nil` o `_` per indicare un valore di default per un tipo. + +## Pattern: Tipi Contenitore + +Vediamo ora qualche caso utile in cui utilizzare le _generics_. + +### Tipi generici nativi + +Fin dall'inizio il Go ha avuto alcune strutture generiche _backed in_ + +- `[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` + +solo che non essendoci le generics non era possibile definire algoritmi generici che le utilizzassero. + +Ora finalmente è possibile ed infatti già ci sono moduli sperimentali della libreria standard che introducono una manciata di funzioni utili per lavorare con queste strutture + +- `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... + +- `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... + +### Strutture Dati Generiche + +Stanno anche nascendo alcuni moduli esterni con varie strutture dati generiche come ad esempio (~1K stelle su GitHub) che fornisce le seguenti strutture + +- `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... + +## Anti-Pattern (1) + +Vediamo ora un esempio (a posteriori discutibile) di come creare una utility http. + +```go +// library code +type Validator interface { + Validate() error +} + +func DecodeAndValidateJSON[T Validator](r *http.Request) (T, error) { + var value T + if err := json.NewDecoder(r.Body).Decode(&value); err != nil { + var zero T + return zero, err + } + + if err := value.Validate(); err != nil { + var zero T + return zero, err + } + + return value, nil +} +``` + +--- + +```go +// client code +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 +} +``` + +```go +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 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 + +--- + +Quindi nella maggior parte dei casi se ci ritroviamo a scrivere una funzione generica con un **parametro vincolato ad un'interfaccia** forse dobbiamo porci qualche domanda + +--- + + + +# Anti-Pattern 2 +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[*bytes.Buffer](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 }) +``` + +--- + +#### Go 1.18 Implementation of Generics via Dictionaries and Gcshape Stenciling + +- _A **gcshape** (or gcshape grouping) is a collection of types that all **share the same instantiation of a generic function/method**_. + +- _Two concrete types are in the same gcshape grouping if and only if they have the **same underlying type** or they are **both pointer types**._ + +- _To avoid creating a different function instantiation for each generic call with distinct type arguments (which would be pure stenciling), we **pass a dictionary along with every call**_. + +:link: [generics-implementation-dictionaries-go1.18.md](https://github.com/golang/proposal/blob/master/design/generics-implementation-dictionaries-go1.18.md) + + + +--- + + + +# Pattern: "PhantomData" +Vediamo un analogo di `PhantomData` dal Rust per rendere _type-safe_ l'interfaccia di una libreria + +--- + +Proviamo ad usare questa tecnica per rendere _type-safe_ l'interfaccia con `*sql.DB` + +```go +type DatabaseRef[T any] string +``` + +```go +package tables + +// tables metadata +var Users = database.Table[User]{ ... } +var Products = database.Table[Product]{ ... } +``` + + +```go +userRef1 := DatabaseRef[User]("j.smith@example.org") +... + +// Ok +user1, err := database.Read(dbConn, tables.Users, userRef1) +// Errore +user2, err := database.Read(dbConn, tables.Products, userRef1) +``` + +--- + +```go +package database + +type WithPK interface { + PrimaryKey() *string +} + +type Ref[T WithPK] string + +type Table[T WithPK] struct { + Name string + PkColumn string + Columns func(*T) []any +} + +... + +func Read[T WithPK](d DB, t Table[T], ref Ref[T]) (*T, error) +``` + +--- + +```go +package database + +func Create[T WithPK](d DB, t Table[T], row T) (Ref[T], error) + +func Insert[T WithPK](d DB, t Table[T], row T) (Ref[T], error) + +func Read[T WithPK](d DB, t Table[T], ref Ref[T]) (*T, error) + +func Update[T WithPK](d DB, t Table[T], row T) error + +func Delete[T WithPK](d DB, t Table[T], id Ref[T]) error +``` + +--- + +```go +func Read[T WithPK](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(t.Columns(&value)...); err != nil { + return nil, err + } + + return &value, nil +} +``` + +--- + +```go +package model + +type User struct { + Username string + FullName string + Age int +} + +func (u *User) PrimaryKey() *string { + return &u.Username +} +``` + +```go +package tables + +var Users = Table[User]{ + Name: "users", + PkColumn: "username", + Columns: func(u *User) []any { + return []any{ &u.Username, &u.FullName, &u.Age } + } +} +``` + +--- + +```go +user1 := &model.User{ "j.smith@example.org", "John Smith", 36 } + +userRef1, _ := database.Insert(db, tables.Users, user1) + +... + +user1, _ := database.Read(db, tables.Users, userRef1) +``` + +--- + + + +# 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 +} +``` + +--- + +```go +type PromiseFunc[T any] func(resolve func(T), reject func(error)) + +func Run[T any](f PromiseFunc[T]) *Promise[T] { + done := make(chan struct{}) + p := Promise{ done: done } + + go f( + func(value T) { p.value = value; done <- struct{} }, + func(err error) { p.err = err; done <- struct{} } + ) + + return &p +} +``` + +--- + +```go +func AwaitAll[T any](ps ...*Promise[T]) error { + ... +} +``` + +```go +type Waiter interface { Wait() error } + +func (p Promise[T]) Wait() error { + <-p.done + return p.err +} + +func AwaitAll(ws ...Waiter) error { + ... +} +``` + +--- + +```go +func ResolveInto[T any](p *Promise[T], target *T) *Promise[T] { + ... +} +``` + +```go +AwaitAll( + ResolveInto(httpRequest1, &result1), // :: *Promise[int] + ResolveInto(httpRequest2, &result2), // :: *Promise[struct{ ... }] + ResolveInto(httpRequest3, &result3), // :: *Promise[any] + timer1, // :: *Promise[struct{}] +) +``` + +--- + + + +# 1 + 1 = 2 +_Proof checking_ in Go + +--- + +## Premesse + +```go +type Bool interface{ isBool() } + +type Term interface{ isTerm() } + +type Term2Term interface{ isTerm2Term() } + +// Trick per codificare higher-kinded types +type V[H Term2Term, T Term] Term +``` + +--- + +## Assiomi dei Naturali + +```go +type Zero Term +type Succ Term2Term + +// Alcuni alias utili +type One = V[Succ, Zero] +type Two = V[Succ, V[Succ, Zero]] +type Three = V[Succ, V[Succ, V[Succ, Zero]]] +``` + +--- + +## Uguaglianza + +```go +type Eq[A, B any] Bool + +// Eq_Refl ovvero l'assioma +// forall x : x = x +func Eq_Reflexive[T any]() Eq[T, T] { + panic("axiom") +} + +// Eq_Symmetric ovvero l'assioma +// forall a, b: a = b => b = a +func Eq_Symmetric[A, B any](_ Eq[A, B]) Eq[B, A] { + panic("axiom") +} + +// Eq_Transitive ovvero l'assioma +// forall a, b, c: a = b e b = c => a = c +func Eq_Transitive[A, B, C any](_ Eq[A, B], _ Eq[B, C]) Eq[A, C] { + panic("axiom") +} +``` + +--- + +## Uguaglianza e Sostituzione + +Per ogni funzione `F`, ovvero tipo vincolato all'interfaccia `Term2Term` vorremmo dire che + +``` + F +Eq[ A , B ] ------> Eq[ F[A] , F[B] ] + +``` + +--- + +## Uguaglianza e Sostituzione + +Data una funzione ed una dimostrazione che due cose sono uguali allora possiamo applicare la funzione ed ottenere altre cose uguali + +```go +// Function_Eq ovvero l'assioma +// forall f function, forall a, b term: a = b => f(a) = f(b) +func Function_Eq[F Term2Term, A, B Term](_ Eq[A, B]) Eq[V[F, A], V[F, B]] { + panic("axiom") +} +``` + +--- + +## Assiomi dell'addizione + +```go +type Plus[L, R Term] Term + +// "n + 0 = n" + +// Plus_Zero ovvero l'assioma +// forall n, m: n + succ(m) = succ(n + m) +func Plus_Zero[N Term]() Eq[Plus[N, Zero], N] { + panic("axiom") +} + +// "n + (m + 1) = (n + m) + 1" + +// Plus_Sum ovvero l'assioma +// forall a, m: n + succ(m) = succ(n + m) +func Plus_Sum[N, M Term]() Eq[ + Plus[N, V[Succ, M]], + V[Succ, Plus[N, M]], +] { panic("axiom") } +``` + +--- + +## 1 + 1 = 2 + +```go +func Theorem_OnePlusOneEqTwo() Eq[Plus[One, One], Two] { + // 1 + 0 = 1 + var en1 Eq[ Plus[One, Zero], One ] = Plus_Zero[One]() + + // (1 + 0) + 1 = 2 + var en2 Eq[ V[Succ, Plus[One, Zero]], Two ] = Function_Eq[Succ](en1) + + // 1 + 1 = (1 + 0) + 1 + var en3 Eq[ Plus[One, One], V[Succ, Plus[One, Zero]] ] = Plus_Sum[One, Zero]() + + return Eq_Transitive(en3, en2) +} +``` + +--- + +## 1 + 1 = 2 + +```go +func Theorem_OnePlusOneEqTwo() Eq[Plus[One, One], Two] { + return Eq_Transitive( + Plus_Sum[One, Zero](), + Function_Eq[Succ]( + Plus_Zero[One](), + ), + ) +} +``` + +--- + + + +# Conclusione + +--- + + + +### Regole generali + +Per scrivere _codice generico_ in Go + +- Se l'implementazione dell'operazione che vogliamo supportare non dipende del tipo usato allora conviene usare dei **type-parameter** + +- Se invece dipende dal tipo usato allora è meglio usare delle **interfacce** + +- Se invece dipende sia dal tipo e deve anche funzionare per tipi che non supportano metodi (ad esempio per i tipi primitivi) allora conviene usare **reflection** + +--- + +# Fine :C + +_Domande_ + +--- + + + +## Bibliografia + +- + +- + +- + +--- + diff --git a/slides.md b/slides.md index 4560d5c..9861548 100644 --- a/slides.md +++ b/slides.md @@ -10,6 +10,7 @@ size: 4:3 font-size: 175%; letter-spacing: 1px; + background: #ffffff; color: #222; } @@ -134,12 +135,12 @@ return y ## Soluzioni Pre-Generics -- Fare una funzione che prende `any` e mettere tanti switch - -- Utilizzare `go generate` [...] +- Fare una funzione che prende `any` ed usare degli switch sul tipo - Copia incollare tante volte la funzione per ogni tipo +- Utilizzare tool come `go generate` + --- ## Soluzione Post-Generics @@ -233,22 +234,21 @@ code { font-size: 150% } #### Type Sets ```go -func SumTwoIntegers[T int](x, y int) T { - if x < y { return x } - return y +func Somma[T float32|float64](x, y T) T { + return x + y } ``` ```go -type Liter int +type Liter float64 ``` ```go var a, b int = 1, 2 -SumTwoIntegers(a, b) // Ok +Somma(a, b) // Ok var a, b Liter = 1, 2 -SumTwoIntegers(a, b) // Errore +Somma(a, b) // Errore ``` --- @@ -256,25 +256,23 @@ SumTwoIntegers(a, b) // Errore #### Type Sets ```go -func SumTwoIntegers[T ~int](x, y int) T { - if x < y { return x } - return y +func Somma[T ~float32|~float64](x, y T) T { + return x + y } ``` ```go -type Liter int +type Liter float64 ``` ```go var a, b int = 1, 2 -SumTwoIntegers(a, b) // Ok +Somma(a, b) // Ok var a, b Liter = 1, 2 -SumTwoIntegers(a, b) // Ok +Somma(a, b) // Ok ``` - --- #### Type Sets @@ -667,32 +665,11 @@ d := &bytes.Buffer{} /* (*bytes.Buffer) */ -# Pattern (2) +# Pattern: "PhantomData" Vediamo un analogo di `PhantomData` dal Rust per rendere _type-safe_ l'interfaccia di una libreria --- -In Rust è obbligatorio utilizzare tutte le generics usate. - -```rust -// Ok -struct Foo { a: String, value: T } - -// Errore -struct Foo { 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 { a: String, foo_type: PhantomData } -``` - ---- - Proviamo ad usare questa tecnica per rendere _type-safe_ l'interfaccia con `*sql.DB` ```go @@ -703,19 +680,18 @@ type DatabaseRef[T any] string package tables // tables metadata -var Users = Table[User]{ ... } -var Products = Table[Product]{ ... } +var Users = database.Table[User]{ ... } +var Products = database.Table[Product]{ ... } ``` ```go userRef1 := DatabaseRef[User]("j.smith@example.org") - ... + // Ok user1, err := database.Read(dbConn, tables.Users, userRef1) - -// Error +// Errore user2, err := database.Read(dbConn, tables.Products, userRef1) ``` @@ -735,6 +711,10 @@ type Table[T WithPK] struct { PkColumn string Columns func(*T) []any } + +... + +func Read[T WithPK](d DB, t Table[T], ref Ref[T]) (*T, error) ``` --- @@ -838,18 +818,6 @@ func (p Promise[T]) Await() (T, error) { --- -```go -func Resolve[T](value T) *Promise[T] { - return &Promise{ value: value } -} - -func Reject[T](err error) *Promise[T] { - return &Promise{ error: err } -} -``` - ---- - ```go type PromiseFunc[T any] func(resolve func(T), reject func(error)) @@ -868,6 +836,12 @@ func Run[T any](f PromiseFunc[T]) *Promise[T] { --- +```go +func AwaitAll[T any](ps ...*Promise[T]) error { + ... +} +``` + ```go type Waiter interface { Wait() error } @@ -877,38 +851,7 @@ func (p Promise[T]) Wait() error { } 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 - } } ``` @@ -916,27 +859,17 @@ func AwaitAll(ws ...Waiter) error { ```go func ResolveInto[T any](p *Promise[T], target *T) *Promise[T] { - return Run[T](func(resolve func(T), reject func(error)) { - value, err := p.Await() - if err != nil { - reject(err) - return - } - - *target = value - resolve(value) - }) + ... } ``` ```go -err := AwaitAll( +AwaitAll( ResolveInto(httpRequest1, &result1), // :: *Promise[int] ResolveInto(httpRequest2, &result2), // :: *Promise[struct{ ... }] ResolveInto(httpRequest3, &result3), // :: *Promise[any] timer1, // :: *Promise[struct{}] ) -... ``` --- @@ -944,7 +877,7 @@ err := AwaitAll( # 1 + 1 = 2 -_Proof checking_ in Go +_Proof checking_ in Go --- @@ -1131,60 +1064,3 @@ li { - - - ---- - - ---- - - - -# Confronto con altri linguaggi -Vediamo come funzionano le generics in Go confrontandole con altri linguaggi - ---- - -## C - -```c -// versione semplificata da linux/minmax.h -#define min(x, y) ({ \ // block expr (GCC extension) - typeof(x) _min1 = (x); \ // eval x once - typeof(y) _min2 = (y); \ // eval y once - (void) (&_min1 == &_min2); \ // check same type - _min1 < _min2 ? _min1 : _min2; \ // do comparison -}) -``` - -Impl. ⇝ _Sostituzione testuale post-tokenizzazione_ - ---- - -## C++ - -```cpp -template -T min(T const& a, T const& b) -{ - return (a < b) ? a : b; -} -``` - -Impl. ⇝ _se funziona allora ok_ - ---- - -## Rust - -```rust -pub fn min(a: T, b: T) -> T { - if a < b { - a - } else { - b - } -} -``` - -Impl. ⇝ _Monomorfizzazione_ -