Skip to content

Commit

Permalink
simplify api
Browse files Browse the repository at this point in the history
Signed-off-by: kom0055 <[email protected]>
  • Loading branch information
kom0055 committed Oct 12, 2022
1 parent 6b357b6 commit 465acdf
Show file tree
Hide file tree
Showing 34 changed files with 649 additions and 670 deletions.
55 changes: 26 additions & 29 deletions README.MD
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# go-flinx
# go-flinx

## Functional LINQ IN Go

[![Sourcegraph](https://sourcegraph.com/github.com/kom0055/go-flinx/-/badge.svg)](https://sourcegraph.com/github.com/kom0055/go-flinx?badge)
Expand All @@ -11,21 +12,20 @@
Thanks [ahmetb/go-linq](https://github.com/ahmetb/go-linq) for a guidance.

Rewrite [ahmetb/go-linq](https://github.com/ahmetb/go-linq) for these reasons:

1. Not compatible with generics. I've been enough to use type assertions.
2. Low performance when use generic function. You could use generic through function ends with 't', but it's not that efficient and not that elegant.
2. Low performance when use generic function. You could use generic through function ends with 't', but it's not that
efficient and not that elegant.

So to solve my problems, I choose to rewrite it using generics and functional-programing.
You know that generics in Go is not that flexible, but feature of Currying and Lazy Evaluation could make this.



## Installation

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

go get github.com/kom0055/go-flinx


## Quickstart

If you were a dotNet/C# developer but a gopher now, you must miss the smooth grammar of LINQ.
Expand All @@ -34,11 +34,10 @@ If you were a fans of functional-programing but a gopher now, you must be eager

Now, you could both enjoy LINQ and functional-programing through this package.


A simple case like below

```go
squares := ToSlice[int](Select[int, int](func(x int) int { return x * x })(Range(1, 10)))
squares := ToSlice(Select(func (x int) int { return x * x })(Range(1, 10)))
```

For more cases, you could visit [example_test.go](example_test.go)
Expand All @@ -49,36 +48,34 @@ Using ahmetb's go-linq comparison benchmark (see [benchmark_test.go](benchmark_t

SelectWhereFirst:

| Lib | Time |
|:------------------------|:-----------------------:|
| go-flinx | 153.4 ns/op |
| go-flink | 162.6 ns/op |
| gp-flink(generics func) | 1043 ns/op |
| Lib | Time |
|:-------------------------|:-----------------------:|
| go-flinx | 153.4 ns/op |
| go-linq | 162.6 ns/op |
| gp-linq(generics func) | 1043 ns/op |

Sum:

| Lib | Time |
|:------------------------|:-----------------------:|
| go-flinx | 31092617 ns/op |
| go-flink | 119863792 ns/op |
| gp-flink(generics func) | 2249057541 ns/op |

| Lib | Time |
|:-------------------------|:----------------:|
| go-flinx | 31092617 ns/op |
| go-linq | 119863792 ns/op |
| gp-linq(generics func) | 2249057541 ns/op |

ZipSkipTake:

| Lib | Time |
|:------------------------|:-----------------------:|
| go-flinx | 120.3 ns/op |
| go-flink | 108.4 ns/op |
| gp-flink(generics func) | 562.8 ns/op |

| Lib | Time |
|:-------------------------|:-----------:|
| go-flinx | 120.3 ns/op |
| go-linq | 108.4 ns/op |
| gp-linq(generics func) | 562.8 ns/op |

FromChannel:

| Lib | Time |
|:------------------------|:-----------------------:|
| go-flinx | 1206086167 ns/op |
| go-flink | 1312612709 ns/op |
| gp-flink(from channelt) | 1975103125 ns/op |
| Lib | Time |
|:-------------------------|:----------------:|
| go-flinx | 1206086167 ns/op |
| go-linq | 1312612709 ns/op |
| gp-linq(from channelt) | 1975103125 ns/op |

We could make conclusion that if you chase for efficient performance and elegant grammar, go-flinx is a good choice.
19 changes: 10 additions & 9 deletions aggregate_test.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
package flinx

import (
"gotest.tools/v3/assert"
"testing"

"gotest.tools/v3/assert"
)
import "strings"

func Test_Aggregate(t *testing.T) {
First[int](Select[int, int](func(i int) int {
First(Select(func(i int) int {
return i * 2
})(Where[int](func(i int) bool {
})(Where(func(i int) bool {
return i%2 == 0
})(Range(1, 10))))

Expand All @@ -21,15 +22,15 @@ func Test_Aggregate(t *testing.T) {
{[]string{}, ""},
}

aggr := Aggregate[string](func(r, i string) string {
aggr := Aggregate(func(r, i string) string {
if len(r) > len(i) {
return r
}
return i
})
for _, test := range tests {

r := aggr(FromSlice[string](test.input))
r := aggr(FromSlice(test.input))
assert.Equal(t, r, test.want)

}
Expand All @@ -39,13 +40,13 @@ func TestAggregateWithSeed(t *testing.T) {
input := []string{"apple", "mango", "orange", "banana", "grape"}
want := "passionfruit"

aggr := AggregateWithSeed[string](want, func(r, i string) string {
aggr := AggregateWithSeed(want, func(r, i string) string {
if len(r) > len(i) {
return r
}
return i
})
r := aggr(FromSlice[string](input))
r := aggr(FromSlice(input))

assert.Equal(t, r, want)
}
Expand All @@ -54,7 +55,7 @@ func TestAggregateWithSeedBy(t *testing.T) {
input := []string{"apple", "mango", "orange", "passionfruit", "grape"}
want := "PASSIONFRUIT"

aggr := AggregateWithSeedBy[string, string]("banana", func(r, i string) string {
aggr := AggregateWithSeedBy("banana", func(r, i string) string {
if len(r) > len(i) {
return r
}
Expand All @@ -63,7 +64,7 @@ func TestAggregateWithSeedBy(t *testing.T) {
func(r string) string {
return strings.ToUpper(r)
})
r := aggr(FromSlice[string](input))
r := aggr(FromSlice(input))

assert.Equal(t, r, want)
}
28 changes: 13 additions & 15 deletions benchmark_test.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package flinx

import (
"github.com/ahmetb/go-linq/v3"
"testing"

"github.com/ahmetb/go-linq/v3"
)

const (
Expand All @@ -12,16 +13,15 @@ const (
func BenchmarkSelectWhereFirst(b *testing.B) {

b.Run("BenchmarkSelectWhereFirst_flinx", func(b *testing.B) {
selectFn := Select[int](func(i int) int {
selectFn := Select(func(i int) int {
return -i
})
whereFn := Where[int](func(i int) bool {
whereFn := Where(func(i int) bool {
return i > -5
})
firstFn := First[int]

for n := 0; n < b.N; n++ {
firstFn(whereFn(selectFn(Range(1, size))))
First(whereFn(selectFn(Range(1, size))))
}
})
b.Run("BenchmarkSelectWhereFirst_linq", func(b *testing.B) {
Expand Down Expand Up @@ -49,12 +49,11 @@ func BenchmarkSelectWhereFirst(b *testing.B) {

func BenchmarkSum(b *testing.B) {
b.Run("BenchmarkSum_flinx", func(b *testing.B) {
whereFn := Where[int](func(i int) bool {
whereFn := Where(func(i int) bool {
return i%2 == 0
})
sumFn := Sum[int, int]
for n := 0; n < b.N; n++ {
sumFn(whereFn(Range(1, size)))
Sum(whereFn(Range(1, size)))
}
})

Expand All @@ -77,19 +76,18 @@ func BenchmarkSum(b *testing.B) {

func BenchmarkZipSkipTake(b *testing.B) {
b.Run("BenchmarkZipSkipTake_flinx", func(b *testing.B) {
takeFn := Take[int](5)
skipFn := Skip[int](2)

selectFn := Select[int](func(i int) int {
selectFn := Select(func(i int) int {
return i * 2
})

zipFn := Zip[int, int, int](func(i, j int) int {
zipFn := Zip(func(i, j int) int {
return i + j
})

for n := 0; n < b.N; n++ {
takeFn(skipFn(zipFn(Range(1, size), selectFn(Range(1, size)))))

Take(Skip(zipFn(Range(1, size), selectFn(Range(1, size))), 2), 5)
}
})

Expand All @@ -116,7 +114,7 @@ func BenchmarkZipSkipTake(b *testing.B) {

func BenchmarkFromChannel(b *testing.B) {
b.Run("BenchmarkFromChannel_flinx", func(b *testing.B) {
allFn := All[int](func(i int) bool {
allFn := All(func(i int) bool {
return true
})

Expand All @@ -129,7 +127,7 @@ func BenchmarkFromChannel(b *testing.B) {

close(ch)
}()
allFn(FromChannel[int](ch))
allFn(FromChannel(ch))
}
})

Expand Down
76 changes: 37 additions & 39 deletions concat.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,30 @@ package flinx

// Append inserts an item to the end of a collection, so it becomes the last
// item.
func Append[T any](items ...T) func(q Query[T]) Query[T] {
return func(q Query[T]) Query[T] {
length := len(items)
var defaultValue T
return Query[T]{
Iterate: func() Iterator[T] {
next := q.Iterate()
index := 0

return func() (T, bool) {
i, ok := next()
if ok {
return i, ok
}
if index < length {
idx := index
index++
func Append[T any](q Query[T], items ...T) Query[T] {

return items[idx], true
}
length := len(items)
var defaultValue T
return Query[T]{
Iterate: func() Iterator[T] {
next := q.Iterate()
index := 0

return func() (T, bool) {
i, ok := next()
if ok {
return i, ok
}
if index < length {
idx := index
index++

return defaultValue, false
return items[idx], true
}
},
}

return defaultValue, false
}
},
}

}
Expand Down Expand Up @@ -62,26 +61,25 @@ func Concat[T any](q, q2 Query[T]) Query[T] {

// Prepend inserts an item to the beginning of a collection, so it becomes the
// first item.
func Prepend[T any](items ...T) func(q Query[T]) Query[T] {
return func(q Query[T]) Query[T] {
length := len(items)
return Query[T]{
Iterate: func() Iterator[T] {
next := q.Iterate()
index := 0

return func() (T, bool) {
if index < length {
idx := index
index++
return items[idx], true
func Prepend[T any](q Query[T], items ...T) Query[T] {

}
length := len(items)
return Query[T]{
Iterate: func() Iterator[T] {
next := q.Iterate()
index := 0

return func() (T, bool) {
if index < length {
idx := index
index++
return items[idx], true

return next()
}
},
}

return next()
}
},
}

}
15 changes: 6 additions & 9 deletions concat_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ import "testing"
func TestAppend(t *testing.T) {
input := []int{1, 2, 3, 4}
want := []int{1, 2, 3, 4, 5}
appendFn := Append[int](5)
if q := appendFn(FromSlice[int](input)); !validateQuery[int](q, want) {
t.Errorf("From(%v).Append()=%v expected %v", input, toSlice[int](q), want)
if q := Append(FromSlice(input), 5); !ValidateQuery(q, want) {
t.Errorf("From(%v).Append()=%v expected %v", input, ToSlice(q), want)
}
}

Expand All @@ -16,17 +15,15 @@ func TestConcat(t *testing.T) {
input2 := []int{4, 5}
want := []int{1, 2, 3, 4, 5}

concatFn := Concat[int]
if q := concatFn(FromSlice[int](input1), FromSlice[int](input2)); !validateQuery[int](q, want) {
t.Errorf("From(%v).Concat(%v)=%v expected %v", input1, input2, toSlice(q), want)
if q := Concat(FromSlice(input1), FromSlice(input2)); !ValidateQuery(q, want) {
t.Errorf("From(%v).Concat(%v)=%v expected %v", input1, input2, ToSlice(q), want)
}
}

func TestPrepend(t *testing.T) {
input := []int{1, 2, 3, 4}
want := []int{0, 1, 2, 3, 4}
prependFn := Prepend[int](0)
if q := prependFn(FromSlice[int](input)); !validateQuery[int](q, want) {
t.Errorf("From(%v).Prepend()=%v expected %v", input, toSlice(q), want)
if q := Prepend(FromSlice(input), 0); !ValidateQuery(q, want) {
t.Errorf("From(%v).Prepend()=%v expected %v", input, ToSlice(q), want)
}
}
Loading

0 comments on commit 465acdf

Please sign in to comment.