課題6: 関数とメソッドライブラリ

課題概要

関数とメソッドを使って、実用的な数学ライブラリとビルダーパターンを実装します。複数戻り値、メソッドチェーン、高階関数を活用します。

マンダトリー要件

要件1: 数学関数ライブラリ

基本的な数学関数を実装してください。

ファイル: mathlib/basic.go

package mathlib

import (
    "errors"
    "math"
)

var (
    ErrDivisionByZero   = errors.New("division by zero")
    ErrNegativeInput    = errors.New("negative input")
    ErrInvalidRange     = errors.New("invalid range")
)

// SafeDivide は安全な除算(エラーを返す)
func SafeDivide(a, b float64) (float64, error) {
    // TODO: 実装してください
    // - b が 0 の場合は ErrDivisionByZero を返す
}

// DivMod は除算と剰余を同時に返す
func DivMod(a, b int) (quotient, remainder int, err error) {
    // TODO: 実装してください
}

// Sqrt は平方根を返す(負数はエラー)
func Sqrt(x float64) (float64, error) {
    // TODO: 実装してください
}

// MinMax は最小値と最大値を返す
func MinMax(numbers ...float64) (min, max float64, err error) {
    // TODO: 実装してください
    // - 引数が空の場合は ErrInvalidRange を返す
}

// Stats は平均、中央値、標準偏差を返す
func Stats(numbers ...float64) (mean, median, stddev float64, err error) {
    // TODO: 実装してください
}

要件2: 関数型ユーティリティ

高階関数を使ったユーティリティを実装してください。

ファイル: funcutil/functional.go

package funcutil

// Transform型: 変換関数
type Transform func(int) int

// Predicate型: 判定関数
type Predicate func(int) bool

// Reducer型: 集約関数
type Reducer func(int, int) int

// Map はスライスの各要素を変換
func Map(numbers []int, fn Transform) []int {
    // TODO: 実装してください
}

// Filter はスライスをフィルタリング
func Filter(numbers []int, fn Predicate) []int {
    // TODO: 実装してください
}

// Reduce はスライスを単一の値に集約
func Reduce(numbers []int, initial int, fn Reducer) int {
    // TODO: 実装してください
}

// Compose は複数の変換関数を合成
func Compose(fns ...Transform) Transform {
    // TODO: 実装してください
    // 例: Compose(f, g, h)(x) = f(g(h(x)))
}

// Pipeline は値を複数の関数に順次適用
func Pipeline(value int, fns ...Transform) int {
    // TODO: 実装してください
    // 例: Pipeline(x, f, g, h) = h(g(f(x)))
}

要件3: 文字列ビルダー(メソッドチェーン)

メソッドチェーンを使った文字列ビルダーを実装してください。

ファイル: strutil/builder.go

package strutil

import (
    "strings"
)

type Builder struct {
    buffer []string
    indent int
}

// NewBuilder はBuilderを作成
func NewBuilder() *Builder {
    // TODO: 実装してください
}

// Append は文字列を追加(チェーン可能)
func (b *Builder) Append(s string) *Builder {
    // TODO: 実装してください
}

// AppendLine は文字列と改行を追加(チェーン可能)
func (b *Builder) AppendLine(s string) *Builder {
    // TODO: 実装してください
}

// AppendFormat は書式付きで追加(チェーン可能)
func (b *Builder) AppendFormat(format string, args ...interface{}) *Builder {
    // TODO: 実装してください
}

// Indent はインデントレベルを増やす
func (b *Builder) Indent() *Builder {
    // TODO: 実装してください
    // インデントレベルを1増やす
}

// Dedent はインデントレベルを減らす
func (b *Builder) Dedent() *Builder {
    // TODO: 実装してください
    // インデントレベルを1減らす(0未満にはしない)
}

// IndentedLine はインデント付きで行を追加
func (b *Builder) IndentedLine(s string) *Builder {
    // TODO: 実装してください
    // 現在のインデントレベル * 2スペースを前に追加
}

// Clear はバッファをクリア
func (b *Builder) Clear() *Builder {
    // TODO: 実装してください
}

// String は結果の文字列を返す
func (b *Builder) String() string {
    // TODO: 実装してください
}

// Length は現在の長さを返す
func (b *Builder) Length() int {
    // TODO: 実装してください
}

要件4: 計算機クラス

メソッドを使った計算機を実装してください。

ファイル: calculator/calculator.go

package calculator

import (
    "errors"
    "math"
)

type Calculator struct {
    result float64
    history []string
}

// New は計算機を作成
func New() *Calculator {
    // TODO: 実装してください
}

// Result は現在の結果を返す
func (c *Calculator) Result() float64 {
    // TODO: 実装してください
}

// Add は加算
func (c *Calculator) Add(n float64) *Calculator {
    // TODO: 実装してください
    // 履歴に "+ n" を追加
}

// Subtract は減算
func (c *Calculator) Subtract(n float64) *Calculator {
    // TODO: 実装してください
}

// Multiply は乗算
func (c *Calculator) Multiply(n float64) *Calculator {
    // TODO: 実装してください
}

// Divide は除算(エラーチェック)
func (c *Calculator) Divide(n float64) (*Calculator, error) {
    // TODO: 実装してください
    // ゼロ除算の場合はエラーを返す
}

// Power は累乗
func (c *Calculator) Power(n float64) *Calculator {
    // TODO: 実装してください
}

// SquareRoot は平方根
func (c *Calculator) SquareRoot() (*Calculator, error) {
    // TODO: 実装してください
    // 負数の場合はエラーを返す
}

// Clear はリセット
func (c *Calculator) Clear() *Calculator {
    // TODO: 実装してください
}

// History は計算履歴を返す
func (c *Calculator) History() []string {
    // TODO: 実装してください
}

期待される出力

// 数学ライブラリ
result, err := mathlib.SafeDivide(10, 2)
// result = 5.0, err = nil

q, r, err := mathlib.DivMod(17, 5)
// q = 3, r = 2, err = nil

min, max, _ := mathlib.MinMax(1, 5, 3, 9, 2)
// min = 1, max = 9

// 関数型ユーティリティ
numbers := []int{1, 2, 3, 4, 5}
doubled := funcutil.Map(numbers, func(n int) int { return n * 2 })
// [2, 4, 6, 8, 10]

evens := funcutil.Filter(numbers, func(n int) bool { return n%2 == 0 })
// [2, 4]

sum := funcutil.Reduce(numbers, 0, func(a, b int) int { return a + b })
// 15

// 文字列ビルダー
html := strutil.NewBuilder().
    AppendLine("<html>").
    Indent().
    IndentedLine("<body>").
    Indent().
    IndentedLine("<p>Hello, World!</p>").
    Dedent().
    IndentedLine("</body>").
    Dedent().
    AppendLine("</html>").
    String()

// 計算機
calc := calculator.New()
result := calc.Add(10).
    Multiply(2).
    Subtract(5).
    Result()  // 15

ボーナス課題

ボーナス1: ジェネリック関数ユーティリティ

Go 1.18以降のジェネリクスを使って、型安全な関数を実装してください。

package generic

// Map はジェネリック版
func Map[T, U any](items []T, fn func(T) U) []U {
    // TODO: 実装してください
}

// Filter はジェネリック版
func Filter[T any](items []T, predicate func(T) bool) []T {
    // TODO: 実装してください
}

// Reduce はジェネリック版
func Reduce[T, U any](items []T, initial U, fn func(U, T) U) U {
    // TODO: 実装してください
}

// Contains はジェネリック版
func Contains[T comparable](items []T, target T) bool {
    // TODO: 実装してください
}

// 使用例
func main() {
    // int -> string
    numbers := []int{1, 2, 3}
    strings := Map(numbers, func(n int) string {
        return fmt.Sprintf("Number: %d", n)
    })

    // string -> int
    words := []string{"a", "bb", "ccc"}
    lengths := Map(words, func(s string) int {
        return len(s)
    })
}

ボーナス2: デコレーターパターン

関数をラップして機能を追加するデコレーターを実装してください。

package decorator

import (
    "log"
    "time"
)

type Handler func(string) (string, error)

// WithLogging はロギング機能を追加
func WithLogging(h Handler) Handler {
    // TODO: 実装してください
    // 入力と出力をログに記録
}

// WithTiming は実行時間計測を追加
func WithTiming(h Handler) Handler {
    // TODO: 実装してください
}

// WithRetry はリトライ機能を追加
func WithRetry(h Handler, maxRetries int) Handler {
    // TODO: 実装してください
    // エラー時に最大maxRetries回リトライ
}

// WithCache はキャッシュ機能を追加
func WithCache(h Handler) Handler {
    // TODO: 実装してください
    // 同じ入力の結果をキャッシュ
}

// 使用例
func main() {
    base := func(input string) (string, error) {
        return strings.ToUpper(input), nil
    }

    // デコレーターを重ねる
    handler := WithLogging(
        WithTiming(
            WithRetry(
                WithCache(base),
                3,
            ),
        ),
    )

    result, err := handler("hello")
}

ボーナス3: 関数オプションパターン

package options

type Server struct {
    host    string
    port    int
    timeout time.Duration
    logger  Logger
}

// Option型: サーバー設定関数
type Option func(*Server)

// NewServer はオプションパターンでサーバーを作成
func NewServer(opts ...Option) *Server {
    // TODO: 実装してください
    // デフォルト値を設定し、オプションを適用
    server := &Server{
        host:    "localhost",
        port:    8080,
        timeout: 30 * time.Second,
    }

    for _, opt := range opts {
        opt(server)
    }

    return server
}

// WithHost はホストを設定
func WithHost(host string) Option {
    // TODO: 実装してください
}

// WithPort はポートを設定
func WithPort(port int) Option {
    // TODO: 実装してください
}

// WithTimeout はタイムアウトを設定
func WithTimeout(timeout time.Duration) Option {
    // TODO: 実装してください
}

// WithLogger はロガーを設定
func WithLogger(logger Logger) Option {
    // TODO: 実装してください
}

// 使用例
func main() {
    server := NewServer(
        WithHost("0.0.0.0"),
        WithPort(3000),
        WithTimeout(60 * time.Second),
    )
}

評価基準

項目 配点 詳細
数学ライブラリ 25点 複数戻り値とエラーハンドリング
関数型ユーティリティ 25点 高階関数が正しく実装されている
文字列ビルダー 25点 メソッドチェーンが動作する
計算機クラス 25点 メソッドとエラーハンドリング
**ボーナス1** 10点 ジェネリクスが正しく使われている
**ボーナス2** 10点 デコレーターパターンが動作する
**ボーナス3** 5点 オプションパターンが実装されている

提出方法

submission/
├── go.mod
├── mathlib/
│   ├── basic.go
│   └── basic_test.go
├── funcutil/
│   ├── functional.go
│   └── functional_test.go
├── strutil/
│   ├── builder.go
│   └── builder_test.go
├── calculator/
│   ├── calculator.go
│   └── calculator_test.go
└── bonus/
    ├── generic/
    ├── decorator/
    └── options/

ヒント

  • 複数戻り値: エラーは最後の戻り値
  • メソッドチェーン: ポインタレシーバでreturn b
  • 高階関数: 関数型を定義すると読みやすい
  • 名前付き戻り値: 短い関数で使用
  • クロージャ: 外側の変数をキャプチャできる
  • テストケース

    // mathlib/basic_test.go
    func TestSafeDivide(t *testing.T) {
        tests := []struct {
            a, b     float64
            expected float64
            hasError bool
        }{
            {10, 2, 5, false},
            {10, 0, 0, true},
            {7, 2, 3.5, false},
        }
    
        for _, tt := range tests {
            result, err := SafeDivide(tt.a, tt.b)
            if tt.hasError {
                if err == nil {
                    t.Errorf("Expected error for %f / %f", tt.a, tt.b)
                }
            } else {
                if err != nil {
                    t.Errorf("Unexpected error: %v", err)
                }
                if result != tt.expected {
                    t.Errorf("SafeDivide(%f, %f) = %f; want %f",
                        tt.a, tt.b, result, tt.expected)
                }
            }
        }
    }
    
    // funcutil/functional_test.go
    func TestMap(t *testing.T) {
        numbers := []int{1, 2, 3, 4, 5}
        doubled := Map(numbers, func(n int) int { return n * 2 })
    
        expected := []int{2, 4, 6, 8, 10}
        if !reflect.DeepEqual(doubled, expected) {
            t.Errorf("Map() = %v; want %v", doubled, expected)
        }
    }
    
    // strutil/builder_test.go
    func TestBuilderChain(t *testing.T) {
        result := NewBuilder().
            Append("Hello").
            Append(", ").
            Append("World").
            String()
    
        expected := "Hello, World"
        if result != expected {
            t.Errorf("Builder = %q; want %q", result, expected)
        }
    }
    

    学習リソース

  • Effective Go - Functions
  • Effective Go - Methods
  • Go by Example - Functions
  • Go by Example - Closures
  • Functional Options Pattern