Go

Golangでreduce関数を提供しているライブラリugoを眺めてみた

More than 1 year has passed since last update.

1. やりたいこと

Golangって、foldとかreduceってできないのかな。。ということでawsome-goを眺めていたら見つけました。

どうやって実装しているのかな、というのが、下記のrob pikeさんのリポジトリとの対比もあって気になりました。

簡単な例として、数値のリストを受け取って、それを与えられた加算の関数を利用して、サムアップする、という処理を書いてみました。

2. ugoの利用方法と実装方法

2-1. ugoの利用方法

ugoを利用する場合には、下記のようになりました。
my_addという関数を事前に準備します。my_funcの戻り値を、ugoObjectにしてあげる必要があります。
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の実装方法

下記抜粋ですが、中身の実態です。

アキュミュレータの初期値、initialnilの時などには、初期値をリストの先頭にしてくれるあたり、よしなにやってくれている感じがあります。
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{}になっています。

reduce.go
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.

本日は以上となります。