ジェネリックメソッドの回避策
CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 71/107。
Go には注目すべき制限があります。構造体に独自の型パラメーターを持つメソッドを定義することはできません。generic な構造体は非常にうまく機能しますが、new な型パラメーターをintroducesするメソッドを追加することはallowedではありません。
このコードはコンパイルできません:
type Box[T any] struct {
Value T
}
// ERROR: メソッドには型パラメータを持たせてはならない
func (b Box[T]) Convert[U any](fn func(T) U) U {
return fn(b.Value)
}回避策は、メソッドの代わりに独立したgeneric関数を使用することです。
type Box[T any] struct {
Value T
}
// 通常のメソッド - 構造体の型パラメータを使用する
func (b Box[T]) Get() T {
return b.Value
}
// スタンドアロン関数 - 独自の型パラメータを持つことができる
func Convert[T, U any](b Box[T], fn func(T) U) U {
return fn(b.Value)
}
func main() {
intBox := Box[int]{Value: 42}
// メソッドを使用する
fmt.Println(intBox.Get()) // 42
// スタンドアロン関数を使用する
strResult := Convert(intBox, func(n int) string {
return fmt.Sprintf("Number: %d", n)
})
fmt.Println(strResult) // Number: 42
}ジェネリック構造体のメソッドでは、構造体の型パラメーター T を引き続き使用できます。この制限は、メソッドシグネチャで追加の型パラメーターを導入する場合にのみ適用されます。
その柔軟性が必要な場合は、構造体を最初の引数として受け取る generic function で同じ結果を実現できます。
チャレンジ
簡単Go のジェネリックメソッドの制限に対する回避策を示す、データ変換ツールキットを構築しましょう!メソッドでは新しい型パラメーターを導入できないため、同じ柔軟性を実現する standalone generic function を作成します。
コードを 2 つのファイルに分けて整理します。
wrapper.go:generic container と transformation functions を Define します。単一のフィールド
Value(型T)を持つ generic structWrapper[T any]を作成します。ラップされた値を returns する
Wrapper[T]の methodGetを Add します。この method は struct の type parameter を使用しますが、これは allowed です。ラッパーの値に transformation function を適用し、results を returns する standalone generic function
Transform[T, U any](w Wrapper[T], fn func(T) U) Uを Create します。この function には出力型用の own type parameterUが必要です。そのため、method ではなく standalone function でなければなりません。ラップされた値を
fmt.Sprintf("%v", ...)を使用して string に変換する、もう 1 つの standalone functionTransformToString[T any](w Wrapper[T]) stringを Create します。main.go:異なる変換を使って workaround pattern を実演します。type indicator(
intまたはstring)を Read し、続けて値を Read します。適切な型のWrapperを Create します。int入力の場合:Wrapper[int]を Create し、Transformを使って値を 2 倍にします(int を Returns)。また、TransformToStringを使って string representation を取得します。string入力の場合:Wrapper[string]を Create し、Transformを使って string の length を取得します(int を Returns)。また、TransformToStringを使って string representation を取得します。results を次の format で Print します。
Original: [value] Transformed: [transformed value] As String: [string representation]
次の入力が提供されます。
- Line 1:Type indicator(
intまたはstring) - Line 2:値
たとえば、次の入力が与えられた場合:
int
25出力は次のようになります。
Original: 25
Transformed: 50
As String: 25また、次の入力が与えられた場合:
string
Hello World出力は次のようになります。
Original: Hello World
Transformed: 11
As String: Hello World重要なポイントは、Transform が最初の引数として Wrapper[T] を受け取り、return type 用の新しい type parameter U を introduces することです。これは struct の method としては実現できません。この pattern により、Go の type system の制約内で作業しながら、generic transformations の柔軟性を得られます。
自分で試してみよう
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
reader := bufio.NewReader(os.Stdin)
// 型インジケーターを読み取る
typeIndicator, _ := reader.ReadString('\n')
typeIndicator = strings.TrimSpace(typeIndicator)
// 値を読み取る
value, _ := reader.ReadString('\n')
value = strings.TrimSpace(value)
if typeIndicator == "int" {
// 整数値を解析する
num, _ := strconv.Atoi(value)
// TODO: 解析した数値で Wrapper[int] を作成する
// TODO: Transform を使用して値を2倍にする(ヒント: 2倍にする関数を渡す)
// TODO: TransformToString を使用して文字列表現を取得する
// TODO: 結果を指定の形式で出力する:
// Original: [value]
// Transformed: [transformed value]
// As String: [string representation]
_ = num // num を使用したらこの行を削除する
} else if typeIndicator == "string" {
// TODO: 値で Wrapper[string] を作成する
// TODO: Transform を使って文字列の長さを取得する(ヒント: len() を返す関数を渡す)
// TODO: TransformToString を使用して文字列表現を取得する
// TODO: 結果を指定の形式で出力する:
// Original: [value]
// Transformed: [transformed value]
// As String: [string representation]
_ = value // value を使ったらこの行を削除する
}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
1Go オブジェクト指向の基礎
外部ファイルGo ワークスペースとモジュールパッケージとインポート公開された名前と非公開の名前Go におけるオブジェクト指向入門クラスとしての構造体構造体へのメソッド定義ポインタレシーバと値レシーバ構造体の初期化コンストラクタ関数まとめ:簡易計算機8エラー処理とOOP
error インターフェースカスタムエラー型エラーラッピング (fmt.Errorf)センチネルエラーerrors.Is() と errors.As()Panic、Defer、Recover復習 - ファイルパーサー自分で練習してみよう: Goオンラインコンパイラ