LoginSignup
4
3

More than 5 years have passed since last update.

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

Posted at

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.

本日は以上となります。

4
3
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
4
3