課題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)
}
}