From 5d6abb108e4a85e20d45966a1733e30fff62603f Mon Sep 17 00:00:00 2001 From: Eli Bendersky Date: Tue, 8 Mar 2022 16:54:29 -0800 Subject: [PATCH] Add example of Go generics Fixes #349 --- examples.txt | 1 + examples/generics/generics.go | 75 ++++++++++ examples/generics/generics.hash | 2 + examples/generics/generics.sh | 2 + public/embedding | 4 +- public/errors | 2 +- public/generics | 251 ++++++++++++++++++++++++++++++++ public/index.html | 2 + 8 files changed, 336 insertions(+), 3 deletions(-) create mode 100644 examples/generics/generics.go create mode 100644 examples/generics/generics.hash create mode 100644 examples/generics/generics.sh create mode 100644 public/generics diff --git a/examples.txt b/examples.txt index c309511..a1e4ef3 100644 --- a/examples.txt +++ b/examples.txt @@ -20,6 +20,7 @@ Structs Methods Interfaces Embedding +Generics Errors Goroutines Channels diff --git a/examples/generics/generics.go b/examples/generics/generics.go new file mode 100644 index 0000000..b1f5f99 --- /dev/null +++ b/examples/generics/generics.go @@ -0,0 +1,75 @@ +// Starting with version 1.18, Go has added support for +// _generics_, also known as _type parameters_. +// TODO: spec link? + +package main + +import "fmt" + +// As an example of a generic function, `MapKeys` takes +// a map of any type and returns a slice of its keys. +// This function has two type parameters - `K` and `V`; +// `K` has the `comparable` _constraint_, meaning that +// we can compare values of this type with the `==` and +// `!=` operators. This is required for map keys in Go. +// `V` has the `any` constraint, meaning that it's not +// restricted in any way (`any` is an alias for `interface{}`). +func MapKeys[K comparable, V any](m map[K]V) []K { + r := make([]K, 0, len(m)) + for k := range m { + r = append(r, k) + } + return r +} + +// As an example of a generic type, `List` is a +// singly-linked list with values of any type. +type List[T any] struct { + head, tail *element[T] +} + +type element[T any] struct { + next *element[T] + val T +} + +// We can define methods on generic types just like we +// do on regular types, but we have to keep the type +// parameters in place. The type is `List[T]`, not `List`. +func (lst *List[T]) Push(v T) { + if lst.tail == nil { + lst.head = &element[T]{val: v} + lst.tail = lst.head + } else { + lst.tail.next = &element[T]{val: v} + lst.tail = lst.tail.next + } +} + +func (lst *List[T]) GetAll() []T { + var elems []T + for e := lst.head; e != nil; e = e.next { + elems = append(elems, e.val) + } + return elems +} + +func main() { + var m = map[int]string{1: "2", 2: "4", 4: "8"} + + // When invoking generic functions, we can often rely + // on _type inference_. Note that we don't have to + // specify the types for `K` and `V` when + // calling `MapKeys` - the compiler infers them + // automatically. + fmt.Println("keys m:", MapKeys(m)) + + // ... though we could also specify them explicitly. + _ = MapKeys[int, string](m) + + lst := List[int]{} + lst.Push(10) + lst.Push(13) + lst.Push(23) + fmt.Println("list:", lst.GetAll()) +} diff --git a/examples/generics/generics.hash b/examples/generics/generics.hash new file mode 100644 index 0000000..f70852a --- /dev/null +++ b/examples/generics/generics.hash @@ -0,0 +1,2 @@ +2066b359af6eb554e2ab3c031ea23a4dd235a7a1 +afmUH4U_14g diff --git a/examples/generics/generics.sh b/examples/generics/generics.sh new file mode 100644 index 0000000..4e59904 --- /dev/null +++ b/examples/generics/generics.sh @@ -0,0 +1,2 @@ +keys: [4 1 2] +list: [10 13 23] diff --git a/public/embedding b/public/embedding index 7e089e1..7630a2c 100644 --- a/public/embedding +++ b/public/embedding @@ -14,7 +14,7 @@ if (e.key == "ArrowRight") { - window.location.href = 'errors'; + window.location.href = 'generics'; } } @@ -230,7 +230,7 @@ we see that a container now implements the

- Next example: Errors. + Next example: Generics.

diff --git a/public/errors b/public/errors index 4ac669a..e7340a7 100644 --- a/public/errors +++ b/public/errors @@ -9,7 +9,7 @@ onkeydown = (e) => { if (e.key == "ArrowLeft") { - window.location.href = 'embedding'; + window.location.href = 'generics'; } diff --git a/public/generics b/public/generics new file mode 100644 index 0000000..87a79ac --- /dev/null +++ b/public/generics @@ -0,0 +1,251 @@ + + + + + Go by Example: Generics + + + + +
+

Go by Example: Generics

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+

Starting with version 1.18, Go has added support for +generics, also known as type parameters. +TODO: spec link?

+ +
+ + +
+ + + +
package main
+
+
+ + + +
import "fmt"
+
+
+

As an example of a generic function, MapKeys takes +a map of any type and returns a slice of its keys. +This function has two type parameters - K and V; +K has the comparable constraint, meaning that +we can compare values of this type with the == and +!= operators. This is required for map keys in Go. +V has the any constraint, meaning that it’s not +restricted in any way (any is an alias for interface{}).

+ +
+ +
+func MapKeys[K comparable, V any](m map[K]V) []K {
+    r := make([]K, 0, len(m))
+    for k := range m {
+        r = append(r, k)
+    }
+    return r
+}
+
+
+

As an example of a generic type, List is a +singly-linked list with values of any type.

+ +
+ +
+type List[T any] struct {
+    head, tail *element[T]
+}
+
+
+ + + +
type element[T any] struct {
+    next *element[T]
+    val  T
+}
+
+
+

We can define methods on generic types just like we +do on regular types, but we have to keep the type +parameters in place. The type is List[T], not List.

+ +
+ +
+func (lst *List[T]) Push(v T) {
+    if lst.tail == nil {
+        lst.head = &element[T]{val: v}
+        lst.tail = lst.head
+    } else {
+        lst.tail.next = &element[T]{val: v}
+        lst.tail = lst.tail.next
+    }
+}
+
+
+ + + +
func (lst *List[T]) GetAll() []T {
+    var elems []T
+    for e := lst.head; e != nil; e = e.next {
+        elems = append(elems, e.val)
+    }
+    return elems
+}
+
+
+ + + +
func main() {
+    var m = map[int]string{1: "2", 2: "4", 4: "8"}
+
+
+

When invoking generic functions, we can often rely +on type inference. Note that we don’t have to +specify the types for K and V when +calling MapKeys - the compiler infers them +automatically.

+ +
+ +
+    fmt.Println("keys m:", MapKeys(m))
+
+
+

… though we could also specify them explicitly.

+ +
+ +
+    _ = MapKeys[int, string](m)
+
+
+ + + +
    lst := List[int]{}
+    lst.Push(10)
+    lst.Push(13)
+    lst.Push(23)
+    fmt.Println("list:", lst.GetAll())
+}
+
+
+ + + + + + + + +
+ + + +
keys: [4 1 2]
+list: [10 13 23]
+
+ + +

+ Next example: Errors. +

+ + + + +
+ + + + diff --git a/public/index.html b/public/index.html index 14d1d60..c7ef0c4 100644 --- a/public/index.html +++ b/public/index.html @@ -71,6 +71,8 @@
  • Embedding
  • +
  • Generics
  • +
  • Errors
  • Goroutines