├── LICENSE ├── README.md ├── concurrency ├── group.go └── parmap.go ├── go.mod ├── go.sum ├── list ├── bench_test.go ├── list.go └── list_test.go ├── metrics ├── metrics.go └── metrics_test.go ├── optional ├── optional.go └── optional_test.go ├── stream ├── stream.go └── stream_test.go ├── tuple ├── examples_test.go ├── tuple.go └── tuple_test.go └── tuple2 ├── README.md ├── examples_test.go ├── tuple.go └── tuple_test.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Jonathan Amsterdam 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-generics 2 | 3 | Experiments in using the new Go generics implementation. 4 | 5 | These all work at 1.18beta1. 6 | -------------------------------------------------------------------------------- /concurrency/group.go: -------------------------------------------------------------------------------- 1 | package concurrency 2 | 3 | type Group struct { 4 | sem chan struct{} 5 | } 6 | 7 | func NewGroup(max int) *Group { 8 | return &Group{sem: make(chan struct{}, max)} 9 | } 10 | 11 | func (g *Group) Go(f func()) { 12 | g.sem <- struct{}{} // wait until the number of goroutines is below the limit 13 | go func() { 14 | defer func() { <-g.sem }() // let another goroutine run 15 | f() 16 | }() 17 | } 18 | 19 | func (g *Group) Wait() { 20 | for i := 0; i < cap(g.sem); i++ { 21 | g.sem <- struct{}{} 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /concurrency/parmap.go: -------------------------------------------------------------------------------- 1 | package concurrency 2 | 3 | import ( 4 | "context" 5 | "sync" 6 | ) 7 | 8 | // type Transformer[A, B any] struct { 9 | // sem chan struct{} 10 | // } 11 | 12 | // func NewTransformer[A, B any](max int) Transformer[A, B] { 13 | // if max <= 0 { 14 | // return &Transformer[A, B]{} 15 | // } 16 | // return &Transformer[A, B]{sem: make(chan struct{}, max)} 17 | // } 18 | 19 | // func (t *Transformer[A, B]) Slice(in []A, f func(A) (B, error)) ([]B, error) { 20 | 21 | func TransformSlice[A, B any](ctx context.Context, in []A, max int, f func(context.Context, A) (B, error)) ([]B, error) { 22 | type el struct { 23 | i int 24 | a A 25 | } 26 | 27 | if max < 0 { 28 | max = len(in) 29 | } 30 | elc := make(chan el) 31 | 32 | errc := make(chan error, 1) 33 | senderr := func(err error) { 34 | select { 35 | case errc <- err: 36 | default: 37 | } 38 | } 39 | 40 | out := make([]B, len(in)) 41 | ctx, cancel := context.WithCancel(ctx) 42 | defer cancel() 43 | var wg sync.WaitGroup 44 | for i := 0; i < max; i++ { 45 | wg.Add(1) 46 | go func() { 47 | defer wg.Done() 48 | for e := range elc { 49 | select { 50 | case <-ctx.Done(): 51 | senderr(ctx.Err()) 52 | return 53 | default: 54 | b, err := f(ctx, e.a) 55 | if err != nil { 56 | cancel() 57 | senderr(err) 58 | return 59 | } 60 | out[e.i] = b 61 | } 62 | } 63 | }() 64 | } 65 | 66 | for i, a := range in { 67 | elc <- el{i, a} 68 | } 69 | close(elc) 70 | 71 | wg.Wait() 72 | select { 73 | case err := <-errc: 74 | return nil, err 75 | default: 76 | return out, nil 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/jba/go-generics 2 | 3 | go 1.18 4 | 5 | require golang.org/x/exp v0.0.0-20211216164055-b2b84827b756 6 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | golang.org/x/exp v0.0.0-20211216164055-b2b84827b756 h1:/5Bs7sWi0i3rOVO5KnM55OwugpsD4bRW1zywKoZjbkI= 2 | golang.org/x/exp v0.0.0-20211216164055-b2b84827b756/go.mod h1:b9TAUYHmRtqA6klRHApnXMnj+OyLce4yF5cZCUbk2ps= 3 | -------------------------------------------------------------------------------- /list/bench_test.go: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import ( 4 | clist "container/list" 5 | "testing" 6 | ) 7 | 8 | const N = 100_000 9 | 10 | func BenchmarkIterateContainerList(b *testing.B) { 11 | l := clist.New() 12 | for j := 0; j < N; j++ { 13 | l.PushBack(l) 14 | } 15 | b.ResetTimer() 16 | for i := 0; i < b.N; i++ { 17 | for e := l.Front(); e != nil; e = e.Next() { 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /list/list.go: -------------------------------------------------------------------------------- 1 | // Package list implements an unrolled doubly-linked list. 2 | package list 3 | 4 | type Element[T any] struct { 5 | n *node[T] 6 | i int 7 | } 8 | 9 | func (e Element[T]) Zero() bool { 10 | return e.n == nil && e.i == 0 11 | } 12 | 13 | // Any modification to the list invalidates Value. 14 | func (e Element[T]) Value() T { 15 | return e.n.vals[e.i] 16 | } 17 | 18 | func (e Element[T]) Next() Element[T] { 19 | i := e.i + 1 20 | if i < e.n.len { 21 | return Element[T]{e.n, i} 22 | } 23 | return Element[T]{e.n.next, 0} 24 | } 25 | 26 | func (e *Element[T]) Prev() Element[T] { 27 | i := e.i - 1 28 | if i >= 0 { 29 | return Element[T]{e.n, i} 30 | } 31 | return Element[T]{e.n.prev, 0} 32 | } 33 | 34 | const nodeSize = 4 35 | 36 | type node[T any] struct { 37 | vals [nodeSize]T 38 | len int 39 | next, prev *node[T] 40 | } 41 | 42 | type List[T any] struct { 43 | front, back *node[T] 44 | len int 45 | } 46 | 47 | func (l *List[T]) Front() Element[T] { 48 | return Element[T]{l.front, 0} 49 | } 50 | 51 | func (l *List[T]) Back() Element[T] { 52 | if l.len == 0 { 53 | return Element[T]{} 54 | } 55 | return Element[T]{l.back, l.back.len} 56 | } 57 | 58 | func (l *List[T]) Len() int { 59 | return l.len 60 | } 61 | 62 | func (l *List[T]) InsertAfter(v T, e Element[T]) Element[T] { 63 | // assert e.i < e.n.len 64 | return l.insertAt(v, e.n, e.i+1) 65 | } 66 | 67 | func (l *List[T]) InsertBefore(v T, e Element[T]) Element[T] { 68 | return l.insertAt(v, e.n, e.i) 69 | } 70 | 71 | func (l *List[T]) insertAt(v T, n *node[T], i int) Element[T] { 72 | l.len++ 73 | if n.len == nodeSize { 74 | // This node is full. Add the element to the next node if there's room. 75 | // Otherwise, split this node into two. 76 | if n.next == nil { 77 | l.addNodeAfter(n) 78 | } 79 | assert(n.next != nil) 80 | if n.next.len == nodeSize { 81 | // Split this node. 82 | const halfSize = nodeSize / 2 83 | n2 := l.addNodeAfter(n) 84 | copy(n2.vals[:], n.vals[halfSize:]) 85 | n2.len = halfSize 86 | n.len = halfSize 87 | if i > halfSize { 88 | n = n2 89 | i -= halfSize 90 | } 91 | } else if i == n.len { 92 | n = n.next 93 | i = 0 94 | } else { 95 | // Try to move i and above; move less there's not enough room. 96 | m := n.len - i 97 | if nodeSize-n.next.len < m { 98 | m = nodeSize - n.next.len 99 | } 100 | copy(n.next.vals[m:], n.next.vals[:n.next.len]) 101 | copy(n.next.vals[:m], n.vals[n.len-m:n.len]) 102 | n.len -= m 103 | n.next.len += m 104 | // We still want to insert at position i in n. 105 | } 106 | } 107 | assert(n.len < nodeSize) 108 | assert(i <= n.len) 109 | n.insertNonFull(v, i) 110 | return Element[T]{n, i} 111 | } 112 | 113 | func assert(b bool) { 114 | if !b { 115 | panic("assertion failed") 116 | } 117 | } 118 | 119 | func (n *node[T]) insertNonFull(v T, i int) { 120 | assert(n.len < nodeSize) 121 | copy(n.vals[i+1:], n.vals[i:n.len]) 122 | n.vals[i] = v 123 | n.len++ 124 | } 125 | 126 | func (l *List[T]) PushFront(v T) Element[T] { 127 | if l.front == nil { 128 | l.init() 129 | } 130 | return l.insertAt(v, l.front, 0) 131 | } 132 | 133 | func (l *List[T]) PushBack(v T) Element[T] { 134 | if l.back == nil { 135 | l.init() 136 | } 137 | return l.insertAt(v, l.back, l.back.len) 138 | } 139 | 140 | func (l *List[T]) init() { 141 | n := &node[T]{} 142 | l.front = n 143 | l.back = n 144 | } 145 | 146 | func (l *List[T]) addNodeAfter(n *node[T]) *node[T] { 147 | n2 := &node[T]{next: n.next, prev: n} 148 | n.next = n2 149 | if n2.next == nil { 150 | l.back = n2 151 | } else { 152 | n2.next.prev = n2 153 | } 154 | return n2 155 | } 156 | 157 | func (l *List[T]) Remove(e Element[T]) { 158 | // TODO: merge half-full nodes 159 | if e.n.len == 1 { 160 | l.unsplice(e.n) 161 | } else { 162 | copy(e.n.vals[e.i:], e.n.vals[e.i+1:]) 163 | e.n.len-- 164 | } 165 | } 166 | 167 | func (l *List[T]) unsplice(n *node[T]) { 168 | if n.next == nil { 169 | l.back = n.prev 170 | } else { 171 | n.next.prev = n.prev 172 | } 173 | if n.prev == nil { 174 | l.front = n.next 175 | } else { 176 | n.prev.next = n.next 177 | } 178 | } 179 | -------------------------------------------------------------------------------- /list/list_test.go: -------------------------------------------------------------------------------- 1 | package list 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "io" 7 | "testing" 8 | ) 9 | 10 | func TestPushBack(t *testing.T) { 11 | l := &List[int]{} 12 | check(t, l, "") 13 | l.PushBack(1) 14 | check(t, l, "1") 15 | l.PushBack(2) 16 | l.PushBack(3) 17 | l.PushBack(4) 18 | check(t, l, "1 2 3 4") 19 | l.PushBack(5) 20 | check(t, l, "1 2 3 4 | 5") 21 | l.PushBack(6) 22 | check(t, l, "1 2 3 4 | 5 6") 23 | } 24 | 25 | func TestPushFront(t *testing.T) { 26 | l := &List[int]{} 27 | for i := 10; i > 0; i-- { 28 | l.PushFront(i) 29 | } 30 | check(t, l, "1 2 3 4 | 5 6 | 7 8 9 10") 31 | } 32 | 33 | func TestInsert(t *testing.T) { 34 | l := newList(10, 20, 30, 40, 50) 35 | check(t, l, "10 20 30 40 | 50") 36 | // Insert at start of non-full node. 37 | l.InsertBefore(45, find(l, 50)) 38 | check(t, l, "10 20 30 40 | 45 50") 39 | // Insert at end of full node. 40 | l.InsertAfter(43, find(l, 40)) 41 | check(t, l, "10 20 30 40 | 43 45 50") 42 | // Insert at end of no-full node. 43 | l.InsertAfter(60, find(l, 50)) 44 | check(t, l, "10 20 30 40 | 43 45 50 60") 45 | // Force a split. 46 | l.InsertAfter(25, find(l, 20)) 47 | check(t, l, "10 20 25 | 30 40 | 43 45 50 60") 48 | // Force a move. 49 | l.InsertAfter(27, find(l, 25)) 50 | check(t, l, "10 20 25 27 | 30 40 | 43 45 50 60") 51 | l.InsertBefore(15, find(l, 20)) 52 | check(t, l, "10 15 20 | 25 27 30 40 | 43 45 50 60") 53 | // TODO: force a move, case of copying from i. 54 | } 55 | func TestRemove(t *testing.T) { 56 | l := &List[int]{} 57 | var els []Element[int] 58 | for i := 0; i < 10; i++ { 59 | els = append(els, l.PushBack(i)) 60 | } 61 | check(t, l, "0 1 2 3 | 4 5 6 7 | 8 9") 62 | l.Remove(els[2]) 63 | check(t, l, "0 1 3 | 4 5 6 7 | 8 9") 64 | l.Remove(l.Front()) 65 | check(t, l, "1 3 | 4 5 6 7 | 8 9") 66 | l.Remove(l.Back()) 67 | check(t, l, "1 3 | 4 5 6 7 | 8") 68 | l.Remove(l.Back()) 69 | check(t, l, "1 3 | 4 5 6 7") 70 | } 71 | 72 | func slice[T any](l *List[T]) []T { 73 | var els []T 74 | for n := l.front; n != nil; n = n.next { 75 | els = append(els, n.vals[:n.len]...) 76 | } 77 | return els 78 | } 79 | 80 | func check(t *testing.T, l *List[int], want string) { 81 | t.Helper() 82 | got := structure(l) 83 | if got != want { 84 | t.Fatalf("got %s, want %s", got, want) 85 | } 86 | } 87 | 88 | func str(l *List[int]) string { 89 | var buf bytes.Buffer 90 | io.WriteString(&buf, "[") 91 | first := true 92 | for n := l.front; n != nil; n = n.next { 93 | for i := 0; i < n.len; i++ { 94 | if !first { 95 | io.WriteString(&buf, ", ") 96 | } 97 | first = false 98 | fmt.Fprintf(&buf, "%d", n.vals[i]) 99 | } 100 | } 101 | io.WriteString(&buf, "]") 102 | return buf.String() 103 | } 104 | 105 | func structure(l *List[int]) string { 106 | var buf bytes.Buffer 107 | for n := l.front; n != nil; n = n.next { 108 | for i := 0; i < n.len; i++ { 109 | fmt.Fprintf(&buf, "%d ", n.vals[i]) 110 | } 111 | if n.next != nil { 112 | io.WriteString(&buf, "| ") 113 | } 114 | } 115 | if buf.Len() > 0 { 116 | buf.Truncate(buf.Len() - 1) 117 | } 118 | return buf.String() 119 | } 120 | 121 | func newList(vals ...int) *List[int] { 122 | l := &List[int]{} 123 | for _, v := range vals { 124 | l.PushBack(v) 125 | } 126 | return l 127 | } 128 | 129 | func find(l *List[int], target int) Element[int] { 130 | for e := l.Front(); !e.Zero(); e = e.Next() { 131 | if e.Value() == target { 132 | return e 133 | } 134 | } 135 | panic("not found") 136 | } 137 | -------------------------------------------------------------------------------- /metrics/metrics.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | // Package metrics provides tracking arbitrary metrics composed of 6 | // values of comparable types. 7 | package metrics 8 | 9 | import "sync" 10 | 11 | // Metric tracks metrics of values of some type. 12 | type Metric[T comparable] struct { 13 | mu sync.Mutex 14 | m map[T]int 15 | } 16 | 17 | // Add adds another instance of some value. 18 | func (m *Metric[T]) Add(v T) { 19 | m.mu.Lock() 20 | defer m.mu.Unlock() 21 | if m.m == nil { 22 | m.m = make(map[T]int) 23 | } 24 | m.m[v]++ 25 | } 26 | 27 | // Count returns the number of instances we've seen of v. 28 | func (m *Metric[T]) Count(v T) int { 29 | m.mu.Lock() 30 | defer m.mu.Unlock() 31 | return m.m[v] 32 | } 33 | 34 | // Metrics returns all the values we've seen, in an indeterminate order. 35 | func (m *Metric[T]) Metrics() []T { 36 | // TODO: use maps.Keys when available. 37 | var keys []T 38 | for k := range m.m { 39 | keys = append(keys, k) 40 | } 41 | return keys 42 | } 43 | -------------------------------------------------------------------------------- /metrics/metrics_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2020 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package metrics 6 | 7 | import ( 8 | "sort" 9 | "testing" 10 | 11 | "github.com/jba/go-generics/tuple" 12 | "golang.org/x/exp/slices" 13 | ) 14 | 15 | type S struct{ a, b, c string } 16 | 17 | func TestMetrics(t *testing.T) { 18 | m1 := Metric[string]{} 19 | if got := m1.Count("a"); got != 0 { 20 | t.Errorf("Count(%q) = %d, want 0", "a", got) 21 | } 22 | m1.Add("a") 23 | m1.Add("a") 24 | if got := m1.Count("a"); got != 2 { 25 | t.Errorf("Count(%q) = %d, want 2", "a", got) 26 | } 27 | if got, want := m1.Metrics(), []string{"a"}; !slices.Equal(got, want) { 28 | t.Errorf("Metrics = %v, want %v", got, want) 29 | } 30 | 31 | m2 := Metric[tuple.T2[int, float64]]{} 32 | m2.Add(tuple.New2(1, 1.0)) 33 | m2.Add(tuple.New2(2, 2.0)) 34 | m2.Add(tuple.New2(3, 3.0)) 35 | m2.Add(tuple.New2(3, 3.0)) 36 | k := m2.Metrics() 37 | 38 | sort.Slice(k, func(i, j int) bool { 39 | if k[i].V0() < k[j].V0() { 40 | return true 41 | } 42 | if k[i].V0() > k[j].V0() { 43 | return false 44 | } 45 | return k[i].V1() < k[j].V1() 46 | }) 47 | 48 | w := [](tuple.T2[int, float64]){tuple.New2(1, 1.0), tuple.New2(2, 2.0), tuple.New2(3, 3.0)} 49 | if !slices.Equal(k, w) { 50 | t.Errorf("m2.Metrics = %v, want %v", k, w) 51 | } 52 | 53 | m3 := Metric[tuple.T3[string, S, S]]{} 54 | m3.Add(tuple.New3("a", S{"d", "e", "f"}, S{"g", "h", "i"})) 55 | m3.Add(tuple.New3("a", S{"d", "e", "f"}, S{"g", "h", "i"})) 56 | m3.Add(tuple.New3("a", S{"d", "e", "f"}, S{"g", "h", "i"})) 57 | m3.Add(tuple.New3("b", S{"d", "e", "f"}, S{"g", "h", "i"})) 58 | if got := m3.Count(tuple.New3("a", S{"d", "e", "f"}, S{"g", "h", "i"})); got != 3 { 59 | t.Errorf("Count(%v, %v, %v) = %d, want 3", "a", S{"d", "e", "f"}, S{"g", "h", "i"}, got) 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /optional/optional.go: -------------------------------------------------------------------------------- 1 | // Package optional provides Opt[T] values, which represent 2 | // either a value of type T or no value. 3 | package optional 4 | 5 | // An Opt[T] represents either a value of type T, or no value. 6 | // The zero value of an Opt[T] represents no value. 7 | type Opt[T any] struct { 8 | Value T 9 | Present bool 10 | } 11 | 12 | // New returns an Opt[T] with value x. 13 | func New[T any](x T) Opt[T] { 14 | return Opt[T]{Value: x, Present: true} 15 | } 16 | 17 | // Set sets the value of o to x. 18 | func (o *Opt[T]) Set(x T) { 19 | o.Value = x 20 | o.Present = true 21 | } 22 | 23 | // ValueOr returns o.Value if it is present. Otherwise it returns other. 24 | func (o Opt[T]) ValueOr(other T) T { 25 | if o.Present { 26 | return o.Value 27 | } 28 | return other 29 | } 30 | 31 | // Must returns T if o has a value. Otherwise it panics. 32 | func (o Opt[T]) Must() T { 33 | if !o.Present { 34 | panic("missing value") 35 | } 36 | return o.Value 37 | } 38 | -------------------------------------------------------------------------------- /optional/optional_test.go: -------------------------------------------------------------------------------- 1 | package optional 2 | 3 | import "testing" 4 | 5 | func Test(t *testing.T) { 6 | x := New(3) 7 | y := x.Must() + 1 8 | if y != 4 { 9 | t.Fatal("y != 4") 10 | } 11 | x.Set(7) 12 | if x.Must() != 7 { 13 | t.Fatal("x.Must() != 7") 14 | } 15 | 16 | // ValueOr 17 | var z Opt[int] 18 | got := z.ValueOr(1) 19 | if want := 1; got !=want { 20 | t.Errorf("z.ValueOr(1) = %d, want %d", got, want) 21 | } 22 | z.Set(2) 23 | got = z.ValueOr(1) 24 | if want := 2; got !=want { 25 | t.Errorf("z.ValueOr(1) = %d, want %d", got, want) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /stream/stream.go: -------------------------------------------------------------------------------- 1 | // Package stream implements operations on streams of values. 2 | package stream 3 | 4 | import "constraints" 5 | 6 | // A Stream[T] is a possibly infinite sequence of values of type T. It is 7 | // implemented by a function that returns either a valid T and true, or the zero 8 | // value for T and false. 9 | type Stream[T any] func() (T, bool) 10 | 11 | // New returns a Stream[T] consisting of the given values. 12 | func New[T any](vals ...T) Stream[T] { 13 | return func() (T, bool) { 14 | if len(vals) == 0 { 15 | var z T 16 | return z, false 17 | } 18 | t := vals[0] 19 | vals = vals[1:] 20 | return t, true 21 | } 22 | } 23 | 24 | // Range returns a stream over numeric values from low (inclusive) to high (exclusive), 25 | // in intervals of step. 26 | // Range panics if step is zero. 27 | func Range[T constraints.Integer | constraints.Float](low, high, step T) Stream[T] { 28 | if step == 0 { 29 | panic("0 step") 30 | } 31 | i := low 32 | return func() (T, bool) { 33 | if (step > 0 && i >= high) || (step < 0 && i <= high) { 34 | var z T 35 | return z, false 36 | } 37 | r := i 38 | i += step 39 | return r, true 40 | } 41 | } 42 | 43 | // Slice collects all the values of s into a slice. 44 | func (s Stream[T]) Slice() []T { 45 | var c []T 46 | for { 47 | next, ok := s() 48 | if !ok { 49 | return c 50 | } 51 | c = append(c, next) 52 | } 53 | } 54 | 55 | // Keep returns a stream that contains the values of s for which f returns true. 56 | func (s Stream[T]) Keep(f func(T) bool) Stream[T] { 57 | return func() (T, bool) { 58 | for { 59 | next, ok := s() 60 | if !ok { 61 | return next, false 62 | } 63 | if f(next) { 64 | return next, true 65 | } 66 | } 67 | } 68 | } 69 | 70 | // Remove returns a stream that contains all the values of s for which 71 | // f returns false. 72 | func (s Stream[T]) Remove(f func(T) bool) Stream[T] { 73 | return s.Keep(func(x T) bool { return !f(x) }) 74 | } 75 | 76 | // Apply invokes f for each element of s. 77 | func (s Stream[T]) Apply(f func(T)) { 78 | for { 79 | next, ok := s() 80 | if !ok { 81 | break 82 | } 83 | f(next) 84 | } 85 | } 86 | 87 | // Map returns the stream that results from applying f to each element of s. 88 | func Map[T, U any](s Stream[T], f func(T) U) Stream[U] { 89 | return func() (U, bool) { 90 | next, ok := s() 91 | if !ok { 92 | var u U 93 | return u, false 94 | } 95 | return f(next), true 96 | } 97 | } 98 | 99 | // MapConcat applies f to each element of s. It returns the stream consisting 100 | // of all the elements returned by f concatenated together. 101 | func MapConcat[T, U any](s Stream[T], f func(T) []U) Stream[U] { 102 | var us []U 103 | return func() (U, bool) { 104 | for { 105 | if len(us) > 0 { 106 | u := us[0] 107 | us = us[1:] 108 | return u, true 109 | } 110 | next, ok := s() 111 | if !ok { 112 | var u U 113 | return u, false 114 | } 115 | us = f(next) 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /stream/stream_test.go: -------------------------------------------------------------------------------- 1 | package stream 2 | 3 | import ( 4 | "math" 5 | "testing" 6 | 7 | "golang.org/x/exp/slices" 8 | ) 9 | 10 | func TestSlice(t *testing.T) { 11 | want := []int{2, 3, 5, 7, 11} 12 | s := New(want...) 13 | got := s.Slice() 14 | if !slices.Equal(got, want) { 15 | t.Errorf("got %v, want %v", got, want) 16 | } 17 | } 18 | 19 | func TestRange(t *testing.T) { 20 | for _, test := range []struct { 21 | low, high, step int 22 | want []int 23 | }{ 24 | {1, 3, 1, []int{1, 2}}, 25 | {1, 1, 1, nil}, 26 | {0, 5, 2, []int{0, 2, 4}}, 27 | {5, 0, -2, []int{5, 3, 1}}, 28 | {5, 1, 1, nil}, 29 | {1, 5, -1, nil}, 30 | } { 31 | got := Range(test.low, test.high, test.step).Slice() 32 | if !slices.Equal(got, test.want) { 33 | t.Errorf("Range(%d, %d, %d) = %v, want %v", test.low, test.high, test.step, got, test.want) 34 | } 35 | } 36 | } 37 | 38 | func TestKeep(t *testing.T) { 39 | s := New(1, 2, 3, 4, 5) 40 | got := s.Keep(func(x int) bool { return x%2 == 0 }).Slice() 41 | want := []int{2, 4} 42 | if !slices.Equal(got, want) { 43 | t.Errorf("got %v, want %v", got, want) 44 | } 45 | } 46 | 47 | func TestMap(t *testing.T) { 48 | s := New(1, 4, 9, 16) 49 | got := Map(s, func(x int) float64 { return math.Sqrt(float64(x)) }).Slice() 50 | want := []float64{1, 2, 3, 4} 51 | if !slices.Equal(got, want) { 52 | t.Errorf("got %#v, want %#v", got, want) 53 | } 54 | } 55 | 56 | func TestMapConcat(t *testing.T) { 57 | s := Range(0, 5, 1) 58 | got := MapConcat(s, func(x int) []int { 59 | if x%2 == 0 { 60 | return []int{x, -x} 61 | } 62 | return nil 63 | }).Slice() 64 | want := []int{0, 0, 2, -2, 4, -4} 65 | if !slices.Equal(got, want) { 66 | t.Errorf("got %#v, want %#v", got, want) 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /tuple/examples_test.go: -------------------------------------------------------------------------------- 1 | // This example demonstrates how a property-testing framework 2 | // could benefit from tuples. 3 | // This design is simplified from github.com/leanovate/gopter. 4 | package tuple_test 5 | 6 | import "github.com/jba/go-generics/tuple" 7 | 8 | // Check generates up to max values by calling gen, and returns the first 9 | // value for which pred returns false, along with a second return value of true. 10 | // If pred never returns false, the second return value is false. 11 | func Check[T any](max int, pred func(T) bool, gen Gen[T]) (T, bool) { 12 | for i := 0; i < max; i++ { 13 | v := gen() 14 | if !pred(v) { 15 | return v, true 16 | } 17 | } 18 | var z T 19 | return z, false 20 | } 21 | 22 | // Gen is a generator: each time it is called, it produces a new value of type T. 23 | type Gen[T any] func() T 24 | 25 | // CombineGens2 converts two generators into a single generator that returns a tuple. 26 | func CombineGens2[A, B any](ga Gen[A], gb Gen[B]) Gen[tuple.T2[A, B]] { 27 | return func() tuple.T2[A, B] { 28 | return tuple.New2(ga(), gb()) 29 | } 30 | } 31 | 32 | func CombineGens3[A, B, C any](ga Gen[A], gb Gen[B], gc Gen[C]) Gen[tuple.T3[A, B, C]] { 33 | return func() tuple.T3[A, B, C] { 34 | return tuple.New3(ga(), gb(), gc()) 35 | } 36 | } 37 | 38 | func CombineGens4[A, B, C, D any](ga Gen[A], gb Gen[B], gc Gen[C], gd Gen[D]) Gen[tuple.T4[A, B, C, D]] { 39 | return func() tuple.T4[A, B, C, D] { 40 | return tuple.New4(ga(), gb(), gc(), gd()) 41 | } 42 | } 43 | 44 | //////////////////////////////////////////////////////////////// 45 | // Alternative to CombineGensN: define CheckN in terms of Check. 46 | 47 | func Check2[A, B any](max int, pred func(A, B) bool, genA Gen[A], genB Gen[B]) (A, B, bool) { 48 | p := func(t tuple.T2[A, B]) bool { return pred(t.Spread()) } 49 | g := func() tuple.T2[A, B] { return tuple.New2(genA(), genB()) } 50 | r, ok := Check(max, p, g) 51 | return r.V0, r.V1, ok 52 | } 53 | -------------------------------------------------------------------------------- /tuple/tuple.go: -------------------------------------------------------------------------------- 1 | // Package tuple provides tuples of values. 2 | // A tuple of size N represents N values, each of a different type. 3 | package tuple 4 | 5 | import "fmt" 6 | 7 | // T2 is a tuple of two elements. 8 | type T2[A, B any] struct { 9 | V0 A 10 | V1 B 11 | } 12 | 13 | // T3 is a tuple of three elements. 14 | type T3[A, B, C any] struct { 15 | V0 A 16 | V1 B 17 | V2 C 18 | } 19 | 20 | // T4 is a tuple of four elements. 21 | type T4[A, B, C, D any] struct { 22 | V0 A 23 | V1 B 24 | V2 C 25 | V3 D 26 | } 27 | 28 | // T5 is a tuple of five elements. 29 | type T5[A, B, C, D, E any] struct { 30 | V0 A 31 | V1 B 32 | V2 C 33 | V3 D 34 | V4 E 35 | } 36 | 37 | // T6 is a tuple of five elements. 38 | type T6[A, B, C, D, E, F any] struct { 39 | V0 A 40 | V1 B 41 | V2 C 42 | V3 D 43 | V4 E 44 | V5 F 45 | } 46 | 47 | // New2 returns a new T2. 48 | func New2[A, B any](a A, b B) T2[A, B] { 49 | return T2[A, B]{a, b} 50 | } 51 | 52 | // New3 returns a new T3. 53 | func New3[A, B, C any](a A, b B, c C) T3[A, B, C] { 54 | return T3[A, B, C]{a, b, c} 55 | } 56 | 57 | // New4 returns a new T4. 58 | func New4[A, B, C, D any](a A, b B, c C, d D) T4[A, B, C, D] { 59 | return T4[A, B, C, D]{a, b, c, d} 60 | } 61 | 62 | // New5 returns a new T5. 63 | func New5[A, B, C, D, E any](a A, b B, c C, d D, e E) T5[A, B, C, D, E] { 64 | return T5[A, B, C, D, E]{a, b, c, d, e} 65 | } 66 | 67 | // New6 returns a new T6. 68 | func New6[A, B, C, D, E, F any](a A, b B, c C, d D, e E, f F) T6[A, B, C, D, E, F] { 69 | return T6[A, B, C, D, E, F]{a, b, c, d, e, f} 70 | } 71 | 72 | // Spread returns the elements of its receiver as separate return values. 73 | func (t T2[A, B]) Spread() (A, B) { return t.V0, t.V1 } 74 | 75 | // Spread returns the elements of its receiver as separate return values. 76 | func (t T3[A, B, C]) Spread() (A, B, C) { return t.V0, t.V1, t.V2 } 77 | 78 | // Spread returns the elements of its receiver as separate return values. 79 | func (t T4[A, B, C, D]) Spread() (A, B, C, D) { return t.V0, t.V1, t.V2, t.V3 } 80 | 81 | // Spread returns the elements of its receiver as separate return values. 82 | func (t T5[A, B, C, D, E]) Spread() (A, B, C, D, E) { return t.V0, t.V1, t.V2, t.V3, t.V4 } 83 | 84 | // Spread returns the elements of its receiver as separate return values. 85 | func (t T6[A, B, C, D, E, F]) Spread() (A, B, C, D, E, F) { return t.V0, t.V1, t.V2, t.V3, t.V4, t.V5 } 86 | 87 | func (t T2[A, B]) String() string { return fmt.Sprintf("<%v, %v>", t.V0, t.V1) } 88 | func (t T3[A, B, C]) String() string { return fmt.Sprintf("<%v, %v, %v>", t.V0, t.V1, t.V2) } 89 | 90 | func (t T4[A, B, C, D]) String() string { 91 | return fmt.Sprintf("<%v, %v, %v, %v>", 92 | t.V0, t.V1, t.V2, t.V3) 93 | } 94 | 95 | func (t T5[A, B, C, D, E]) String() string { 96 | return fmt.Sprintf("<%v, %v, %v, %v, %v>", 97 | t.V0, t.V1, t.V2, t.V3, t.V4) 98 | } 99 | 100 | func (t T6[A, B, C, D, E, F]) String() string { 101 | return fmt.Sprintf("<%v, %v, %v, %v, %v, %v>", 102 | t.V0, t.V1, t.V2, t.V3, t.V4, t.V5) 103 | } 104 | -------------------------------------------------------------------------------- /tuple/tuple_test.go: -------------------------------------------------------------------------------- 1 | package tuple 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestVs(t *testing.T) { 9 | t3 := New3("x", 8, true) 10 | if got, want := t3.V0, "x"; got != want { 11 | t.Errorf("got %v, want %v", got, want) 12 | } 13 | if got, want := t3.V1, 8; got != want { 14 | t.Errorf("got %v, want %v", got, want) 15 | } 16 | if got, want := t3.V2, true; got != want { 17 | t.Errorf("got %v, want %v", got, want) 18 | } 19 | } 20 | 21 | func TestSpread(t *testing.T) { 22 | t3 := New3("x", 8, true) 23 | g1, g2, g3 := t3.Spread() 24 | w1, w2, w3 := "x", 8, true 25 | if g1 != w1 || g2 != w2 || g3 != w3 { 26 | t.Errorf("got (%v, %v, %v), want (%v, %v, %v)", g1, g2, g3, w1, w2, w3) 27 | } 28 | } 29 | 30 | func TestString(t *testing.T) { 31 | tu := New6(1, "x", false, 8.0, -3, 2+3i) 32 | got := fmt.Sprintf("%v", tu) 33 | want := "<1, x, false, 8, -3, (2+3i)>" 34 | if got != want { 35 | t.Errorf("got %q, want %q", got, want) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tuple2/README.md: -------------------------------------------------------------------------------- 1 | This package introduces generic tuples. Here I justify why they are useful. 2 | 3 | Tuples are somewhat useful their own. For instance, they would come in handy 4 | when parallelizing a function with more than one return value: 5 | 6 | ``` 7 | func f(int) (int, error) ... 8 | 9 | var ints []int = ... 10 | c := make(chan tuple.T2[int, error], len(ints)) 11 | for _, i := range ints { 12 | i := i 13 | go func() { c <- tuple.New2(f(i)) } 14 | } 15 | ``` 16 | 17 | However, I don't think they carry their weight when viewed in isolation. 18 | 19 | But they will turn out to be extremely useful when defining other generic 20 | packages. They can help reduce or eliminate "arity copying," defining multiple 21 | types or functions to handle different numbers of arguments. 22 | 23 | The generic metrics package included in the prototype translator is a good 24 | example. It has types Metric1, Metric2 and so on, to support metrics with 25 | different numbers of dimensions. Each MetricN type has copies of all the Metric 26 | methods with only slight variations. You might argue that as bad as this is, it 27 | is at least confined to the package itself; users don't feel the pain. But what 28 | if a user wanted to write a function that took a Metric as an argument, or 29 | returned one? They would need a separate function for each MetricN type. Arity 30 | copying is infectious. 31 | 32 | Now consider the metrics package in this repo, built with tuples in mind. There 33 | is only one type, `Metric[T]`, and one set of methods. `T` can be any comparable 34 | type, or a tuple of two or more comparable types--which, thanks to Go's rules on 35 | struct equality, is itself comparable. 36 | 37 | The metrics package is practically a best case for tuples. In other cases, 38 | tuples can't eliminate arity copying, but they can mitigate it. The file 39 | examples_test.go contains the outline of a property-testing framework, greatly 40 | simplified from github.com/leanovate/gopter. The basic idea is to generate a lot 41 | of random arguments for a boolean function (the predicate) and see if it ever 42 | returns false on them. The gopter package handles functions of any arity via 43 | reflection. To build a type-safe version using generics, arity copying is needed 44 | somewhere, but we can at least define a single Check function that takes a 45 | one-argument predicate and a single generator of random values: 46 | 47 | ``` 48 | func Check[T any](max int, pred func(T) bool, gen Gen[T]) 49 | ``` 50 | 51 | The file presents two designs that start from there, both built on the 52 | fact that the `T` parameter of `Check` can be a tuple. 53 | 54 | In the first design, users must adapt their multi-argument predicate 55 | to take a tuple, which is easily done via the Spread method defined on 56 | each tuple type: 57 | 58 | ``` 59 | func pred(int, string) bool ... 60 | 61 | func wrapper(t tuple.T2[int, string]) bool { return pred(t.Spread()) } 62 | ``` 63 | 64 | Combining N individual generators into one that generates an N-tuple is harder, 65 | so the package provides a series of CombineGensN functions for that purpose. By 66 | means of a small local helper and the tuples.JoinN functions, the Nth 67 | CombineGens function can be defined in terms of the (N-1)st in one line: 68 | 69 | ``` 70 | func CombineGens3[type A, B, C](ga Gen[A], gb Gen[B], gc Gen[C]) Gen(tuple.T3[A, B, C]) { 71 | return joinGen(CombineGens2(ga, gb), gc, tuple.Join3(A, B, C)) 72 | } 73 | ``` 74 | 75 | This is possible because instead of the obvious representation of an N-tuple as 76 | N separate fields, the tuples package defines it as an (N-1)-tuple with a single 77 | new field. You can also see this pseudo-recursion at work in the `Nth` methods 78 | defined on each tuple type. 79 | 80 | In the second design of the property tester, a number of CheckN functions are 81 | defined as small wrappers around the one-argument `Check`: 82 | 83 | ``` 84 | func Check2[type A, B](max int, pred func(A, B) bool, genA Gen[A], genB Gen[B]) (A, B, bool) { 85 | p := func(t tuple.T2[A, B]) bool { return pred(t.Spread()) } 86 | g := func() tuple.T2[A, B] { return tuples.New2(genA(), genB()) } 87 | r, ok := Check(max, p, g) 88 | return r.V0(), r.V1(), ok 89 | } 90 | ``` 91 | 92 | This is facilitated by the Spread method and the NewN constructor functions. 93 | 94 | In general, whenever there is a need to support functions of varying arity in 95 | arguments or return values, a single function signature `func(T) R` will 96 | suffice, with tuples providing the glue. 97 | -------------------------------------------------------------------------------- /tuple2/examples_test.go: -------------------------------------------------------------------------------- 1 | // This example demonstrates how a property-testing framework 2 | // could benefit from tuples. 3 | // This design is simplified from github.com/leanovate/gopter. 4 | package tuple2_test 5 | 6 | import tuple "github.com/jba/go-generics/tuple2" 7 | 8 | // Check generates up to max values by calling gen, and returns the first 9 | // value for which pred returns false, along with a second return value of true. 10 | // If pred never returns false, the second return value is false. 11 | func Check[T any](max int, pred func(T) bool, gen Gen[T]) (T, bool) { 12 | for i := 0; i < max; i++ { 13 | v := gen() 14 | if !pred(v) { 15 | return v, true 16 | } 17 | } 18 | var z T 19 | return z, false 20 | } 21 | 22 | // Gen is a generator: each time it is called, it produces a new value of type T. 23 | type Gen[T any] func() T 24 | 25 | // CombineGens2 converts two generators into a single generator that returns a tuple. 26 | func CombineGens2[A, B any](ga Gen[A], gb Gen[B]) Gen[tuple.T2[A, B]] { 27 | return joinGen(ga, gb, tuple.Join2[A, B]) 28 | } 29 | 30 | func CombineGens3[A, B, C any](ga Gen[A], gb Gen[B], gc Gen[C]) Gen[tuple.T3[A, B, C]] { 31 | return joinGen(CombineGens2(ga, gb), gc, tuple.Join3[A, B, C]) 32 | } 33 | 34 | func CombineGens4[A, B, C, D any](ga Gen[A], gb Gen[B], gc Gen[C], gd Gen[D]) Gen[tuple.T4[A, B, C, D]] { 35 | return joinGen(CombineGens3(ga, gb, gc), gd, tuple.Join4[A, B, C, D]) 36 | } 37 | 38 | func joinGen[T, V, R any](gt Gen[T], gv Gen[V], join func(T, V) R) Gen[R] { 39 | return func() R { return join(gt(), gv()) } 40 | } 41 | 42 | //////////////////////////////////////////////////////////////// 43 | // Alternative to CombineGensN: define CheckN in terms of Check. 44 | 45 | func Check2[A, B any](max int, pred func(A, B) bool, genA Gen[A], genB Gen[B]) (A, B, bool) { 46 | p := func(t tuple.T2[A, B]) bool { return pred(t.Spread()) } 47 | g := func() tuple.T2[A, B] { return tuple.New2(genA(), genB()) } 48 | r, ok := Check(max, p, g) 49 | return r.V0(), r.V1(), ok 50 | } 51 | -------------------------------------------------------------------------------- /tuple2/tuple.go: -------------------------------------------------------------------------------- 1 | // Package tuple provides tuples of values. 2 | // A tuple of size N represents N values, each of a different type. 3 | package tuple2 4 | 5 | import "fmt" 6 | 7 | // T2 is a tuple of two elements. 8 | type T2[A, B any] struct { 9 | T A 10 | V B 11 | } 12 | 13 | // T3 is a tuple of three elements. 14 | type T3[A, B, C any] struct { 15 | T T2[A, B] 16 | V C 17 | } 18 | 19 | // T4 is a tuple of four elements. 20 | type T4[A, B, C, D any] struct { 21 | T T3[A, B, C] 22 | V D 23 | } 24 | 25 | // New2 returns a new T2. 26 | func New2[A, B any](a A, b B) T2[A, B] { 27 | return Join2(a, b) 28 | } 29 | 30 | // New3 returns a new T3. 31 | func New3[A, B, C any](a A, b B, c C) T3[A, B, C] { 32 | return Join3(New2(a, b), c) 33 | } 34 | 35 | // New4 returns a new T4. 36 | func New4[A, B, C, D any](a A, b B, c C, d D) T4[A, B, C, D] { 37 | return Join4(New3(a, b, c), d) 38 | } 39 | 40 | // Join2 returns a T2 consisting of the elements t and v. 41 | // Join2 is identical to New2. 42 | func Join2[A, B any](t A, v B) T2[A, B] { return T2[A, B]{t, v} } 43 | 44 | // Join3 returns a T3 consisting of the T2 t and the value v. 45 | func Join3[A, B, C any](t T2[A, B], v C) T3[A, B, C] { return T3[A, B, C]{t, v} } 46 | 47 | // Join4 returns a T4 consisting of the T3 t and the value v. 48 | func Join4[A, B, C, D any](t T3[A, B, C], v D) T4[A, B, C, D] { return T4[A, B, C, D]{t, v} } 49 | 50 | // V0 returns the first element of its receiver tuple. 51 | func (t T2[A, B]) V0() A { return t.T } 52 | 53 | // V1 returns the second element of its receiver tuple. 54 | func (t T2[A, B]) V1() B { return t.V } 55 | 56 | func (t T3[A, B, C]) V0() A { return t.T.T } 57 | func (t T3[A, B, C]) V1() B { return t.T.V } 58 | func (t T3[A, B, C]) V2() C { return t.V } 59 | 60 | func (t T4[A, B, C, D]) V0() A { return t.T.T.T } 61 | func (t T4[A, B, C, D]) V1() B { return t.T.T.V } 62 | func (t T4[A, B, C, D]) V2() C { return t.T.V } 63 | func (t T4[A, B, C, D]) V3() D { return t.V } 64 | 65 | // Spread returns the elements of its receiver as separate return values. 66 | func (t T2[A, B]) Spread() (A, B) { return t.V0(), t.V1() } 67 | 68 | // Spread returns the elements of its receiver as separate return values. 69 | func (t T3[A, B, C]) Spread() (A, B, C) { return t.V0(), t.V1(), t.V2() } 70 | 71 | // Spread returns the elements of its receiver as separate return values. 72 | func (t T4[A, B, C, D]) Spread() (A, B, C, D) { return t.V0(), t.V1(), t.V2(), t.V3() } 73 | 74 | func (t T2[A, B]) String() string { return fmt.Sprintf("<%v, %v>", t.V0(), t.V1()) } 75 | func (t T3[A, B, C]) String() string { return fmt.Sprintf("<%v, %v, %v>", t.V0(), t.V1(), t.V2()) } 76 | func (t T4[A, B, C, D]) String() string { 77 | return fmt.Sprintf("<%v, %v, %v, %v>", 78 | t.V0(), t.V1(), t.V2(), t.V3()) 79 | } 80 | 81 | // Nth returns the nth element ,0-based, of its receiver. 82 | func (t T2[A, B]) Nth(i int) interface{} { 83 | switch i { 84 | case 0: 85 | return t.T 86 | case 1: 87 | return t.V 88 | default: 89 | panic("bad index") 90 | } 91 | } 92 | 93 | // Nth returns the nth element ,0-based, of its receiver. 94 | func (t T3[A, B, C]) Nth(i int) interface{} { 95 | if i == 2 { 96 | return t.V 97 | } 98 | return t.T.Nth(i) 99 | } 100 | 101 | // Nth returns the nth element ,0-based, of its receiver. 102 | func (t T4[A, B, C, D]) Nth(i int) interface{} { 103 | if i == 3 { 104 | return t.V 105 | } 106 | return t.T.Nth(i) 107 | } 108 | -------------------------------------------------------------------------------- /tuple2/tuple_test.go: -------------------------------------------------------------------------------- 1 | package tuple2 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestVs(t *testing.T) { 8 | t3 := New3("x", 8, true) 9 | if got, want := t3.V0(), "x"; got != want { 10 | t.Errorf("got %v, want %v", got, want) 11 | } 12 | if got, want := t3.V1(), 8; got != want { 13 | t.Errorf("got %v, want %v", got, want) 14 | } 15 | if got, want := t3.V2(), true; got != want { 16 | t.Errorf("got %v, want %v", got, want) 17 | } 18 | } 19 | 20 | func TestNth(t *testing.T) { 21 | t3 := New3("x", 8, true) 22 | if got, want := t3.Nth(0), "x"; got != want { 23 | t.Errorf("got %v, want %v", got, want) 24 | } 25 | if got, want := t3.Nth(1), 8; got != want { 26 | t.Errorf("got %v, want %v", got, want) 27 | } 28 | if got, want := t3.Nth(2), true; got != want { 29 | t.Errorf("got %v, want %v", got, want) 30 | } 31 | } 32 | 33 | func TestSpread(t *testing.T) { 34 | t3 := New3("x", 8, true) 35 | g1, g2, g3 := t3.Spread() 36 | w1, w2, w3 := "x", 8, true 37 | if g1 != w1 || g2 != w2 || g3 != w3 { 38 | t.Errorf("got (%v, %v, %v), want (%v, %v, %v)", g1, g2, g3, w1, w2, w3) 39 | } 40 | } 41 | --------------------------------------------------------------------------------