상세 컨텐츠

본문 제목

22/02 Study daily record

Go/Mastering Go

by Gopythor 2022. 2. 23. 12:14

본문

728x90
반응형

A Closer Look at Interfaces and Methods II

Embedding

Embedding is Go's answer to subclassing.
When you embed a type inside another, the embedded type is called the inner type while the other type is called the outer type.
The outer type has access to all the exported fields of the inner type.
The outer and inner types has to be properly initialized.

  • Embessing is the closest thing in go to subclassing. However it is not the same as subclassing in other languages. We'll cover why later embedding means that a ???? type like a struct can host another type and have access to all its fields and methods.
  • The type inside is called the inner type while the type that acts of the host is called the outer type.
  • The outer type will have access to all the exported fileds of the inner type for embedding to work both.
  • The outer and inner types have to be initialized properly.

Custom Randomizer

We will write a custom randomizer which will embed Go's math/rand functionality.
It will count the number of times we call a random function.
It will retrieve a random number between two values.

  • We will write some code to show how to use embedding and how powerful it can be.
  • Let's say we want to write a custom randomizer which will include Go's random number generation functionality which we can find in the math/rand package.
  • The randomizer will count the number of times we call the function.
  • It will also add new functionality where it will generate a random number between two values.

Math/rand package

type Rand
  func New(src Source) *Rand
  func (r *Rand) ExpFloat64() float64
  func (r *Rand) Float32() float32
  func (r *Rand) Float64() float64
  func (r *Rand) Int() int
  func (r *Rand) Int31() int32
  func (r *Rand) Int31n(n int32) int32
  func (r *Rand) Int63() int64
  func (r *Rand) Int63n(n int64) int64
  func (r *Rand) Intn(n int) int
  func (r *Rand) NormFloat64() float64
  func (r *Rand) Perm(n int) []int
  func (r *Rand) Read(p []byte) (n int, err error)
  func (r *Rand) Seed(seed int64)
  func (r *Rand) Shuffle(n int, swap func(i, j int))
  func (r *Rand) Uint32() uint32
  func (r *Rand) Uint64() uint64
  • Let's start by exploring the range struct in the math/rand package.
  • The Rand pointer implements methods set to expose a lot of the important functionalities in math/rand.
  • We can generate random numbers of different types as well as provide a seed.
  • func (r *Rand) Seed(seed int64)
  • A seed is a way to initialze a random number generator
  • a typical use case for a random number generator is to initialize it via a seed and then generate the random number of a specific type based on the method that we call. The package can be found at comment.
package main

import "math/rand"

type customRand struct {
    *rand.Rand
    count int
}

func NewCustomRand(i int64) *customRand {
    return &customRand{
        Rand: rand.New(rand.NewSource(i)),
        count:0,
    }
}
  • To embed an inner type in your struct, include the embedded type name without a field name as we did here was *rand.Rand.
  • By doing this we effectively included all the capability of the Rand struct pointer that we have seen earlier inside our custom Rand struct.
  • In this example, the rand struct pointer is the inner type whereas the customRand struct is the outer type.
  • We'll then create the constructor to initialize a new instance of the customRand pointer and we will use the struct literal statement for the inside the struct literal.
  • We'll initialize a pointer to our Rand struct by first calling rand.NewSource with the argument that is supplied to our constructor to create a seed and will feed the seed to a function called rand.New which will generate a pointer of the Rand type.
  • We use the name Rand in the struct literal statement to signify that this is embedded type Rand.
  • Go will take care of the interaction between the Rand struct and the Rand struct pointer for us.
  • Now whatif we want to access one of the inner type of Rand method via our outer type customRand struct.
  • As an example, Let's say you want to access Rand Intn method which is implemented by the Rand struct pointer as shown.func NewSourceNewSource returns a new pseudo-random Source seeded with the given value. Unlike the default Source used by top-level functions, this source is not safe for concurrent use by multiple goroutines.
  • func NewSource(seed int64) Source

func New

 func New(src Source) *Rand
  • New returns a new Rand that uses random values from src to generate other random values.

Intn

func (r *Rand) Intn(n int) int

Intn returns, as an int, a non-negative pseudo-random number in the half-open interval [0,n). It panics if n <= 0.

  • This method gets a random number between 0 and n which is supplied of an argument.

    func (cr *customRand) RandRange(min, max int) int { 
    cr.count++ 
    return cr.Rand.Intn(max-min) + min 
    }
  • Let's start by implementing a method called RandRange which is attached to the customRand struct pointer.

  • The method will increment the count field in the customRand struct by 1.

  • Then it will return a random value between the min argument and the max argument.

  • This could be done by getting a random value between 0 and (max-min)+min.

  • As shown here the methods belonging to the inner Rand struct type are accessible from within our customRand struct simply using the inner type name(cr.Rand.Intn) and again Go will take care of the pointer indirections for us.

  • So this will create a random number between 0 and (max-min)+min.

func (cr *customRand) GetCount() int {
    return cr.count
}
  • We will also create a GetCount accessor to get the count which represents the number of time the custom randomizer is used.
func main() {
    cr := NewCustomRand(time.Now().UnixNano())
    fmt.Println(cr.RandRange(5, 30))

    fmt.Println(cr.GetCount())
}
  • Now in the main function. Let's create a NewCustomRand with a seed value that is the current time in nanoseconds.
  • This(time.Now().UnixNano()) is a common seed value used with initializing random number generators.
  • We will then test the RandRange method by calling get to optain a random number between 5 and 30 and then printing the results.
  • However before we run the program, Let's cover another interesting case when using embedding.
  • What if we want to call an original Rand method like Rand.intn for example from our custom randomizer.
func main() {
    cr := NewCustomRand(time.Now().UnixNano())
    fmt.Println(cr.RandRange(5, 30))
    fmt.Println(cr.Intn(10))
    fmt.Println(cr.GetCount())
}

Why we use time pacakge?

Computer logic and arithmetic operations are not suitable for generating random values.
The generated random value is a pseudo-random value, not a completely random value.
Currently, computers cannot generate completely random values, so a pseudo-random algorithm is used.
Since the initial value from which a random value is calculated is the same, simply using the rand.Intn () function is created in the same way every time.
This initial value is called a random seed.
To generate a different random value each time the program is executed, the most used method is to set the current time value.

  • Embedding actually makes sense simple because we can just call Intn which belongs to the Rand pointer from the math.rand package are not our custom randomizer directly from our custom randomizer.
  • That is because our custom randomizer embed the math/rand struct pointer and Go will figure out that we need to access a method from the embedded type.
  • This feature is called promoting because methods belonging to the inner type will be promoted to be accessible directly from the outer type.
29
4
1
  • So let's run our code using go run. Let's inspect the results so we can see here that the code works as expected. So we got a random number between 5 and 30 and then we got a random number between 0 and 9.
  • But then we call the cr.GetCount we only got one.
  • the reason for that is because the Intn method called here was not part of the cr.
  • This method belongs to the embedded type of the cr

Embedding gotcha1

If the outer type implements a method that has the same name as a method implemented by the inner type, the outer type method will be given priority.

  • What if we need to write a method belonging to customRand which has the same name as a method from the embedded type like Intn.
  • That's simple. Go understands that the outer type has a method with the same name as a method and the inner type and will give priority to the outer type method.
func (cr *customRand) Intn(n int) int {
    cr.count++
    return cr.Rand.Intn(n) +1
}
  • So for example, if we write a customRand methond called Intn as shown it will take priority over the inner Intn method of the math/rand type.
  • However we can still call the inner type method as shown.
  • Let's make the customRand Intn increment the inner Rand.Intn by 1.
func (cr *customRand) Intn(n int) int {
    fmt.Println("Outer Intn called...")
    cr.count++
    return cr.Rand.Intn(n) +1
}
  • Let's print the message in the standard output indicating that the outer Intn is the method that is being called.
10
Outer Intn called...
5
2
  • Now let's run our code again.
  • So when we run it, we can see here the outer Intn was the one that was called.
  • So we know that it takes priority over the inner Intn
  • when we call this from the outer type.
  • go.dev/play/p/i5IvOMnc3E9

Embedding gotcha 2

Outer type stays as a different type than the inner type, meaning that a value of the inner type can not be assigned to a value of the outer type or vice versa.

  • Another gotcha to look out for when it comes to embedding is the fact that embedding is not equivalent to subclassing as in Java or C# meaning that we can't assign inner type values to outer type values or vice versa.
  • Because the types are still considered different.
var r *rand.Rand = cr
  • So for example, if it creates a Rand struct pointer variable and try to assign a value to it that is of type customRand.

  • This program will show an error citing the fact that the two types are different.

  • So an embedded type cannot be assigned a value of the outer type.

  • If we try the other way around , we still get an error.

    cr = &rand.Rand{}

Readers

Reader => Read data from a slice of bytes

type Reader interface { Read(p []byte) (n int, err error) }

A very common interface in GO
Can be considered a read stream
Examples: File reads, JSON prasing, TCP communications, http requests, and cryptography

  • Now let's talk about two very famous interfaces in the GO language which are IO readers and IO writers.
  • Readers are basically interfaces used to read data from byte slices.
  • This is used everywhere in Go.
  • For example when you read from a file communicate to a network or decode JSON format or send http requests or even do decryption you're effectively using a reader.
  • type that implement reader interfaces can be considered as read streams.
  • So they are similar to read streams in other languages.
  • We will be using readers a lot as we continue on learning GO.

Writers

Writer => Write data to a slice of bytes

Type Writer interface{ Write(p []byte) (n int, err error) }

A very common interface in Go
Can be considered a writer stream
Examples : Write to Json, http responses, and cryptograpy

  • Writers on the other hand are used to write data to byte slices.

  • They are very common in the language.

  • We use them whenever we write to files, write http responses encode some data and so forth.

  • You can think of any type that implements a writer interface as a write stream.

  • We can actually also embed interface type inside other interfaces.

    type ReadWriter interface { Reader Writer }`
  • So what happens if we embed our reader interface and writer interface under a new interface.

  • We get a type that can do both read and write.

  • So this is an equivalent of a stream type that can be both an input and an output and Go actually has a native type that it's just that this type can be found in Go's IO package which defines a lot of the major interfaces in Go related to input and output streams.

  • I would recommend looking at the IO package page at comment io for further reading into what this package can do.

from Mastering Go Programming Section 4 - 19

728x90
반응형

관련글 더보기

댓글 영역