1. やりたいこと
Golangって、foldとかreduceってできないのかな。。ということでawsome-goを眺めていたら見つけました。
どうやって実装しているのかな、というのが、下記のrob pikeさんのリポジトリとの対比もあって気になりました。
簡単な例として、数値のリストを受け取って、それを与えられた加算の関数を利用して、サムアップする、という処理を書いてみました。
2. ugoの利用方法と実装方法
2-1. ugoの利用方法
ugoを利用する場合には、下記のようになりました。
my_add
という関数を事前に準備します。my_func
の戻り値を、ugo
のObject
にしてあげる必要があります。
Reduce
関数を利用して、リストの値をmy_add
でサムアップした後に、ugo.Object
が返却されるので、例えば、リストの要素がint
であれば、.(int)
のようにしてあげる必要があるみたいです。
package main
import "github.com/alxrm/ugo"
func my_add(elem, acc, _, _ ugo.Object) ugo.Object {
return elem.(int) + acc.(int)
}
func main() {
a := ugo.Seq{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
result := ugo.Reduce(a, my_add, 1).(int)
println(result)
}
2-2. ugoの実装方法
下記抜粋ですが、中身の実態です。
アキュミュレータの初期値、initial
がnil
の時などには、初期値をリストの先頭にしてくれるあたり、よしなにやってくれている感じがあります。
Reduce
関数自体が返却するのは、ugo.Object
になります。
// Reduce makes single value from all of the slice elements, iterating from left
func Reduce(seq Seq, cb Collector, initial Object) Object {
var memo Object
if IsEmpty(seq) || cb == nil {
return nil
}
length := len(seq) - 1
if initial == nil {
memo = seq[0]
return createReduce(seq, cb, memo, 1, toMax, length-1)
}
memo = initial
return createReduce(seq, cb, memo, 0, toMax, length)
}
// createShuffle returns single value folded to it from given slice
func createReduce(seq Seq, cb Collector, memo Object, startPoint, direction, length int) Object {
result := memo
index := startPoint
for i := 0; i <= length; i++ {
result = cb(result, seq[index], index, seq)
index += direction
}
return result
}
3. rob pikeさんの利用方法と実装方法
3-1. robpike.io/filterの利用方法
次に、robpikeさんのReduceを利用してみました。
package main
import "robpike.io/filter"
`ugo`とは違って、独自の`Object`を定義しておらず、シンプルにそのまま、`my_add`の戻り値の型を`int`にできるようです。利用者からすると、`ugo`よりはわかりやすそうです。
func my_add(elem, acc int) int { return elem + acc }
func main() {
a := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
result := filter.Reduce(a, my_add, 1).(int)
println(result)
}
3-2. robpike.io/filterの実装方法
中身の実装を見てみると下記のようになっていました。
reflect
パッケージを利用しています。
Reduce
自体が返却するのはinterface{}
になっています。
func Reduce(slice, pairFunction, zero interface{}) interface{} {
in := reflect.ValueOf(slice)
if in.Kind() != reflect.Slice {
panic("reduce: not slice")
}
n := in.Len()
switch n {
case 0:
return zero
case 1:
return in.Index(0)
}
elemType := in.Type().Elem()
fn := reflect.ValueOf(pairFunction)
if !goodFunc(fn, elemType, elemType, elemType) {
str := elemType.String()
panic("apply: function must be of type func(" + str + ", " + str + ") " + str)
}
// Do the first two by hand to prime the pump.
var ins [2]reflect.Value
ins[0] = in.Index(0)
ins[1] = in.Index(1)
out := fn.Call(ins[:])[0]
// Run from index 2 to the end.
for i := 2; i < n; i++ {
ins[0] = out
ins[1] = in.Index(i)
out = fn.Call(ins[:])[0]
}
return out.Interface()
}
そして、rob pikeさんの実装だと、リストの要素数が1つだけの時は、エラーになってしまいます。
ここはもうちょっと見ていこうと思います。
とはいえ、このライブラリ自体をrob pikeさんもREADMEで下記のように言っており、今後もGoではReduceが標準ライブラリになることはないかもしれません^^;
I wanted to see how hard it was to implement this sort of thing in Go, with as nice an API as I could manage. It wasn't hard.
Having written it a couple of years ago, I haven't had occasion to use it once. Instead, I just use "for" loops.
You shouldn't use it either.
本日は以上となります。