Skip to content

Commit b945a41

Browse files
authored
go-linq v4 (#125)
1 parent 4325236 commit b945a41

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+1596
-1568
lines changed

README.md

Lines changed: 65 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ A powerful language integrated query (LINQ) library for Go.
1212

1313
When used with Go modules, use the following import path:
1414

15-
go get github.com/ahmetb/go-linq/v3
15+
go get github.com/ahmetb/go-linq/v4
1616

1717
Older versions of Go using different dependency management tools can use the
1818
following import path to prevent breaking API changes:
1919

20-
go get gopkg.in/ahmetb/go-linq.v3
20+
go get gopkg.in/ahmetb/go-linq.v4
2121

2222
## Quickstart
2323

@@ -28,7 +28,7 @@ Usage is as easy as chaining methods like:
2828
**Example 1: Find all owners of cars manufactured after 2015**
2929

3030
```go
31-
import . "github.com/ahmetb/go-linq/v3"
31+
import . "github.com/ahmetb/go-linq/v4"
3232

3333
type Car struct {
3434
year int
@@ -40,9 +40,9 @@ type Car struct {
4040

4141
var owners []string
4242

43-
From(cars).Where(func(c interface{}) bool {
43+
FromSlice(cars).Where(func(c any) bool {
4444
return c.(Car).year >= 2015
45-
}).Select(func(c interface{}) interface{} {
45+
}).Select(func(c any) any {
4646
return c.(Car).owner
4747
}).ToSlice(&owners)
4848
```
@@ -53,7 +53,7 @@ Or, you can use generic functions, like `WhereT` and `SelectT` to simplify your
5353
```go
5454
var owners []string
5555

56-
From(cars).WhereT(func(c Car) bool {
56+
FromSlice(cars).WhereT(func(c Car) bool {
5757
return c.year >= 2015
5858
}).SelectT(func(c Car) string {
5959
return c.owner
@@ -63,27 +63,27 @@ From(cars).WhereT(func(c Car) bool {
6363
**Example 2: Find the author who has written the most books**
6464

6565
```go
66-
import . "github.com/ahmetb/go-linq/v3"
66+
import . "github.com/ahmetb/go-linq/v4"
6767

6868
type Book struct {
6969
id int
7070
title string
7171
authors []string
7272
}
7373

74-
author := From(books).SelectMany( // make a flat array of authors
75-
func(book interface{}) Query {
74+
author := FromSlice(books).SelectMany( // make a flat array of authors
75+
func(book any) Query {
7676
return From(book.(Book).authors)
7777
}).GroupBy( // group by author
78-
func(author interface{}) interface{} {
78+
func(author any) any {
7979
return author // author as key
80-
}, func(author interface{}) interface{} {
80+
}, func(author any) any {
8181
return author // author as value
8282
}).OrderByDescending( // sort groups by its length
83-
func(group interface{}) interface{} {
83+
func(group any) any {
8484
return len(group.(Group).Group)
8585
}).Select( // get authors out of groups
86-
func(group interface{}) interface{} {
86+
func(group any) any {
8787
return group.(Group).Key
8888
}).First() // take the first author
8989
```
@@ -94,21 +94,16 @@ author := From(books).SelectMany( // make a flat array of authors
9494
type MyQuery Query
9595

9696
func (q MyQuery) GreaterThan(threshold int) Query {
97-
return Query{
98-
Iterate: func() Iterator {
99-
next := q.Iterate()
100-
101-
return func() (item interface{}, ok bool) {
102-
for item, ok = next(); ok; item, ok = next() {
103-
if item.(int) > threshold {
104-
return
105-
}
106-
}
107-
108-
return
109-
}
110-
},
111-
}
97+
return Query{
98+
Iterate: func(yield func(any) bool) {
99+
q.Iterate(func(item any) bool {
100+
if item.(int) > threshold {
101+
return yield(item)
102+
}
103+
return true
104+
})
105+
},
106+
}
112107
}
113108

114109
result := MyQuery(Range(1,10)).GreaterThan(5).Results()
@@ -117,25 +112,25 @@ result := MyQuery(Range(1,10)).GreaterThan(5).Results()
117112
## Generic Functions
118113

119114
Although Go doesn't implement generics, with some reflection tricks, you can use go-linq without
120-
typing `interface{}`s and type assertions. This will introduce a performance penalty (5x-10x slower)
115+
typing `any`s and type assertions. This will introduce a performance penalty (5x-10x slower)
121116
but will yield in a cleaner and more readable code.
122117

123118
Methods with `T` suffix (such as `WhereT`) accept functions with generic types. So instead of
124119

125-
.Select(func(v interface{}) interface{} {...})
120+
.Select(func(v any) any {...})
126121

127122
you can type:
128123

129124
.SelectT(func(v YourType) YourOtherType {...})
130125

131-
This will make your code free of `interface{}` and type assertions.
126+
This will make your code free of `any` and type assertions.
132127

133128
**Example 4: "MapReduce" in a slice of string sentences to list the top 5 most used words using generic functions**
134129

135130
```go
136131
var results []string
137132

138-
From(sentences).
133+
FromSlice(sentences).
139134
// split sentences to words
140135
SelectManyT(func(sentence string) Query {
141136
return From(strings.Split(sentence, " "))
@@ -161,11 +156,49 @@ From(sentences).
161156
ToSlice(&results)
162157
```
163158

159+
## Manual Iteration
160+
161+
Since **go-linq v4** manual iteration follows Go’s standard iterator pattern introduced with the `iter` package.
162+
The Query type exposes an `Iterate` field of type `iter.Seq[any]`, making it easier to integrate with Go’s native
163+
iteration style.
164+
165+
**Example 5: Iterate over a query using the standard `for ... range` loop**
166+
167+
```go
168+
q := FromSlice([]int{1, 2, 3, 4})
169+
170+
for v := range q.Iterate {
171+
fmt.Println(v)
172+
}
173+
```
174+
164175
**More examples** can be found in the [documentation](https://godoc.org/github.com/ahmetb/go-linq).
165176

177+
## Data Source Constructors
178+
179+
Since **go-linq v4**, a new family of constructor functions provides a type-safe and efficient way to create queries from
180+
various data sources. Each function is optimized for its specific input type, avoiding the overhead of reflection.
181+
182+
Available constructors:
183+
- `FromSlice` - creates a query from a slice
184+
- `FromMap` - creates a query from a map
185+
- `FromChannel` - creates a query from a channel
186+
- `FromChannelWithContext` - creates a query from a channel with `Context` support
187+
- `FromString` - creates a query from a string (iterating over runes)
188+
- `FromIterable` - creates a query from a custom collection implementing the `Iterable` interface
189+
190+
The older `From` function remains available for backward compatibility, but it relies on runtime reflection and is
191+
significantly less efficient. For all new code, it’s recommended to use the explicit `From*` constructors.
192+
166193
## Release Notes
167194

168195
```text
196+
v4.0.0 (2025-10-12)
197+
* Breaking change: Migrated to standard Go iterator pattern. (thanks @kalaninja!)
198+
* Added typed constructors: FromSlice(), FromMap(), FromChannel(),
199+
FromChannelWithContext(), FromString().
200+
* Breaking change: Removed FromChannelT() in favor of FromChannel().
201+
169202
v3.2.0 (2020-12-29)
170203
* Added FromChannelT().
171204
* Added DefaultIfEmpty().

aggregate.go

Lines changed: 27 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,23 @@
11
package linq
22

3+
import "iter"
4+
35
// Aggregate applies an accumulator function over a sequence.
46
//
57
// Aggregate method makes it simple to perform a calculation over a sequence of
6-
// values. This method works by calling f() one time for each element in source
8+
// values. This method works by calling f() one time for each element in a source
79
// except the first one. Each time f() is called, Aggregate passes both the
810
// element from the sequence and an aggregated value (as the first argument to
9-
// f()). The first element of source is used as the initial aggregate value. The
11+
// f()). The first element of the source is used as the initial aggregate value. The
1012
// result of f() replaces the previous aggregated value.
1113
//
1214
// Aggregate returns the final result of f().
13-
func (q Query) Aggregate(f func(interface{}, interface{}) interface{}) interface{} {
14-
next := q.Iterate()
15+
func (q Query) Aggregate(f func(accumulator, item any) any) any {
16+
next, stop := iter.Pull(q.Iterate)
17+
defer stop()
1518

16-
result, any := next()
17-
if !any {
19+
result, ok := next()
20+
if !ok {
1821
return nil
1922
}
2023

@@ -30,7 +33,7 @@ func (q Query) Aggregate(f func(interface{}, interface{}) interface{}) interface
3033
// - f is of type: func(TSource, TSource) TSource
3134
//
3235
// NOTE: Aggregate has better performance than AggregateT.
33-
func (q Query) AggregateT(f interface{}) interface{} {
36+
func (q Query) AggregateT(f any) any {
3437
fGenericFunc, err := newGenericFunc(
3538
"AggregateT", "f", f,
3639
simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(genericType))),
@@ -39,7 +42,7 @@ func (q Query) AggregateT(f interface{}) interface{} {
3942
panic(err)
4043
}
4144

42-
fFunc := func(result interface{}, current interface{}) interface{} {
45+
fFunc := func(result any, current any) any {
4346
return fGenericFunc.Call(result, current)
4447
}
4548

@@ -50,20 +53,18 @@ func (q Query) AggregateT(f interface{}) interface{} {
5053
// specified seed value is used as the initial accumulator value.
5154
//
5255
// Aggregate method makes it simple to perform a calculation over a sequence of
53-
// values. This method works by calling f() one time for each element in source
56+
// values. This method works by calling f() one time for each element in a source
5457
// except the first one. Each time f() is called, Aggregate passes both the
5558
// element from the sequence and an aggregated value (as the first argument to
5659
// f()). The value of the seed parameter is used as the initial aggregate value.
5760
// The result of f() replaces the previous aggregated value.
5861
//
5962
// Aggregate returns the final result of f().
60-
func (q Query) AggregateWithSeed(seed interface{},
61-
f func(interface{}, interface{}) interface{}) interface{} {
62-
63-
next := q.Iterate()
63+
func (q Query) AggregateWithSeed(seed any,
64+
f func(accumulator, item any) any) any {
6465
result := seed
6566

66-
for current, ok := next(); ok; current, ok = next() {
67+
for current := range q.Iterate {
6768
result = f(result, current)
6869
}
6970

@@ -76,8 +77,8 @@ func (q Query) AggregateWithSeed(seed interface{},
7677
//
7778
// NOTE: AggregateWithSeed has better performance than
7879
// AggregateWithSeedT.
79-
func (q Query) AggregateWithSeedT(seed interface{},
80-
f interface{}) interface{} {
80+
func (q Query) AggregateWithSeedT(seed any,
81+
f any) any {
8182
fGenericFunc, err := newGenericFunc(
8283
"AggregateWithSeed", "f", f,
8384
simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(genericType))),
@@ -86,7 +87,7 @@ func (q Query) AggregateWithSeedT(seed interface{},
8687
panic(err)
8788
}
8889

89-
fFunc := func(result interface{}, current interface{}) interface{} {
90+
fFunc := func(result any, current any) any {
9091
return fGenericFunc.Call(result, current)
9192
}
9293

@@ -106,14 +107,13 @@ func (q Query) AggregateWithSeedT(seed interface{},
106107
//
107108
// The final result of func is passed to resultSelector to obtain the final
108109
// result of Aggregate.
109-
func (q Query) AggregateWithSeedBy(seed interface{},
110-
f func(interface{}, interface{}) interface{},
111-
resultSelector func(interface{}) interface{}) interface{} {
110+
func (q Query) AggregateWithSeedBy(seed any,
111+
f func(accumulator, item any) any,
112+
resultSelector func(any) any) any {
112113

113-
next := q.Iterate()
114114
result := seed
115115

116-
for current, ok := next(); ok; current, ok = next() {
116+
for current := range q.Iterate {
117117
result = f(result, current)
118118
}
119119

@@ -127,9 +127,9 @@ func (q Query) AggregateWithSeedBy(seed interface{},
127127
//
128128
// NOTE: AggregateWithSeedBy has better performance than
129129
// AggregateWithSeedByT.
130-
func (q Query) AggregateWithSeedByT(seed interface{},
131-
f interface{},
132-
resultSelectorFn interface{}) interface{} {
130+
func (q Query) AggregateWithSeedByT(seed any,
131+
f any,
132+
resultSelectorFn any) any {
133133
fGenericFunc, err := newGenericFunc(
134134
"AggregateWithSeedByT", "f", f,
135135
simpleParamValidator(newElemTypeSlice(new(genericType), new(genericType)), newElemTypeSlice(new(genericType))),
@@ -138,7 +138,7 @@ func (q Query) AggregateWithSeedByT(seed interface{},
138138
panic(err)
139139
}
140140

141-
fFunc := func(result interface{}, current interface{}) interface{} {
141+
fFunc := func(result any, current any) any {
142142
return fGenericFunc.Call(result, current)
143143
}
144144

@@ -150,7 +150,7 @@ func (q Query) AggregateWithSeedByT(seed interface{},
150150
panic(err)
151151
}
152152

153-
resultSelectorFunc := func(result interface{}) interface{} {
153+
resultSelectorFunc := func(result any) any {
154154
return resultSelectorGenericFunc.Call(result)
155155
}
156156

aggregate_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@ import "strings"
55

66
func TestAggregate(t *testing.T) {
77
tests := []struct {
8-
input interface{}
9-
want interface{}
8+
input any
9+
want any
1010
}{
1111
{[]string{"apple", "mango", "orange", "passionfruit", "grape"}, "passionfruit"},
1212
{[]string{}, nil},
1313
}
1414

1515
for _, test := range tests {
16-
r := From(test.input).Aggregate(func(r interface{}, i interface{}) interface{} {
16+
r := From(test.input).Aggregate(func(r any, i any) any {
1717
if len(r.(string)) > len(i.(string)) {
1818
return r
1919
}
@@ -42,7 +42,7 @@ func TestAggregateWithSeed(t *testing.T) {
4242
want := "passionfruit"
4343

4444
r := From(input).AggregateWithSeed(want,
45-
func(r interface{}, i interface{}) interface{} {
45+
func(r any, i any) any {
4646
if len(r.(string)) > len(i.(string)) {
4747
return r
4848
}
@@ -70,13 +70,13 @@ func TestAggregateWithSeedBy(t *testing.T) {
7070
want := "PASSIONFRUIT"
7171

7272
r := From(input).AggregateWithSeedBy("banana",
73-
func(r interface{}, i interface{}) interface{} {
73+
func(r any, i any) any {
7474
if len(r.(string)) > len(i.(string)) {
7575
return r
7676
}
7777
return i
7878
},
79-
func(r interface{}) interface{} {
79+
func(r any) any {
8080
return strings.ToUpper(r.(string))
8181
},
8282
)

0 commit comments

Comments
 (0)