🎡

Repeatパターンとfirst-restシグニチャ

2021/09/11に公開

はじめに

前日Dave Cheney氏による『Practical Go: Real world advice for writing maintainable Go programs』(以下、Practical Go)の存在を遅ればせながら知り、一通り拝読しました。
Practical Goは非常に為になることばかり書いてあるので未読の方は一度は目を通すことを強く推奨します。

今回はPractical Goの6.2.2項『Prefer var args to []T parameters』から学んだことを活かしてRepeatパターン[1]を実装してみようと思います。

『Prefer var args to []T parameters』について

このトピックに関するざっくりとした私の理解は

//このシグニチャだとスライスの要素数が1つだけでもスライスによってボックス化されてしまう
//また、引数がnilでもコンパイルが通るので厄介
func f(nums []int) error {} 
//引数がnilだとコンパイルが通らなくなったがf()だと結局通るので
//f()で関数が呼ばれた場合のことを念頭に置く必要ある
func f(nums ...int) error {}
//最低でも1つint型を引数に渡すことを関数の呼ぶ側に強制するので
//上記のものと違いf()での関数呼び出しは不可(無論nilでも)
//引数が1つだけの場合スライスによるボックス化を回避できる!
func f(first int, rest ...int) error {}

といった感じです。(的はずれな理解だったら、ごめんなさい。)
本記事では3つ目のようなシグニチャをもつ関数をfirst-restシグニチャと呼称し、この考え方を応用してRepeatパターンを実装します。

実装

『Go言語による並行処理』におけるRepeatパターンのシグニチャは以下の通りです。

repeat := func(
	done <-chan interface{},
	values ...interface{},
	) <-chan interface{} {}
	
//変数に関数を代入しない場合
func repeat(done <-chan interface{}, values ...interface{}) <- chan interface{}
//first-restシグニチャを用いたrepeatパターンの実装
func repeat(done <-chan struct{}, first int, rest ...int) <-chan int {
	out := make(chan int)

	go func() {
		defer close(out)
		for {
			select {
			case <-done:
				return
			default:
				//もっと良い書き方がありそう
				out <- first
				for _, v := range rest {
					out <- v
				}
			}
		}
	}()

	return out
}

テスト

package main

import (
	"reflect"
	"testing"
)

func TestRepeat(t *testing.T) {
	tests := []struct {
		id       string
		first    int
		rest     []int
		cnt      int
		expected []int
	}{
		{
			id:       "1",
			first:    1,
			rest:     []int{2, 3},
			cnt:      5,
			expected: []int{1, 2, 3, 1, 2},
		},
		{
			id:       "2",
			first:    22,
			rest:     []int{2, 222},
			cnt:      10,
			expected: []int{22, 2, 222, 22, 2, 222, 22, 2, 222, 22},
		},
		{
			id:       "3",
			first:    1,
			rest:     []int{},
			cnt:      3,
			expected: []int{1, 1, 1},
		},
	}

	for _, test := range tests {
		t.Run(test.id, func(t *testing.T) {
			done := make(chan struct{})
			defer close(done)

			actual := make([]int, len(test.expected))
			var i int
			for v := range repeat(done, test.first, test.rest...) {
				actual[i] = v
				i++
				if i == test.cnt {
					if !reflect.DeepEqual(actual, test.expected) {
						t.Errorf("invalid output: expected: %v, actual: %v\n", test.expected, actual)
					}
					return
				}
			}
		})
	}
}
//テスト結果
$ go test -v
=== RUN   TestRepeat
=== RUN   TestRepeat/1
=== RUN   TestRepeat/2
=== RUN   TestRepeat/3
--- PASS: TestRepeat (0.00s)
    --- PASS: TestRepeat/1 (0.00s)
    --- PASS: TestRepeat/2 (0.00s)
    --- PASS: TestRepeat/3 (0.00s)
PASS
ok      gopls-workspace 0.002s

おわりに

私自身がGo初心者ということもあり、こういったアウトプットが誰かの為になるのかどうか確信がもてませんが、そうなってくれれば幸いです。

脚注
  1. ここでいうRepeatパターンとは『Go言語による並行処理』4.6.2項で述べられているものを指します。(ざっくりいうと引数で与えられた値を戻り値のチャネルにひたすら送信し続ける関数です) ↩︎

Discussion