はじめに
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
大丈夫だ、問題ない。
おっと、そういえば課題の注意文に問いがありましたね。
注意:
https://go-tour-jp.appspot.com/methods/20Error
メソッドの中で、fmt.Sprint(e)
を呼び出すことは、無限ループのプログラムになることでしょう。最初にfmt.Sprint(float64(e))
としてe
を変換しておくことで、これを避けることができます。なぜでしょうか?
なんでかな〜?なんでだろ〜?
確かに fmt.Sprintf(float64(e))
は出力されるが fmt.Sprint(e)
は何も出力されない。
答えは、fmt
パッケージは error
インターフェースを確認するので Error
メソッドが自動的に実行される、つまり、型を float64
に変換することで無限に Error
メソッドが実行されるのを回避できたということですね。
以上です。
おわりに
Go だと ぬるぽ
じゃなくて にるぽ
だよなってクッソどうでもいいこと考えてた。
■━⊂( ・∀・) 彡 ガッ☆`Д´)ノ