Open10

ちょっとしたgoでのgenericsの利用例

podhmopodhmo

テスト上でError monadというかrustの!みたいなことをするパッケージ。

https://github.com/podhmo/or

genericsのsignatureの仕様でmethodがtype parametersを持てないところで無理やり考えたのが部分適用した関数を返す関数。多値の仕様との兼ね合いも考える必要があった。

podhmopodhmo

こういうのもあるか。前者は例えば json.Unmarshal() のwrapperとか。それを後者を使うと、副作用ではなく戻り値で受け取る形式にできる。

  • シンプルには型の強制(ポインタの強制)
  • ゼロ値での初期化
func DecodeJSON[T any](r io.Reader) (T, error) {
    var ob T
    if err := json.NewDecoder(r).Decode(&ob); err != nil {
         return err
    }
    return ob
}

型の強制はこういうやつ

func BindJSON[T any](b []byte, ob *T) error {
    return json.Unmarshal(b, ob)
}
podhmopodhmo

一般的な実装を書いて、型を厳しくしたような実装も作れるかも。enum的な何かを引数に取る関数に使えそう。

func DefineEnum[T any](r *Router, defaultValue T, values ...T) *reflectopenapi.RegisterTypeAction {
	dst := make([]interface{}, len(values)+1)
	typedValue := T(defaultValue)
	dst[0] = typedValue
	for i, v := range values {
		dst[i+1] = T(v)
	}
	return r.m.RegisterType(typedValue, func(ref *openapi3.Schema) {
		ref.Default = dst[0]
		ref.Enum = dst
	})
}
func DefineStringEnum[T ~string](r *Router, defaultValue T, values ...T) *reflectopenapi.RegisterTypeAction {
	return DefineEnum(r, defaultValue, values...)
}
func DefineIntEnum[T ~int](r *Router, defaultValue T, values ...T) *reflectopenapi.RegisterTypeAction {
	return DefineEnum(r, defaultValue, values...)
}
podhmopodhmo

メソッドは型パラメーターを持てない制限が結構辛いのだけれど、struct typeに型変数を持たせて、メソッドを定義し、それをpackage globalなvarで持っておくみたいなことをすると、いくつかの関数群を一度に定義みたいなこともできそう。

例えばこれは、contextに値を封入したり取り出したりするgetter,setterのペアを作るようなコード(keyのユニーク性はもうちょっと頑張る必要がありそう)。

https://gist.github.com/podhmo/48b132b10a493bb3e13a38ccd674c957

podhmopodhmo

ちょっとしたsliceのhelper

func prepend[T any](xs []T, x T) []T {
	return append([]T{x}, xs...)
}
podhmopodhmo

reflectとgenericsの組み合わせで特定のインターフェイスの実装を探して呼び出す

https://gist.github.com/podhmo/b3a1bbbb0d792b199cb7e06466c37def

podhmopodhmo

ネストした表現はサポートしていないかも

高階関数の引数としてインターフェイスをとる関数を型変数を持った関数に渡すと、その関数のボディの中では特殊化されるから、そのインターフェイスにcastができて、渡された関数を呼び出せる。