LoginSignup
7
4

More than 5 years have passed since last update.

Go で型を抽象的に扱うには

Posted at

はじめに

  • GAE/Go + Datastore 向けの汎用レポジトリライブラリみたいなものを作ってみて Go で型を抽象的に扱うコードをいくらか書いたのでまとめてみる。

Go で型を抽象的に扱うには

  • Go では reflect と interface を使って型を抽象的に扱うことができる
  • Go で型を抽象的に扱う時に出てくる登場人物としては、抽象的に値を扱うことができる reflect.Value と抽象的に型を扱うことができる reflect.Type と任意の値を扱うことができる interface{} がある
  • ここで Go の値(便宜的に Go value と表記)とそれぞれの登場人物の相関図とそれぞれの操作を表すコードを記載する

golang-reflect.jpg

package main

import (
    "reflect"
    "log"
)

func main() {
    // 1. Go value から reflect.Value へ変換する
    gv := 1
    rv := reflect.ValueOf(gv)
    p("ValueOf.", rv)

    // 2. reflect.Value から Go value へ変換する
    //    Int, Float, String など組み込みの型のみ関数が用意されている
    gv2 := rv.Int()
    p("Int.", gv2)

    // 3. reflect.Value から interface{} へ変換する
    i := rv.Interface()
    p("interface{}.", i)

    // 4. interface{} から Go value へ変換する
    //    Type Assertion を使う
    if gv, ok := i.(int); ok {
        p("Go value.", gv)
    }

    // 5. Go value から reflect.Type を取得する
    rt := reflect.TypeOf(gv)
    p("Type.", rt)

    // 6. reflect.Value から reflect.Type を取得する
    rt = rv.Type()
    p("Type.", rt)

    // 7. reflect.Type から任意の型の reflect.Value を作成する
    // []int の reflect.Type を作成する
    srt := reflect.SliceOf(rt)
    // []int の reflect.Value を作成する
    srv := reflect.MakeSlice(srt, 0, 0)
    // []int の reflect.Value に append する
    srv = reflect.Append(srv, reflect.ValueOf(100), reflect.ValueOf(200))
    // reflect.Value を interface{} に変換して Go value に変換する
    if s, ok := srv.Interface().([]int); ok {
        p("MakeSlice.", s)
    }
}

func p(prefix string, val interface{}) {
    rt := reflect.TypeOf(val)
    log.Printf(prefix + "\nvalue: %+v, type: %+v, kind: %+v", val, rt, rt.Kind())
}

型を抽象的に扱うといえば Generics

  • Go には Generics はないが Generics がなくても何とかなる部分とやっぱりあると嬉しい部分も見えてきた。

Generics がなくても何とかなる部分

  • interface{} を使えば任意の値を扱うことができる。
  • reflect.Type を使えば型のメタデータにアクセスして型に合わせた柔軟な処理を記述できる。

やっぱり Generics があると嬉しい部分

  • interface{} から Go value への変換(上記の図の 4 の部分)でどうしても具体的な型が必要になるのでここだけ抽象化できずにライブラリなんかにまとめることができない。
  • reflect.Type で型チェックをする場合、実行時に行われるため型安全ではなくなる。

型アサーションが抽象化できない問題への対応

  • 諦めて型アサーションする
  • 型アサーションをするラッパーを都度実装する

型チェックが実行時に行われる問題への対応

  • 型安全にするならダックタイピング
    • インターフェイスに合わせた実装が必要なので気軽には扱えない
    • ライブラリで扱うならありかも

Generics は必要か?

  • あると便利で、なくてもある程度はできるのがわかってきた。
  • ダックタイピングを利用すれば型安全にある程度のことはできる。
  • しかし気軽に利用できるような何かがあると嬉しいので、この部分が Go は筋力が必要と言われる部分。
  • 型アサーションが reflect.Type でできるとか reflect 内で Go value へのキャストまでできるようになれば、個人的には十分な気がしている。
7
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
4