関数
関数
関数は日常生活の「自動販売機」のようなものです — パラメータを入れると、あらかじめ定められたルールに従って処理し、結果を返します。Goでは、関数は第一級市民です:変数に代入でき、引数として渡せ、値として返せ、名前がないこともあります。関数をマスターすることは、エレガントなGoコードを書くための重要なステップです。
1. コアコンセプト
| コンセプト | 説明 |
|---|---|
func定義 |
funcキーワードで関数を宣言し、パラメータと戻り値の型をサポート |
| 複数戻り値 | Goの関数は複数の値を返せます。結果 + エラーでよく使用 |
| 名前付き戻り値 | 戻り値に名前を付け、関数本体で直接使用でき、returnで自動的に返す |
| 可変長パラメータ | ...Typeで任意の数の引数を受け取る |
| 匿名関数 | 名前のない関数。即座の呼び出しや変数への代入によく使用 |
| クロージャ | 外部変数をキャプチャする匿名関数。クロージャを形成 |
init関数 |
各パッケージは複数のinit関数を持て、プログラム起動時に自動実行 |
defer |
遅延実行。関数が返る前にスタック順序で実行 |
2. 基本構文/使い方
関数定義
GO
// 基本的な関数定義
func functionName(parameterList) returnType {
// 関数本体
return value
}
// パラメータなし、戻り値なし
func sayHello() {
fmt.Println("Hello!")
}
// パラメータと戻り値付き
func add(a int, b int) int {
return a + b
}
// パラメータ型が同じ場合の省略
func add(a, b int) int {
return a + b
}
複数戻り値
GO
// 2つの値を返す:結果とエラー
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, fmt.Errorf("除数はゼロにできません")
}
return a / b, nil
}
// 呼び出し時に両方の戻り値を受け取る
result, err := divide(10, 3)
名前付き戻り値
GO
// 名前付き戻り値:関数本体で変数名を直接使用
func rectangleInfo(length, width float64) (area, perimeter float64) {
area = length * width
perimeter = 2 * (length + width)
return // 名前付き戻り値を自動的に返す
}
可変長パラメータ
GO
// 可変長パラメータ:...Typeで任意の数の引数を受け取る
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
// 呼び出し
sum(1, 2, 3) // 6
sum(1, 2, 3, 4, 5) // 15
匿名関数とクロージャ
GO
// 匿名関数を変数に代入
greet := func(name string) string {
return "Hello, " + name
}
// 即座に呼び出される関数
func() {
fmt.Println("即座に実行")
}()
// クロージャ:外部変数をキャプチャ
func counter() func() int {
count := 0
return func() int {
count++ // 外部のcount変数をキャプチャ
return count
}
}
init関数
GO
// init関数:プログラム起動時に自動実行。手動呼び出しは不要
// 各パッケージは複数のinit関数を持てる
func init() {
fmt.Println("init関数が実行されました")
}
defer
GO
// defer:遅延実行。関数が返る前にLIFO(後入れ先出し)順序で実行
func readFile(filename string) {
f, _ := os.Open(filename)
defer f.Close() // 関数終了前にファイルを閉じる
// ... ファイル読み取り
}
💡 ヒント: Goでは、全ての関数引数は値渡しです。スライス、map、チャネル、ポインタなどは参照のコピーを渡しますが、それらが指すデータは共有されます。
💡 ヒント:
defer文の引数は宣言時に評価され、実行時ではありません。これはよくある落とし穴です。
💡 ヒント:
init関数は他の関数から呼び出すことはできず、mainの前に自動的に実行されます。複数のinit関数がある場合、ソースファイルのアルファベット順に実行されます。
3. 基本構文/使い方(コード例)
例1:基本的な使い方(難易度⭐)
目的: 基本的な関数定義、呼び出し、複数戻り値を学びます。
GO
package main
import (
"errors"
"fmt"
)
// greetは名前を受け取り、挨拶を返します
func greet(name string) string {
return "Hello, " + name + "!"
}
// addは2つの整数の合計を返します
func add(a, b int) int {
return a + b
}
// swapは2つの文字列をスワップします(複数戻り値のデモ)
func swap(a, b string) (string, string) {
return b, a
}
// divideはエラー処理をデモンストレーションします
func divide(a, b float64) (float64, error) {
if b == 0 {
return 0, errors.New("除数はゼロにできません")
}
return a / b, nil
}
func main() {
// 単一戻り値の関数を呼び出し
fmt.Println(greet("Alice")) // 出力:Hello, Alice!
fmt.Println(add(3, 5)) // 出力:8
// 複数戻り値の関数を呼び出し
x, y := swap("hello", "world")
fmt.Println(x, y) // 出力:world hello
// エラー処理付きの関数を呼び出し
result, err := divide(10, 3)
if err != nil {
fmt.Println("エラー:", err)
} else {
fmt.Printf("10 / 3 = %.2f\n", result) // 出力:10 / 3 = 3.33
}
// ゼロ除算エラーのテスト
_, err = divide(10, 0)
if err != nil {
fmt.Println("エラー:", err) // 出力:エラー: 除数はゼロにできません
}
}
出力:
TEXT
Hello, Alice!
8
world hello
10 / 3 = 3.33
エラー: 除数はゼロにできません
要点:
funcキーワードで関数を定義し、パラメータ型はパラメータ名の後に記述します- 複数戻り値はカンマ区切りで、呼び出し時に複数の変数で受け取ります
- エラー処理はGoのコアパターンです:
(result, error)を返します
例2:中級的な使い方(難易度⭐⭐)
目的: 名前付き戻り値、可変長パラメータ、匿名関数、クロージャを学びます。
GO
package main
import "fmt"
// ============ 名前付き戻り値 ============
// rectangleは長方形の面積と周長を計算します(名前付き戻り値を使用)
func rectangle(length, width float64) (area, perimeter float64) {
area = length * width // 名前付き変数を直接使用
perimeter = 2 * (length + width)
return // 名前付き戻り値を自動的に返す。return area, perimeterと書く必要なし
}
// ============ 可変長パラメータ ============
// sumは任意の数の整数の合計を計算します
func sum(nums ...int) int {
total := 0
for _, n := range nums {
total += n
}
return total
}
// averageは平均を計算し、可変長パラメータと他のパラメータの混在をデモンストレーションします
func average(first float64, rest ...float64) float64 {
total := first
count := 1.0
for _, v := range rest {
total += v
count++
}
return total / count
}
// ============ 匿名関数 ============
// applyは整数に関数を適用し、結果を返します
func apply(n int, f func(int) int) int {
return f(n)
}
func main() {
// 名前付き戻り値
area, perimeter := rectangle(5, 3)
fmt.Printf("面積: %.1f、周長: %.1f\n", area, perimeter)
// 出力:面積: 15.0、周長: 16.0
// 可変長パラメータ
fmt.Println("sum(1,2,3):", sum(1, 2, 3)) // 6
fmt.Println("sum(1,2,3,4,5):", sum(1, 2, 3, 4, 5)) // 15
// 可変長パラメータ:スライスの渡し方
nums := []int{10, 20, 30}
fmt.Println("sum(nums...):", sum(nums...)) // 60(...でスライスを展開)
// 可変長パラメータと他のパラメータの混在
fmt.Printf("average: %.2f\n", average(10, 20, 30)) // 20.00
// 引数としての匿名関数
double := func(n int) int { return n * 2 }
square := func(n int) int { return n * n }
fmt.Println("apply(5, double):", apply(5, double)) // 10
fmt.Println("apply(5, square):", apply(5, square)) // 25
// 匿名関数の即座呼び出し
msg := "Hello"
func(m string) {
fmt.Println(m)
}(msg)
// ============ クロージャ ============
// counterはカウンタークロージャを返します
counter := func() func() int {
count := 0
return func() int {
count++ // 外部変数countをキャプチャ
return count
}
}()
fmt.Println("counter:", counter()) // 1
fmt.Println("counter:", counter()) // 2
fmt.Println("counter:", counter()) // 3
// アキュムレーターを実装するクロージャ
adder := func() func(int) int {
sum := 0
return func(n int) int {
sum += n
return sum
}
}()
fmt.Println("adder(10):", adder(10)) // 10
fmt.Println("adder(20):", adder(20)) // 30
fmt.Println("adder(30):", adder(30)) // 60
}
出力:
TEXT
面積: 15.0、周長: 16.0
sum(1,2,3): 6
sum(1,2,3,4,5): 15
sum(nums...): 60
average: 20.00
apply(5, double): 10
apply(5, square): 25
Hello
counter: 1
counter: 2
counter: 3
adder(10): 10
adder(20): 30
adder(30): 60
要点:
- 名前付き戻り値はコードをより明確にしますが、過度に使用すると可読性が低下する場合があります
- 可変長パラメータ
...Typeは関数の最後のパラメータでなければなりません - スライスを可変長パラメータに渡すには、
slice...で展開します - クロージャは外部変数の状態を「記憶」し、各呼び出しで同じ変数を共有します
例3:総合応用(難易度⭐⭐⭐)
目的: リアルワールドの関数デザインパターン:ファンクショナルオプションパターン、ミドルウェアチェーン、エラー処理付きdefer。
GO
package main
import (
"fmt"
"strings"
"time"
)
// ============ ファンクショナルオプションパターン ============
// サーバー設定
type Server struct {
host string
port int
timeout time.Duration
maxConn int
}
// Optionは設定オプションの関数型
type Option func(*Server)
// WithHostはホストアドレスを設定します
func WithHost(host string) Option {
return func(s *Server) {
s.host = host
}
}
// WithPortはポートを設定します
func WithPort(port int) Option {
return func(s *Server) {
s.port = port
}
}
// WithTimeoutはタイムアウト時間を設定します
func WithTimeout(timeout time.Duration) Option {
return func(s *Server) {
s.timeout = timeout
}
}
// WithMaxConnは最大接続数を設定します
func WithMaxConn(max int) Option {
return func(s *Server) {
s.maxConn = max
}
}
// NewServerはオプションパターンでサーバーを作成します
func NewServer(opts ...Option) *Server {
// デフォルト設定
s := &Server{
host: "localhost",
port: 8080,
timeout: 30 * time.Second,
maxConn: 100,
}
// 全てのオプションを適用
for _, opt := range opts {
opt(s)
}
return s
}
// StringはStringerインターフェースを実装します
func (s *Server) String() string {
return fmt.Sprintf("Server{host:%s, port:%d, timeout:%v, maxConn:%d}",
s.host, s.port, s.timeout, s.maxConn)
}
// ============ ミドルウェアチェーンパターン ============
// Handlerは処理関数型
type Handler func(string) string
// Middlewareはミドルウェア型
type Middleware func(Handler) Handler
// loggingMiddlewareは入力と出力をログに記録します
func loggingMiddleware(next Handler) Handler {
return func(input string) string {
fmt.Printf("[ログ] 入力: %s\n", input)
result := next(input)
fmt.Printf("[ログ] 出力: %s\n", result)
return result
}
}
// upperMiddlewareは入力を大文字に変換します
func upperMiddleware(next Handler) Handler {
return func(input string) string {
return next(strings.ToUpper(input))
}
}
// wrapMiddlewareは出力を括弧で囲みます
func wrapMiddleware(next Handler) Handler {
return func(input string) string {
return "【" + next(input) + "】"
}
}
// chainは複数のミドルウェアをチェーンに結合します
func chain(handler Handler, middlewares ...Middleware) Handler {
// 後ろから前にラップし、最初のミドルウェアが最初に実行されるようにする
for i := len(middlewares) - 1; i >= 0; i-- {
handler = middlewares[i](handler)
}
return handler
}
// ============ 実践的なdefer:リソースクリーンアップとエラーリカバリ ============
// simulateDBはデータベース操作をシミュレートします
func simulateDB() (err error) {
fmt.Println("1. データベース接続を取得中")
// deferはLIFO順序で実行
defer fmt.Println("5. データベース接続を解放(最後に実行)")
defer fmt.Println("4. 操作ログを書き込み中")
defer fmt.Println("3. カーソルを閉じ中")
fmt.Println("2. クエリを実行中")
// panicリカバリをシミュレート
defer func() {
if r := recover(); r != nil {
fmt.Printf(" [リカバリ] panicをキャッチ: %v\n", r)
err = fmt.Errorf("操作失敗: %v", r)
}
}()
// panicをシミュレート
panic("接続タイムアウト")
// 以下のコードは実行されない
// fmt.Println("クエリ完了")
// return nil
}
func main() {
// ===== ファンクショナルオプションパターン =====
fmt.Println("=== ファンクショナルオプションパターン ===")
// デフォルト設定を使用
s1 := NewServer()
fmt.Println(s1)
// カスタム設定
s2 := NewServer(
WithHost("0.0.0.0"),
WithPort(9090),
WithTimeout(60*time.Second),
WithMaxConn(500),
)
fmt.Println(s2)
// ===== ミドルウェアチェーン =====
fmt.Println("\n=== ミドルウェアチェーン ===")
// 基本ハンドラ
handler := func(s string) string {
return "処理済み: " + s
}
// ミドルウェアを結合
final := chain(handler, loggingMiddleware, upperMiddleware, wrapMiddleware)
result := final("hello world")
fmt.Println("最終結果:", result)
// ===== deferとエラーリカバリ =====
fmt.Println("\n=== defer実行順序 ===")
err := simulateDB()
if err != nil {
fmt.Println("メイン関数がエラーをキャッチ:", err)
}
}
出力:
TEXT
=== ファンクショナルオプションパターン ===
Server{host:localhost, port:8080, timeout:30s, maxConn:100}
Server{host:0.0.0.0, port:9090, timeout:1m0s, maxConn:500}
=== ミドルウェアチェーン ===
[ログ] 入力: HELLO WORLD
[ログ] 出力: 【処理済み: HELLO WORLD】
最終結果: 【処理済み: HELLO WORLD】
=== defer実行順序 ===
1. データベース接続を取得中
2. クエリを実行中
[リカバリ] panicをキャッチ: 接続タイムアウト
3. カーソルを閉じ中
4. 操作ログを書き込み中
5. データベース接続を解放(最後に実行)
メイン関数がエラーをキャッチ: 操作失敗: 接続タイムアウト
要点:
- ファンクショナルオプションパターン:クロージャを返すことで柔軟な設定を実現。Goコミュニティで広く使われるデザインパターン
- ミドルウェアチェーン:第一級市民としての関数は結合・渡しができ、処理パイプラインを形成
- defer実行順序:LIFO(後入れ先出し)。最初に宣言されたdeferが最後に実行
- panic/recover:
defer+recoverでpanicをキャッチし、プログラムのクラッシュを防止
3. よくあるユースケース
ケース1:エラー処理のカプセル化
実際のプロジェクトでは、統一されたエラー処理ロジックをカプセル化する必要があります:
GO
package main
import (
"fmt"
"strconv"
)
// Resultは統一された結果ラッパー
type Result struct {
Data interface{}
Err error
}
// parseJSONはJSONパースをシミュレートし、結果とエラーを返します
func parseJSON(jsonStr string) (map[string]interface{}, error) {
// 簡略化デモ:実際のプロジェクトではencoding/jsonを使用
if jsonStr == "" {
return nil, fmt.Errorf("JSON文字列は空にできません")
}
return map[string]interface{}{"key": "value"}, nil
}
// parseIntは文字列を安全に整数に変換します
func parseInt(s string) (int, error) {
n, err := strconv.Atoi(s)
if err != nil {
return 0, fmt.Errorf("整数 '%s' のパースに失敗: %w", s, err)
}
return n, nil
}
// withRetryは関数をリトライします
func withRetry(attempts int, f func() error) error {
var err error
for i := 0; i < attempts; i++ {
err = f()
if err == nil {
return nil
}
fmt.Printf(" 試行 %d 失敗: %v\n", i+1, err)
}
return fmt.Errorf("%d回の試行後に失敗: %w", attempts, err)
}
func main() {
// エラー処理
result, err := parseJSON(`{"name": "test"}`)
if err != nil {
fmt.Println("パースエラー:", err)
return
}
fmt.Println("パース結果:", result)
// 安全な型変換
num, err := parseInt("123")
if err != nil {
fmt.Println(err)
return
}
fmt.Println("変換結果:", num)
// リトライメカニズム
attempt := 0
err = withRetry(3, func() error {
attempt++
if attempt < 3 {
return fmt.Errorf("シミュレーション失敗")
}
return nil
})
if err != nil {
fmt.Println("最終失敗:", err)
} else {
fmt.Println("最終成功!")
}
}
ケース2:関数をデータ処理パイプラインとして使用
GO
package main
import (
"fmt"
"strings"
)
// Transformはデータ変換関数型
type Transform func(string) string
// Pipelineはデータ処理パイプライン
type Pipeline struct {
steps []Transform
}
// NewPipelineは新しいパイプラインを作成します
func NewPipeline() *Pipeline {
return &Pipeline{}
}
// Addは処理ステップを追加します
func (p *Pipeline) Add(steps ...Transform) *Pipeline {
p.steps = append(p.steps, steps...)
return p
}
// Executeはパイプラインを実行します
func (p *Pipeline) Execute(input string) string {
result := input
for _, step := range p.steps {
result = step(result)
}
return result
}
func main() {
// 変換関数を定義
trim := func(s string) string { return strings.TrimSpace(s) }
lower := func(s string) string { return strings.ToLower(s) }
replace := func(s string) string { return strings.ReplaceAll(s, " ", "-") }
prefix := func(s string) string { return "processed-" + s }
// 処理パイプラインを構築
pipeline := NewPipeline().
Add(trim, lower, replace, prefix)
// パイプラインを実行
result := pipeline.Execute(" Hello World Go ")
fmt.Println(result) // 出力:processed-hello-world-go
// 複数の入力にパイプラインを再利用
inputs := []string{" Foo Bar ", " HELLO ", " Go Lang "}
for _, input := range inputs {
fmt.Printf("%-20s => %s\n", input, pipeline.Execute(input))
}
}
❓ よくある質問
質問1:関数パラメータは値渡しですか、参照渡しですか?
回答: Goでは、全ての関数パラメータは値渡しです。ただし、スライス、map、チャネル、ポインタなどの型は「参照のコピー」を渡します — それらが指すデータの変更は元のデータに影響しますが、再代入は元の変数に影響しません。
GO
package main
import "fmt"
// modifySliceはスライスの内容を変更します(元のスライスに影響)
func modifySlice(s []int) {
s[0] = 999 // 要素の変更は元のスライスに影響
fmt.Println("関数内:", s)
}
// reassignSliceはスライスを再代入します(元のスライスに影響しない)
func reassignSlice(s []int) {
s = append(s, 4, 5, 6) // appendは新しい基本配列を作成する可能性がある
fmt.Println("関数内:", s)
}
func main() {
nums := []int{1, 2, 3}
modifySlice(nums)
fmt.Println("関数外:", nums) // [999 2 3]、元のスライスが変更された
reassignSlice(nums)
fmt.Println("関数外:", nums) // [999 2 3]、元のスライスは変更されない
}
質問2:deferの引数はいつ評価されますか?
回答: defer文の引数は宣言時に評価され、実行時ではありません。
GO
package main
import "fmt"
func main() {
x := 10
// defer引数は宣言時に評価、x = 10
defer fmt.Println("defer内のx =", x)
x = 20
fmt.Println("変更後のx =", x)
// defer実行時に評価が必要な場合はクロージャを使用
defer func() {
fmt.Println("クロージャdefer内のx =", x) // x = 20
}()
x = 30
}
出力:
TEXT
変更後のx = 20
クロージャdefer内のx = 30
defer内のx = 10
質問3:パッケージに複数のinit関数は持てますか?実行順序は?
回答: はい。パッケージ(甚至是単一ファイル)には複数のinit関数を持てます。実行順序:
- まず、インポートされたパッケージの
init関数を実行 - 同じパッケージ内では、ソースファイルのアルファベット順に実行
- 同じファイル内では、
init関数の出現順に実行
GO
package main
import "fmt"
func init() {
fmt.Println("最初のinit")
}
func init() {
fmt.Println("2番目のinit")
}
func main() {
fmt.Println("main関数")
}
出力:
TEXT
最初のinit
2番目のinit
main関数
質問4:オプションパラメータはどう実装しますか?
回答: Goはオプションパラメータを直接サポートしていません。以下の3つの方法が一般的に使用されます:
GO
package main
import "fmt"
// 方法1:構造体を使用
type Config struct {
Host string
Port int
Debug bool
}
func connect(cfg Config) {
fmt.Printf("%s:%dに接続中 (debug=%v)\n", cfg.Host, cfg.Port, cfg.Debug)
}
// 方法2:ファンクショナルオプションパターン(推奨)
type Option func(*Config)
func WithHost(host string) Option {
return func(c *Config) { c.Host = host }
}
func WithPort(port int) Option {
return func(c *Config) { c.Port = port }
}
func WithDebug(debug bool) Option {
return func(c *Config) { c.Debug = debug }
}
func newConnect(opts ...Option) {
cfg := &Config{Host: "localhost", Port: 8080, Debug: false}
for _, opt := range opts {
opt(cfg)
}
fmt.Printf("%s:%dに接続中 (debug=%v)\n", cfg.Host, cfg.Port, cfg.Debug)
}
func main() {
// 構造体方式
connect(Config{Host: "127.0.0.1", Port: 3306})
// オプションパターン
newConnect()
newConnect(WithHost("127.0.0.1"), WithPort(3306), WithDebug(true))
}
📖 まとめ
- 関数定義:
funcキーワードを使用。パラメータ型はパラメータ名の後に記述。複数戻り値をサポート - 名前付き戻り値:戻り値に名前を付け、関数本体で直接使用でき、
returnで自動的に返す - 可変長パラメータ:
...Typeで任意の数の引数を受け取り、最後のパラメータでなければならない - 匿名関数:名前のない関数。変数に代入や即座の呼び出しが可能
- クロージャ:外部変数をキャプチャする匿名関数。変数のライフタイムが延長
- init関数:プログラム起動時に自動実行。手動呼び出し不可。初期化作業に適している
- defer:LIFO順序の遅延実行。引数は宣言時に評価
- 関数は第一級市民:代入、渡し、返しが可能で、柔軟なデザインパターンを実現
📝 演習
演習1(⭐):基本的な関数演習
要件: 以下の関数を書き、テストしてください:
max(a, b int) int— 2つの整数の大きい方を返すisEven(n int) bool— 整数が偶数かチェックswap(a, b *int)— ポインタを使って2つの整数をスワップ
GO
// 参考フレームワーク
package main
import "fmt"
func max(a, b int) int {
// ここに実装
return 0
}
func isEven(n int) bool {
// ここに実装
return false
}
func swap(a, b *int) {
// ここに実装(ヒント:ポインタを使用)
}
func main() {
fmt.Println(max(3, 5)) // 5が出力されるはず
fmt.Println(isEven(4)) // trueが出力されるはず
fmt.Println(isEven(7)) // falseが出力されるはず
x, y := 10, 20
swap(&x, &y)
fmt.Println(x, y) // 20 10が出力されるはず
}
演習2(⭐⭐):可変長パラメータとクロージャ
要件:
filter(nums []int, predicate func(int) bool) []intを書き、条件を満たす要素をフィルタリングmakeMultiplier(factor int) func(int) intを書き、乗算クロージャを返すfilterとクロージャを使って、スライスから偶数を全て選択
GO
// 参考フレームワーク
package main
import "fmt"
func filter(nums []int, predicate func(int) bool) []int {
// 実装:numsをイテレーションし、predicateを満たす要素を結果スライスに追加
return nil
}
func makeMultiplier(factor int) func(int) int {
// 実装:入力にfactorを掛けるクロージャを返す
return nil
}
func main() {
nums := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
// 偶数をフィルタリング
evens := filter(nums, func(n int) bool {
return n%2 == 0
})
fmt.Println("偶数:", evens) // [2 4 6 8 10]
// 5より大きい数をフィルタリング
big := filter(nums, func(n int) bool {
return n > 5
})
fmt.Println("5より大きい:", big) // [6 7 8 9 10]
// クロージャで乗算器を作成
double := makeMultiplier(2)
triple := makeMultiplier(3)
fmt.Println("double(5):", double(5)) // 10
fmt.Println("triple(5):", triple(5)) // 15
}
演習3(⭐⭐⭐):実践プロジェクト — コマンドパターン付き電卓
要件: コマンド登録をサポートする電卓システムを実装:
- コマンドマップ
map[string]func([]int) intを持つCalculator構造体を定義 Register(name string, op func([]int) int)でコマンドを登録Execute(name string, args []int) (int, error)でコマンドを実行- 以下のコマンドを登録:
add(合計)、mul(積)、max(最大値) init関数で全コマンドを自動登録
GO
// 参考フレームワーク
package main
import (
"errors"
"fmt"
)
type Calculator struct {
// ここにフィールドを定義
}
func NewCalculator() *Calculator {
// ここに実装
return nil
}
func (c *Calculator) Register(name string, op func([]int) int) {
// ここに実装
}
func (c *Calculator) Execute(name string, args []int) (int, error) {
// 実装:コマンドを探して実行。見つからない場合はエラーを返す
return 0, nil
}
func (c *Calculator) ListCommands() []string {
// 実装:登録済みの全コマンド名を返す
return nil
}
func main() {
calc := NewCalculator()
// コマンドを登録
calc.Register("add", func(args []int) int {
sum := 0
for _, v := range args {
sum += v
}
return sum
})
calc.Register("mul", func(args []int) int {
product := 1
for _, v := range args {
product *= v
}
return product
})
calc.Register("max", func(args []int) int {
if len(args) == 0 {
return 0
}
m := args[0]
for _, v := range args[1:] {
if v > m {
m = v
}
}
return m
})
// テスト
tests := []struct {
cmd string
args []int
}{
{"add", []int{1, 2, 3, 4, 5}},
{"mul", []int{2, 3, 4}},
{"max", []int{10, 3, 7, 9, 1}},
}
for _, t := range tests {
result, err := calc.Execute(t.cmd, t.args)
if err != nil {
fmt.Printf("エラー: %v\n", err)
} else {
fmt.Printf("%s(%v) = %d\n", t.cmd, t.args, result)
}
}
// 全コマンドを一覧表示
fmt.Println("利用可能なコマンド:", calc.ListCommands())
// 存在しないコマンドをテスト
_, err := calc.Execute("sqrt", []int{16})
if err != nil {
fmt.Println("エラー:", err)
}
}
期待される出力:
TEXT
add([1 2 3 4 5]) = 15
mul([2 3 4]) = 24
max([10 3 7 9 1]) = 10
利用可能なコマンド: [add mul max]
エラー: 不明なコマンド: sqrt



