From 44f983b2eed524c7f5721e830396006cf70f28d0 Mon Sep 17 00:00:00 2001 From: Ville Puuska <40150442+VillePuuska@users.noreply.github.com> Date: Fri, 22 Nov 2024 16:11:18 +0200 Subject: [PATCH] Add example of calling generic constructor with type that cannot be inferred (#778) * add constructor example for Stack * typo --- generics.md | 77 +++++++++++++++++++++++++++++++++++++++ generics/generics_test.go | 2 +- generics/stack.go | 4 ++ 3 files changed, 82 insertions(+), 1 deletion(-) diff --git a/generics.md b/generics.md index 8b85b1422..0191ed5ca 100644 --- a/generics.md +++ b/generics.md @@ -513,6 +513,83 @@ func (s *Stack[Apple]) Pop() (Apple, bool) Now that we have done this refactoring, we can safely remove the string stack test because we don't need to prove the same logic over and over. +Note that so far in the examples of calling generic functions, we have not needed to specify the generic types. For example, to call `AssertEqual[T]`, we do not need to specify what the type `T` is since it can be inferred from the arguments. In cases where the generic types cannot be inferred, you need to specify the types when calling the function. The syntax is the same as when defining the function, i.e. you specify the types inside square brackets before the arguments. + +For a concrete example, consider making a constructor for `Stack[T]`. +```go +func NewStack[T any]() *Stack[T] { + return new(Stack[T]) +} +``` +To use this constructor to create a stack of ints and a stack of strings for example, you call it like this: +```go +myStackOfInts := NewStack[int]() +myStackOfStrings := NewStack[string]() +``` + +Here is the `Stack` implementation and the tests after adding the constructor. + +```go +type Stack[T any] struct { + values []T +} + +func NewStack[T any]() *Stack[T] { + return new(Stack[T]) +} + +func (s *Stack[T]) Push(value T) { + s.values = append(s.values, value) +} + +func (s *Stack[T]) IsEmpty() bool { + return len(s.values) == 0 +} + +func (s *Stack[T]) Pop() (T, bool) { + if s.IsEmpty() { + var zero T + return zero, false + } + + index := len(s.values) - 1 + el := s.values[index] + s.values = s.values[:index] + return el, true +} +``` + +```go +func TestStack(t *testing.T) { + t.Run("integer stack", func(t *testing.T) { + myStackOfInts := NewStack[int]() + + // check stack is empty + AssertTrue(t, myStackOfInts.IsEmpty()) + + // add a thing, then check it's not empty + myStackOfInts.Push(123) + AssertFalse(t, myStackOfInts.IsEmpty()) + + // add another thing, pop it back again + myStackOfInts.Push(456) + value, _ := myStackOfInts.Pop() + AssertEqual(t, value, 456) + value, _ = myStackOfInts.Pop() + AssertEqual(t, value, 123) + AssertTrue(t, myStackOfInts.IsEmpty()) + + // can get the numbers we put in as numbers, not untyped interface{} + myStackOfInts.Push(1) + myStackOfInts.Push(2) + firstNum, _ := myStackOfInts.Pop() + secondNum, _ := myStackOfInts.Pop() + AssertEqual(t, firstNum+secondNum, 3) + }) +} +``` + + Using a generic data type we have: - Reduced duplication of important logic. diff --git a/generics/generics_test.go b/generics/generics_test.go index f1afe37eb..09ef11be5 100644 --- a/generics/generics_test.go +++ b/generics/generics_test.go @@ -18,7 +18,7 @@ func TestAssertFunctions(t *testing.T) { func TestStack(t *testing.T) { t.Run("integer stack", func(t *testing.T) { - myStackOfInts := new(Stack[int]) + myStackOfInts := NewStack[int]() // check stack is empty AssertTrue(t, myStackOfInts.IsEmpty()) diff --git a/generics/stack.go b/generics/stack.go index 461a82847..82572bb63 100644 --- a/generics/stack.go +++ b/generics/stack.go @@ -4,6 +4,10 @@ type Stack[T any] struct { values []T } +func NewStack[T any]() *Stack[T] { + return new(Stack[T]) +} + func (s *Stack[T]) Push(value T) { s.values = append(s.values, value) }