A Tour of Go の Exercise: Errors 解答例

はじめに

Go 言語基礎文法最速マスターにて出題されている演習課題「Exercise: Errors(エラー)」の答えに辿り着くまでの手順と解答例の解説を記事にしてみました。fmt.Sprint(e) が無限ループに陥る理由などについても簡単に説明しています。

課題

課題の目的は A Tour of Go の Exercise: Loops and Functions 解答例 で作った Sqrt 関数をコピーして、負の値が与えられた時にエラーを返すように実装することです。

package main

import (
	"fmt"
)

func Sqrt(x float64) (float64, error) {
	return 0, nil
}

func main() {
	fmt.Println(Sqrt(2))
	fmt.Println(Sqrt(-2))
}

解答例

package main

import (
	"fmt"
	"math"
)

const cd = 1e-9  // close difference

type ErrNegativeSqrt float64

func (e ErrNegativeSqrt) Error() string {
	return fmt.Sprintf("cannot Sqrt negative number: %f", e)
}

func Sqrt(x float64) (float64, error) {
	if x < 0 {
		return 0, ErrNegativeSqrt(x)
	}

	z, d := x / 2, 0.

	for i := 1; i <= 10; i++ {
		z -= (z * z - x) / (2 * z)
		if math.Abs(z - d) < cd {
			break
		}
		d = z
	}
	return z, nil
}

func main() {
	fmt.Println(Sqrt(2))
	fmt.Println(Sqrt(-2))
}

解説

初めに、課題のソースコードを読み解きます。

3 〜 4 行目、fmf パッケージを Imports しています。Sqrt 関数で math.Abs 関数を使うので、後で math パッケージもインポートする必要がありますね。

import (
	"fmt"
)

7 〜 9 行目、Sqrt 関数の戻り値が Multiple results になっています。

func Sqrt(x float64) (float64, error) {
	return 0, nil
}

11 〜 14 行目、Sqrt 関数に正数と負数を渡して、戻り値を fmt.Println 関数で出力していますね。

func main() {
	fmt.Println(Sqrt(2))
	fmt.Println(Sqrt(-2))
}

さて、課題の文章に 3 点の指示がありますね。

1 点目、下記の新しい型を作成してくださいとあります。

type ErrNegativeSqrt float64

A Tour of Go の Exercise: Stringers 解答例 でやったように type で float64 型を ErrNegativeSqrt という名前で 型宣言 しているだけですね。

2 点目、ErrNegativeSqrt(-2).Error()"cannot Sqrt negative number: -2" を返すメソッドを定義してくださいとあります。

func (e ErrNegativeSqrt) Error() string

これは Errors で説明あったように、Go はエラーの状態を error 値で表現していて、error 型は fmt.Stringer に似た下記のような組み込みのインターフェースです。

type error interface {
	Error() string
}

つまり、A Tour of Go の Exercise: Stringers 解答例 でやった時と同じように ErrNegativeSqrt 型に下記のような Error() string メソッドを実装するだけですね。

func (e ErrNegativeSqrt) Error() string {
	return fmt.Sprintf("cannot Sqrt negative number: %f", e)
}

fmt.Sprintf 関数で %f という書式指定子を指定すると浮動小数点数で出力してくれます。

3 点目、Sqrt 関数に負の値が与えられた時に ErrNegativeSqrt の値を返すようにしてくださいとあります。

これは、下記のように Sqrt 関数に if 文で負数だった場合は ErrNegativeSqrt を返すようにすれば良いだけですね。

if x < 0 {
	return 0, ErrNegativeSqrt(x)
}

その後、fmt.Println 関数に値が渡されるのですが、Errors の説明であったように fmt パッケージは変数を文字列で出力する際に error インターフェースを確認します。

そして、呼び出し元はエラーが nil かどうかを確認することでエラーハンドリング(エラーを捕捉する)します。

なので Sqrt 関数が正常終了した時は nil を返してあげましょう。

return z, nil

因みに、Go には try, catch, throw などの例外処理はありませんが、error インターフェースや後に学ぶ panic, defer, recover などで処理することができます。

今までの話をまとめて実装するとこんな感じです。

package main

import (
	"fmt"
	"math"
)

const cd = 1e-9  // close difference

type ErrNegativeSqrt float64

func (e ErrNegativeSqrt) Error() string {
	return fmt.Sprintf("cannot Sqrt negative number: %f", e)
}

func Sqrt(x float64) (float64, error) {
	if x < 0 {
		return 0, ErrNegativeSqrt(x)
	}

	z, d := x / 2, 0.

	for i := 1; i <= 10; i++ {
		z -= (z * z - x) / (2 * z)
		if math.Abs(z - d) < cd {
			break
		}
		d = z
	}
	return z, nil
}

func main() {
	fmt.Println(Sqrt(2))
	fmt.Println(Sqrt(-2))
}

実行すると下記のように出力されます。

1.4142135623730951 <nil>
0 cannot Sqrt negative number: -2.000000

大丈夫だ、問題ない。

おっと、そういえば課題の注意文に問いがありましたね。

注意: Error メソッドの中で、fmt.Sprint(e) を呼び出すことは、無限ループのプログラムになることでしょう。最初に fmt.Sprint(float64(e)) として e を変換しておくことで、これを避けることができます。なぜでしょうか?

https://go-tour-jp.appspot.com/methods/20

なんでかな〜?なんでだろ〜?

確かに fmt.Sprintf(float64(e)) は出力されるが fmt.Sprint(e) は何も出力されない。

答えは、fmt パッケージは error インターフェースを確認するので Error メソッドが自動的に実行される、つまり、型を float64 に変換することで無限に Error メソッドが実行されるのを回避できたということですね。

以上です。

おわりに

Go だと ぬるぽ じゃなくて にるぽ だよなってクッソどうでもいいこと考えてた。

■━⊂( ・∀・) 彡 ガッ☆`Д´)ノ