From 3c2447b2d17245be129fdae0646b8954735b620d Mon Sep 17 00:00:00 2001 From: Eli Bendersky Date: Tue, 15 Mar 2022 10:53:49 -0700 Subject: [PATCH] Add example of Go generics (#415) * Add example of Go generics Fixes #349 * Remove TODO * Update public link * Update GitHub action to only build with 1.18-rc1 1.17 won't successfully build the generics example --- .github/workflows/test.yml | 2 +- examples.txt | 1 + examples/generics/generics.go | 74 ++++++++++ examples/generics/generics.hash | 2 + examples/generics/generics.sh | 2 + public/embedding | 4 +- public/errors | 2 +- public/generics | 250 ++++++++++++++++++++++++++++++++ public/index.html | 2 + 9 files changed, 335 insertions(+), 4 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/.github/workflows/test.yml b/.github/workflows/test.yml index 45e8e87..2dfed23 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,7 +12,7 @@ jobs: strategy: matrix: os: [ubuntu-latest, macos-latest] - go-version: [1.17.x, 1.18.0-rc.1] + go-version: [1.18.0-rc.1] runs-on: ${{ matrix.os }} steps: 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..de20e28 --- /dev/null +++ b/examples/generics/generics.go @@ -0,0 +1,74 @@ +// Starting with version 1.18, Go has added support for +// _generics_, also known as _type parameters_. + +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..77ce621 --- /dev/null +++ b/examples/generics/generics.hash @@ -0,0 +1,2 @@ +6219a4dadc2685ab1b61dc8e95799fd683ccb761 +YulcAofh266 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..bddca50 --- /dev/null +++ b/public/generics @@ -0,0 +1,250 @@ + + + + + Go by Example: Generics + + + + +
+

Go by Example: Generics

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

Starting with version 1.18, Go has added support for +generics, also known as type parameters.

+ +
+ + +
+ + + +
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