Goでプログラムを書いていると、汎用的なmap関数やfold関数(reduce関数)のようなものがあれば便利なのに、という場面が結構あります。
そういうときは、それぞれの型専用の関数を一通りあらかじめ用意しておく、というような方法でお茶を濁すことが多いと思いますが、そんなものではHaskellやLispな人はもちろんRubyやPythonに慣れたLL脳な人にも満足できないはずです。
そこで本記事では、ジェネリックな高階関数をリフレクションを駆使して実装することで、Goで関数型プログラミングを試みようと思います。
単純な実装のmap関数
最初に、関数を引数にとる関数を単純に実装した場合をみてみます。
例えば、sliceのすべての要素に対して関数を適用した結果を新たなsliceで返すMap関数を考えてみます。
package main
import (
"fmt"
)
var (
ints = []int{0, 1, 2, 3}
)
func MapInt(x []int, f func(int) int) []int {
r := make([]int, len(x))
for i, e := range x {
r[i] = f(e)
}
return r
}
func main() {
fmt.Println(MapInt(ints, func(a int) int { return a * 2 }))
}
int限定であれば上記のようにして簡単に実装できます。しかしint64やstringに対しては、Mapの実装は全く同じにもかかわらず別の関数として定義してやる必要があります。
そうかといって、func Map(x []interface{}, f interface{}) []interface{}
のような型の関数では以下のような問題でうまくいきません。
-
[]interface{}
はinterface{}
を中身にもつsliceなので、int等の具体的な型のsliceは新たなinterfaceのsliceへの変換が必要 -
interface{}
で受けとった値(や関数)を実際につかうときは明示的にtype assertionをつかって本来の型に戻してやる必要がある
1つ目の問題は、単に関数の引数の型を[]interface{}
ではなくinterface{}
で受ければsliceの変換はしなくてよくなりますが、そのsliceの中に格納されている型がユーザ定義型の可能性がある場合も考えると、map関数側では一意に決められません。2つ目の問題も同様でsliceの中身が分からないので関数の型も決められません。
ここで必要になるのが、interfaceの中に格納されている実際の型を知る方法です。それは標準ライブラリに含まれるreflect
パッケージをつかうことで実現できます。
Goのリフレクションについて
リフレクションをつかったコードに進むまえに、Goのリフレクションについて簡単に解説しておきます。
Goのリフレクションでできることはほぼすべてreflect.Valueとreflect.Type型に集約されています。名前のとおり、それぞれ元となったinterfaceの値と型を表します。
interfaceからreflect.Valueを取得するにはreflect.ValueOf
、reflect.Valueからinterfaceに戻すにはValue.Interface()
メソッドで相互に変換できます。
また、値を更新するにはその値のポインタのreflect.Valueが必要になります。
以下サンプルコードです。
package main
import (
"fmt"
"reflect"
)
func main() {
var i int
// iのコピーのreflect.Valueを取得(Goでは一部を除いて値渡しなので)
fmt.Println(reflect.ValueOf(i)) // => <int Value>
// reflect.Typeを取得
fmt.Println(reflect.ValueOf(i).Type()) // => int
// 元の値をinterface{}で取得
fmt.Println(reflect.ValueOf(i).Interface()) // => 0
// iのポインタのreflect.Valueを取得
fmt.Println(reflect.ValueOf(&i)) // => <*int Value>
// ポインタの実体へのreflect.Valueを取得
p := reflect.ValueOf(&i).Elem()
fmt.Println(p) // => <int Value>
// Settabilityを調べる
fmt.Println(p.CanSet()) // => true
fmt.Println(reflect.ValueOf(i).CanSet()) // => false
fmt.Println(reflect.ValueOf(&i).CanSet()) // => false
// 値をセットする
p.Set(reflect.ValueOf(123))
fmt.Println(p.Interface()) // => 123
}
詳細はhttp://blog.golang.org/laws-of-reflectionやreflect
パッケージのマニュアルを読んでください。
なお、実行時に新しい型をつくる安全な方法は現在のところ提供されてないようなので、カリー化のようにある型の関数から別の型を生成する場合は呼出側からその型を持つ(nilでもよい)変数を提供してもらう必要があります。
reflectを用いたmap関数
reflectでできることがわかればあとは簡単です。以下はエラー処理等を省略した簡易版mapの実装例です(フルの実装はここ にあります。)
func Map(lis interface{}, f interface{}) interface{} {
lisv, fv := reflect.ValueOf(lis), reflect.ValueOf(f)
if lisv.Kind() == reflect.Slice {
// 引数に与えられた関数の返り値の型のsliceを生成する
res := reflect.MakeSlice(reflect.SliceOf(fv.Type().Out(0)), lisv.Len(), lisv.Cap())
for i, l := 0, lisv.Len(); i < l; i++ {
// 生成したsliceに関数を呼び出した結果を格納する
res.Index(i).Set(fv.Call([]reflect.Value{lisv.Index(i)})[0])
}
return res.Interface()
}
return nil
}
func main() {
// 任意の型が使えるようになった!
fmt.Println(Map([]int{1, 2, 3}, func(x int) int { return x * 2 })) // => [2 4 6]
fmt.Println(Map([]int64{1, 2, 3}, func(x int64) float64 { return float64(x) + 0.1 })) // => [1.1 2.1 3.1]
}
様々な高階関数
さらに抽象的な高階関数も同様の方法で実装できます。
例えば、カリー化する関数や関数合成をする関数はそれぞれ以下のように実装できます。(ここでもエラー処理は省いてあります)
func Curry(f interface{}, curried interface{}) {
foutv := reflect.ValueOf(curried).Elem()
foutt := foutv.Type()
foutv.Set(reflect.MakeFunc(foutt, func(args1 []reflect.Value) []reflect.Value {
f := reflect.MakeFunc(foutt.Out(0), func(args2 []reflect.Value) []reflect.Value {
return reflect.ValueOf(f).Call(append(args1, args2...))
})
return []reflect.Value{f}
}))
}
func Compose(f, g, composed interface{}) {
foutv := reflect.ValueOf(composed).Elem()
fv, gv := reflect.ValueOf(f), reflect.ValueOf(g)
foutv.Set(reflect.MakeFunc(foutv.Type(), func(args []reflect.Value) []reflect.Value {
return fv.Call(gv.Call(args))
}))
}
MakeFuncをつかえばreflect.Typeがとれるものであればどんな関数でもfunc([]reflect.Value) []reflect.Value
という型の関数としてかけるます。
ここではそれぞれ関数を適用した結果の関数の型を呼出し側に指定させるようになっていますが、これは前述の実行時に新しい型をつくれないという制限を回避するためのの妥協です。
MakeFuncについては、reflectのマニュアルのexampleをみてもらうと分かりやすいと思います。
http://golang.org/pkg/reflect/#example_MakeFunc
関数型言語Go?
上記のような関数があればこんな感じのこともできるようになります。
これを実行するにはgo get github.com/taksatou/go/functional
してください。
var (
flippedEach func(interface{}, interface{}) error
curriedFlippedEach func(interface{}) func(interface{}) error
doubleAndPrint func(e int)
)
functional.Flip(functional.Each, &flippedEach)
functional.Curry(flippedEach, &curriedFlippedEach)
functional.Compose(func(e interface{}) { fmt.Println(e) },
func(e int) interface{} { return e * 2 }, &doubleAndPrint)
doubleAndPrintAll := curriedFlippedEach(doubleAndPrint)
doubleAndPrintAll([]int{1, 2, 3})
だんだんGoも関数型言語のように見えてきませんか? ...あんまり見えてきませんね。
まとめ
Goは静的型付けで型推論も限定的な言語なので、関数が第一級オブジェクトなのにもかかわらず、いわゆる関数型言語のようなスタイルでプログラミングすることにはあまり適していません。
実際のところ、Goにジェネリクスのようなものがないからといって、慣れてくるとそれほど不便には感じなくなくなってきます。
しかし、reflectでゴリ押しすれば(実用的かどうかは別として)なんとなくそれっぽいことはできるようになることを紹介しました。
そもそもGoの設計方針からしてこのようなテクニックは決して推奨されるようなものではありません。ただ、メタプログラミングを通してそのプログラミング言語の仕様についてより深く理解できるし、なにより楽しいので学習テーマとしてはおすすめです。
(今回紹介したコードはすべてgithubにあげてあります。 https://github.com/taksatou/go/tree/master/functional )