Lesson 13. Methods

After reading lesson 13, you’ll be able to

  • Declare new types
  • Rewrite functions as methods

Methods are like functions that enhance types with additional behavior. Before you can declare a method, you need to declare a new type. This lesson takes the kelvinToCelsius function from lesson 12 and transforms it into a type with methods.

At first it may look like methods are just a different syntax for doing what functions already do, and you would be right. Methods provide another way to organize code, an arguably nicer way for the examples in this lesson. Later lessons, those in unit 5 in particular, demonstrate how methods can be combined with other language features to bring new capabilities.

Consider this

When you type numbers on a calculator versus a typewriter, the expected behavior is quite different. Go has built-in functionality to operate on numbers and text (+) in unique ways, as demonstrated in lesson 10.

What if you want to represent a new type of thing and bundle behaviors with it? A float64 is too generic to adequately represent a thermometer, and a dog’s bark() is entirely different from the bark of a tree. Functions have a place, but types and methods provide another useful way to organize code and represent the world around you.

Before you start on this lesson, look around and consider the types around you and the behaviors they each have.

13.1. Declaring new types

Go declares a number of types, many of which are covered in unit 2. Sometimes those types don’t adequately describe the kind of values you want to hold.

A temperature isn’t a float64, though that may be its underlying representation. Temperature is a measurement in Celsius, Fahrenheit, or Kelvin. Declaring new types not only makes code clearer, it can help prevent errors.

The type keyword declares a new type with a name and an underlying type, as shown in the following listing.

Listing 13.1. A Celsius type: celsius.go
type celsius float64              1

var temperature celsius = 20

fmt.Println(temperature)          2

  • 1 The underlying type is float64.
  • 2 Prints 20

The numeric literal 20, like all numeric literals, is an untyped constant. It can be assigned to a variable of type int, float64, or any other numeric type. The celsius type is a new numeric type with the same behavior and representation as a float64, so the assignment in the previous listing works.

You can also add values to temperature and generally use it as though it were a float64, as shown in the next listing.

Listing 13.2. A celsius type behaves like a float64: celsius-addition.go
type celsius float64

const degrees = 20
var temperature celsius = degrees

temperature += 10

The celsius type is a unique type, not a type alias like those mentioned in lesson 9. If you try to use it with a float64, you’ll get a mismatched types error:

var warmUp float64 = 10
temperature += warmUp              1

  • 1 Invalid operation: mismatched types

To add warmUp, it must first be converted to the celsius type. This version works:

var warmUp float64 = 10
temperature += celsius(warmUp)

Being able to define your own types can be incredibly useful for improving both readability and reliability. The following listing demonstrates that celsius and fahrenheit types can’t accidentally be compared or combined.

Listing 13.3. Types can’t be mixed
type celsius float64
type fahrenheit float64

var c celsius = 20
var f fahrenheit = 20

if c == f {             1
}

c += f                  1

  • 1 Invalid operation: mismatched types celsius and fahrenheit
Quick check 13.1

Q1:

What are some advantages of declaring new types, such as celsius and fahrenheit?

QC 13.1 answer

1:

The new type can better describe the value it contains, such as celsius instead of float64. Having unique types helps avoid silly mistakes, like adding a Fahrenheit value to a Celsius value.

 

13.2. Bring your own types

The previous section declared new celsius and fahrenheit types, bringing the domain of temperatures to the code, while de-emphasizing the underlying storage representation. Whether a temperature is represented as a float64 or float32 says little about the value a variable contains, whereas types like celsius, fahrenheit, and kelvin convey their purpose.

Once you declare a type, you can use it everywhere you would use a predeclared Go type (int, float64, string, and so on), including function parameters and results, as shown in the following listing.

Listing 13.4. Functions with custom types: temperature-types.go
package main

import "fmt"

type celsius float64
type kelvin float64

// kelvinToCelsius converts °K to °C
func kelvinToCelsius(k kelvin) celsius {
    return celsius(k - 273.15)                 1
}

func main() {
    var k kelvin = 294.0                       2
    c := kelvinToCelsius(k)
    fmt.Print(k, "° K is ", c, "° C")          3
}

  • 1 A type conversion is necessary.
  • 2 The argument must be of type kelvin.
  • 3 Prints 294° K is 20.850000000000023° C

The kelvinToCelsius function will only accept an argument of the kelvin type, which can prevent silly mistakes. It won’t accept an argument of the wrong type, such as fahrenheit, kilometers, or even float64. Go is a pragmatic language, so it’s still possible to pass a literal value or untyped constant. Rather than write kelvinToCelsius(kelvin(294)), you can write kelvinToCelsius(294).

The result returned from kelvinToCelsius is of type celsius, not kelvin, so the type must be converted to celsius before it can be returned.

Quick check 13.2

Q1:

Write a celsiusToKelvin function that uses the celsius and kelvin types defined in listing 13.4. Use it to convert 127° C, the surface temperate of the sunlit moon, to degrees Kelvin.

QC 13.2 answer

1:

func celsiusToKelvin(c celsius) kelvin {
    return kelvin(c + 273.15)
}

func main() {
    var c celsius = 127.0
    k := celsiusToKelvin(c)
    fmt.Print(c, "° C is ", k, "° K")     1
}

  • 1 Prints 127° C is 400.15° K

 

13.3. Adding behavior to types with methods

Though this be madness, yet there is method in ‘t.

Shakespeare, Hamlet

For decades classical object-oriented languages have taught that methods belong with classes. Go is different. There are no classes or even objects, really, yet Go has methods. That may seem odd, maybe even a bit crazy, but methods in Go are actually more flexible than in languages of the past.

Functions like kelvinToCelsius, celsiusToFahrenheit, fahrenheitToCelsius, and celsiusToKelvin get the job done, but we can do better. Declaring a few methods in their place will make temperature-conversion code nice and concise.

You can associate methods with any type declared in the same package, but not with predeclared types (int, float64, and so forth). You’ve already seen how to declare a type:

type kelvin float64

The kelvin type has the same behavior as its underlying type, a float64. You can add, multiply, and perform other operations on kelvin values, just like floating-point numbers. Declaring a method to convert kelvin to celsius is as easy as declaring a function. They both begin with the func keyword, and the function body is identical to the method body:

func kelvinToCelsius(k kelvin) celsius {       1
     return celsius(k - 273.15)
}

func (k kelvin) celsius() celsius {            2
     return celsius(k - 273.15)
}

  • 1 kelvinToCelsius function
  • 2 celsius method on the kelvin type

The celsius method doesn’t accept any parameters, but it has something like a parameter before the name. It’s called a receiver, as shown in figure 13.1. Methods and functions can both accept multiple parameters, but methods must have exactly one receiver. Inside the celsius method body, the receiver acts like any other parameter.

Figure 13.1. A method declaration

The syntax to use a method is different than calling a function:

var k kelvin = 294.0
var c celsius

c = kelvinToCelsius(k)          1
c = k.celsius()                 2

  • 1 Calls the kelvinToCelsius function
  • 2 Calls the celsius method

Methods are called with dot notation, which looks like calling a function in another package. But in this case a variable of the correct type is followed by a dot and the method name.

Now that temperature conversion is a method on the kelvin type, a name like kelvinToCelsius is superfluous. A package can only have a single function with a given name, and it can’t be the same name as a type, so a celsius function that returns a celsius type isn’t possible. But each temperature type can provide a celsius method, so for example, the fahrenheit type can be enhanced as follows:

type fahrenheit float64

// celsius converts °F to °C
func (f fahrenheit) celsius() celsius {
    return celsius((f - 32.0) * 5.0 / 9.0)
}

This creates a nice symmetry, where every type of temperature can have a celsius method to convert to Celsius.

Quick check 13.3

Q1:

Identify the receiver in this method declaration: func (f fahrenheit) celsius() celsius

QC 13.3 answer

1:

The receiver is f of type fahrenheit.

 

Summary

  • Declaring your own types can help with readability and reliability.
  • Methods are like functions associated to a type by way of a receiver specified before the method name. Methods can accept multiple parameters and return multiple results, just like functions, but they must always have exactly one receiver. Within the method body, the receiver behaves just like any other parameter.
  • The calling syntax for methods uses dot notation, with a variable of the appropriate type followed by a dot, the method name, and any arguments.

Let’s see if you got this...

Experiment: methods.go

Write a program with celsius, fahrenheit, and kelvin types and methods to convert from any temperature type to any other temperature type.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset