Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
3
Help us understand the problem. What is going on with this article?
@nirasan

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

More than 3 years have passed since last update.

はじめに

  • 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 へのキャストまでできるようになれば、個人的には十分な気がしている。
3
Help us understand the problem. What is going on with this article?
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
nirasan
フリーで開発者をしています。

Comments

No comments
Sign up for free and join this conversation.
Sign Up
If you already have a Qiita account Login
3
Help us understand the problem. What is going on with this article?