Skip to content

Latest commit

 

History

History
189 lines (136 loc) · 5.21 KB

generics.md

File metadata and controls

189 lines (136 loc) · 5.21 KB

Golang introduces Generics in version 1.18

What is Generics?

Generics means parameterized types. The idea is to allow type (Integer, String, … etc., and user-defined types) to be a parameter to methods, classes, and interfaces. Using Generics, it is possible to create functions that work with different data types.

With generics, you can declare and use functions or types that are written to work with any of a set of types provided by calling code.

Why should i care about generics?

You should care about generics because they mean you don’t have to write as much code, especially if you’re in the business of writing packages and tooling. It can be frustrating to write utility functions without generics support. Think about common data structures like binary search trees and linked lists. Why would you want to rewrite them for every type they could possibly contain? int, bool, float64, and string aren’t the end of the list, because you may want to store a custom struct type.

Generics will finally give Go developers an elegant way to write amazing utility packages.

Generics 3 main features

  • Type Parameter with constraints
  • Type Inference
  • Type Set

1. Type Parameter with constraints

package main
import "fmt"

func myFunc[T any](a T) T {
	return a
}

func main() {
	a := myFunc[int](10)
	fmt.Println(a)
}

Calling above function

a:=myFunc[int](10)
fmt.Println(a)

2. Type Inference

Inference is using observation and background to reach a logical conclusion. You probably practice inference every day. For example, if you see someone eating a new food and he or she makes a face, then you infer he does not like it. Or if someone slams a door, you can infer that she is upset about something.

Inference: the process of inferring something. -> deduce or conclude -> remove type when calling a function

Calling differently according to the inference theory.

package main

import "fmt"

func myFunc[T any](a T) T {
	return a
}

// Any value with int64 or underlying type int64 are allowed
// with the special symbol called tilde  ~
// **Hints:** notice a tilde sign before int64
func nextNumber[T ~int64 | float64](a T) T {
	return a + 1
}

type superInt int64

func main() {

	a := myFunc[int](10) //calling with type
	fmt.Println("myFunc:", a)

	a = myFunc(10) //calling without type == type inference
	fmt.Println("myFunc withoutType:", a)

	b := nextNumber(5.0)
	fmt.Println("nextNumber:", b)

	var c superInt = 7 //superInt underlying type is int64
	d := nextNumber(c) //if you remove tilde sign from the function defination it wouldn't work.
	fmt.Println("~ nextNumber:", d)

}

3. Type Set (Declare a type constraint| Custom type)

package main

import "fmt"

type Number interface {
	~float64 | int64 | int //Declare a union of int64 and float64 inside the interface.
}

type Price float64

func nextNumber[T Number](a T) T {
	return a + 1
}

func main() {

	next := nextNumber(50)
	fmt.Println("nextNumber:", next)

	var p Price = 75.50
	another := nextNumber(p)
	fmt.Println("another nextNumber:", another)
}

You are only allowed to make your custom constraint using the following types. why?

  • Ordered is a type constraint that matches any ordered type.
  • An ordered type is one that supports the <, <=, >, and >= operators.

Type lists

  • ~int | ~int8 | ~int16 | ~int32 | ~int64 |
  • ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr |
  • ~float32 | ~float64 |
  • ~string

Generic Types vs Generic Function

So we know that we can write functions that use generic types, but what if we want to create a custom type that can contain generic types? For example, a slice of order-able objects. The new proposal makes this possible.

type comparableSlice[T comparable] []T

func allEqual[T comparable](s comparableSlice[T]) bool {
    if len(s) == 0 {
        return true
    }
    last := s[0]
    for _, cur := range s[1:] {
        if cur != last {
            return false
        }
        last = cur
    }
    return true 
}

func main() {
    fmt.Println(allEqual([]int{4,6,2}))
    // false

    fmt.Println(allEqual([]int{1,1,1}))
    // true
}
func splitAnySlice[T any](s []T) ([]T, []T) {
    mid := len(s)/2
    return s[:mid], s[mid:]
}

func main() {
    firstInts, secondInts := splitAnySlice([]int{0, 1, 2, 3})
    fmt.Println(firstInts, secondInts)
    // prints [0 1] [2 3]

    firstStrings, secondStrings := splitAnySlice([]string{"zero", "one", "two", "three"})
    fmt.Println(firstStrings, secondStrings)
    // prints [zero one] [two three]
}

Builtin Constraints

  • any
  • comparable
  • Parametric constraints or Custom constraints

Comparable constraint

The comparable constraint is a predefined constraint as well, just like the any constraint. When using the comparable constraint instead of the any constraint, you can use the != and == operators within your function logic.

Generics Golang Playground

Why Generics?

Resource