aboutsummaryrefslogtreecommitdiff
path: root/content/posts/2022-01-17-generics-in-go-1.18.md
diff options
context:
space:
mode:
Diffstat (limited to 'content/posts/2022-01-17-generics-in-go-1.18.md')
-rw-r--r--content/posts/2022-01-17-generics-in-go-1.18.md316
1 files changed, 316 insertions, 0 deletions
diff --git a/content/posts/2022-01-17-generics-in-go-1.18.md b/content/posts/2022-01-17-generics-in-go-1.18.md
new file mode 100644
index 0000000..5379ab7
--- /dev/null
+++ b/content/posts/2022-01-17-generics-in-go-1.18.md
@@ -0,0 +1,316 @@
+---
+slug: generics-in-go-1.18
+title: "Generics in Go 1.18"
+date: "2022-01-17T00:49:28-04:00"
+---
+I spent some time trying the [generics types and generic
+functions][generics] in [Go 1.18][], and I like what they've got so far.
+
+Below is a [Go 1.17][] program which prints the results of the following
+functions:
+
+* `SumInts()`: calculate the the sum of of a slice of `int` values.
+* `SumFloats()`: calculate the the sum of of a slice of `float32`
+ values.
+* `SumComplexes()`: calculate the the sum of of a slice of `complex64`
+ values.
+
+```go
+package main
+
+import "fmt"
+
+// Return sum of all values in slice of ints.
+func SumInts(m []int) int {
+ var r int
+
+ for _, v := range(m) {
+ r += v
+ }
+
+ return r
+}
+
+// Return sum of all values in slice of float32s.
+func SumFloats(m []float32) float32 {
+ var r float32
+
+ for _, v := range(m) {
+ r += v
+ }
+
+ return r
+}
+
+// Return sum of all values in slice of complex64s.
+func SumComplexes(m []complex64) complex64 {
+ var r complex64
+
+ for _, v := range(m) {
+ r += v
+ }
+
+ return r
+}
+
+var (
+ // test integers
+ ints = []int { 10, 20, 30 }
+
+ // test floating point numbers
+ floats = []float32 { 10.0, 20.0, 30.0 }
+
+ // test complex numbers
+ complexes = []complex64 { complex(10, 1), complex(20, 2), complex(30, 3) }
+)
+
+func main() {
+ // print sums
+ fmt.Printf("ints = %d\n", SumInts(ints))
+ fmt.Printf("floats = %2.1f\n", SumFloats(floats))
+ fmt.Printf("complexes = %g\n", SumComplexes(complexes))
+}
+```
+ 
+
+Here's the same program, written using [Go 1.18][] [generics][]:
+
+```go
+package main
+
+import "fmt"
+
+// Return sum of all numeric values in slice.
+func Sum[V int|float32|complex64](vals []V) V {
+ var r V
+
+ for _, v := range(vals) {
+ r += v
+ }
+
+ return r
+}
+
+var (
+ // test integers
+ ints = []int { 10, 20, 30 }
+
+ // test floating point numbers
+ floats = []float32 { 10.0, 20.0, 30.0 }
+
+ // test complex numbers
+ complexes = []complex64 { complex(10, 1), complex(20, 2), complex(30, 3) }
+)
+
+func main() {
+ // print sums using generics w/explicit types
+ fmt.Printf("ints = %d\n", Sum[int](ints))
+ fmt.Printf("floats = %2.1f\n", Sum[float32](floats))
+ fmt.Printf("complexes = %g\n", Sum[complex64](complexes))
+}
+```
+ 
+
+You can use [type inference][] to drop the type parameters in many
+instances. For example, we can rewrite `main()` from the previous
+example like this:
+
+```go
+func main() {
+ // print sums using generics w/explicit types
+ fmt.Printf("ints = %d\n", Sum(ints))
+ fmt.Printf("floats = %2.1f\n", Sum(floats))
+ fmt.Printf("complexes = %g\n", Sum(complexes))
+}
+```
+ 
+
+[Generics][] can also be used in type definitions. Example:
+
+```go
+package main
+
+import "fmt"
+
+// Fraction
+type Frac[T int|int32|int64] struct {
+ num T // numerator
+ den T // denominator
+}
+
+// Add two fractions.
+func (a Frac[T]) Add(b Frac[T]) Frac[T] {
+ return Frac[T] { a.num + b.num, a.den * b.den }
+}
+
+// Multiple fractions.
+func (a Frac[T]) Mul(b Frac[T]) Frac[T] {
+ return Frac[T] { a.num * b.num, a.den * b.den }
+}
+
+// Return inverse of fraction.
+func (a Frac[T]) Inverse() Frac[T] {
+ return Frac[T] { a.den, a.num }
+}
+
+// Return string representation of fraction.
+func (a Frac[T]) String() string {
+ return fmt.Sprintf("%d/%d", a.num, a.den)
+}
+
+func main() {
+ // test fractions
+ fracs = []Frac[int] {
+ Frac[int] { 1, 2 },
+ Frac[int] { 3, 4 },
+ Frac[int] { 5, 6 },
+ }
+
+ // print fractions
+ for _, f := range(fracs) {
+ fmt.Printf("%s => %s\n", f, f.Mul(f.Add(f.Inverse())))
+ }
+}
+```
+ 
+
+Interface type declarations can now be used to specify type unions. For
+example, the `Frac` type declaration from the previous example could be
+written like this instead:
+
+```go
+// Integral number type.
+type integral interface {
+ int | int32 | int64
+}
+
+// Fraction
+type Frac[T integral] struct {
+ num T // numerator
+ den T // denominator
+}
+```
+ 
+
+There are two new keywords:
+
+* `any`: Alias for `interface {}`.
+* `comparable`: Any type which can be compared for equality with `==`
+ and `!=`. Useful for the parameterizing map keys.
+
+There is a new `constraints` package, which (not yet visible in the
+[online Go documentation][go-docs] as of this writing) that provides a
+couple of useful unions, but it's relatively anemic at the moment:
+
+```bash
+$ go1.18beta1 doc -all constraints
+package constraints // import "constraints"
+
+Package constraints defines a set of useful constraints to be used with type
+parameters.
+
+TYPES
+
+type Complex interface {
+ ~complex64 | ~complex128
+}
+ Complex is a constraint that permits any complex numeric type. If future
+ releases of Go add new predeclared complex numeric types, this constraint
+ will be modified to include them.
+
+type Float interface {
+ ~float32 | ~float64
+}
+ Float is a constraint that permits any floating-point type. If future
+ releases of Go add new predeclared floating-point types, this constraint
+ will be modified to include them.
+
+type Integer interface {
+ Signed | Unsigned
+}
+ Integer is a constraint that permits any integer type. If future releases of
+ Go add new predeclared integer types, this constraint will be modified to
+ include them.
+
+type Ordered interface {
+ Integer | Float | ~string
+}
+ Ordered is a constraint that permits any ordered type: any type that
+ supports the operators < <= >= >. If future releases of Go add new ordered
+ types, this constraint will be modified to include them.
+
+type Signed interface {
+ ~int | ~int8 | ~int16 | ~int32 | ~int64
+}
+ Signed is a constraint that permits any signed integer type. If future
+ releases of Go add new predeclared signed integer types, this constraint
+ will be modified to include them.
+
+type Unsigned interface {
+ ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr
+}
+ Unsigned is a constraint that permits any unsigned integer type. If future
+ releases of Go add new predeclared unsigned integer types, this constraint
+ will be modified to include them.
+```
+&nbsp;
+
+Using the `constraints` package, the `Frac` type from the previous
+example could be written like this:
+
+```go
+import "constraints"
+
+// Fraction
+type Frac[T constraints.Signed] struct {
+ num T // numerator
+ den T // denominator
+}
+```
+&nbsp;
+
+And with `constraints`, the `Sum()` function from the first example
+could be defined like this:
+
+```go
+// Numeric value.
+type Number interface {
+ constraints.Integer | constraints.Float | constraints.Complex
+}
+
+// Return sum of all numeric values in slice.
+func Sum[V Number](vals []V) V {
+ var r V
+
+ for _, v := range(vals) {
+ r += v
+ }
+
+ return r
+}
+```
+&nbsp;
+
+Other useful tidbits:
+
+* No [type erasure][].
+* The standard library is still backwards compatible, so there is no
+ need to rewrite your existing code.
+* There are [two new tutorials][] which explain generics and fuzzing.
+
+[go]: https://go.dev/
+ "Go programming language"
+[go 1.18]: https://tip.golang.org/doc/go1.18
+ "Go 1.18"
+[go 1.17]: https://go.dev/doc/go1.17
+ "Go 1.17"
+[generics]: https://en.wikipedia.org/wiki/Generic_programming
+ "Generic programming"
+[type inference]: https://en.wikipedia.org/wiki/Type_inference
+ "Automatic detection of variable types."
+[go-docs]: https://pkg.go.dev/
+ "Online Go package documentation."
+[type erasure]: https://en.wikipedia.org/wiki/Type_erasure
+ "Type erasure."
+[two new tutorials]: https://go.dev/blog/tutorials-go1.18
+ "New Go tutorials which explain generics and fuzzing."