├── .gitignore ├── assert └── assert.go2 ├── collector └── collector.go2 ├── result ├── result_test.go2 └── result.go2 ├── stream ├── examples_test.go2 └── stream.go2 └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | *.go 2 | -------------------------------------------------------------------------------- /assert/assert.go2: -------------------------------------------------------------------------------- 1 | package assert 2 | 3 | import "testing" 4 | import "fmt" 5 | 6 | func Equal(type T comparable)( 7 | t *testing.T, 8 | actual, 9 | expected T, 10 | msgs ...string, 11 | ) bool { 12 | if actual != expected { 13 | msg := "" 14 | if len(msgs) > 0 { 15 | msg = fmt.Sprintf(msgs[0]+": ", msgs[1:]) 16 | } 17 | t.Errorf("%sexpected value: %v but got: %v.", msg, expected, actual) 18 | 19 | return false 20 | } 21 | 22 | return true 23 | } 24 | -------------------------------------------------------------------------------- /collector/collector.go2: -------------------------------------------------------------------------------- 1 | package collector 2 | 3 | type Collector(type Type, Col) interface { 4 | Supply() Col 5 | Accumulate(Col, Type) Col 6 | Finish(Col) Col 7 | Combine(Col, Col) Col 8 | } 9 | 10 | type impl(type Type, Col) struct { 11 | supply func() Col 12 | accumulate func(Col, Type) Col 13 | finish func(Col) Col 14 | combine func(Col, Col) Col 15 | } 16 | 17 | func Of(type Type, Col)( 18 | supply func() Col, 19 | accumulate func(Col, Type) Col, 20 | finish func(Col) Col, 21 | combine func(Col, Col) Col, 22 | ) Collector(Type, Col) { 23 | return impl(Type, Col){ 24 | supply: supply, 25 | accumulate: accumulate, 26 | finish: finish, 27 | combine: combine, 28 | } 29 | } 30 | 31 | func (collector impl(Type, Col)) Supply() Col { 32 | return collector.supply() 33 | } 34 | 35 | func (collector impl(Type, Col)) Accumulate(collection Col, item Type) Col { 36 | return collector.accumulate(collection, item) 37 | } 38 | 39 | func (collector impl(Type, Col)) Finish(collection Col) Col { 40 | if collector.finish == nil { 41 | return collection 42 | } 43 | return collector.finish(collection) 44 | } 45 | 46 | func (collector impl(Type, Col)) Combine(collection1, collection2 Col) Col { 47 | return collector.combine(collection1, collection2) 48 | } 49 | 50 | func ToMap(type Type interface{}, Key comparable, Value interface{})( 51 | mapper func(Type) (Key, Value), 52 | accumulator func(Value, Value) Value, 53 | ) Collector(Type, map[Key]Value) { 54 | return Of( 55 | func() map[Key]Value { 56 | return map[Key]Value{} 57 | }, 58 | func(table map[Key]Value, item Type) map[Key]Value { 59 | key, value := mapper(item) 60 | now, _ := table[key] 61 | table[key] = accumulator(now, value) 62 | return table 63 | }, 64 | func(table map[Key]Value) map[Key]Value { 65 | return table 66 | }, 67 | func(table1, table2 map[Key]Value) map[Key]Value { 68 | for k2, v2 := range table2 { 69 | v1, _ := table1[k2] 70 | table1[k2] = accumulator(v1, v2) 71 | } 72 | return table1 73 | }, 74 | ) 75 | } 76 | -------------------------------------------------------------------------------- /result/result_test.go2: -------------------------------------------------------------------------------- 1 | package result 2 | 3 | import ( 4 | "errors" 5 | "os" 6 | "testing" 7 | "strconv" 8 | 9 | "github.com/reconquest/goava/assert" 10 | ) 11 | 12 | func TestIsOk(t *testing.T) { 13 | assert.Equal(t, Ok(1).IsOk(), true) 14 | 15 | assert.Equal(t, Err(int)(os.ErrNotExist).IsOk(), false) 16 | } 17 | 18 | func TestIsErr(t *testing.T) { 19 | assert.Equal(t, Ok(1).IsErr(), false) 20 | 21 | assert.Equal(t, Err(int)(os.ErrNotExist).IsErr(), true) 22 | } 23 | 24 | func TestAnd(t *testing.T) { 25 | assert.Equal(t, Ok(1).And(Ok(2)).Ok(), 2) 26 | 27 | assert.Equal(t, Ok(1).And(Err(int)(errors.New("a"))).IsOk(), false) 28 | 29 | assert.Equal(t, Err(int)(errors.New("a")).And(Ok(1)).IsOk(), false) 30 | 31 | // The tech is not there yet 32 | // https://github.com/golang/go/issues/39878 33 | // assert.Equal(t, Ok(1).And(Err(int)(os.ErrClosed)).Err(), os.ErrClosed) 34 | } 35 | 36 | func TestOr(t *testing.T) { 37 | assert.Equal(t, Ok(1).Or(Ok(2)).Ok(), 1) 38 | 39 | assert.Equal(t, Ok(1).Or(Err(int)(os.ErrClosed)).Ok(), 1) 40 | 41 | assert.Equal(t, Err(int)(os.ErrClosed).Or(Ok(1)).Ok(), 1) 42 | 43 | assert.Equal(t, Err(int)(errors.New("a")).Or(Err(int)(errors.New("b"))).Err().Error(), "b") 44 | } 45 | 46 | func TestMatch(t *testing.T) { 47 | t.Run("ok", func(t *testing.T) { 48 | Match(From(strconv.Atoi("10")), 49 | func(i int) (s struct{}) { 50 | assert.Equal(t, i, 10) 51 | return 52 | }, 53 | func(err error) (s struct{}) { 54 | t.Fatal("unreachable") 55 | return 56 | }, 57 | ) 58 | }) 59 | 60 | t.Run("err", func(t *testing.T) { 61 | Match(From(strconv.Atoi("invalidstring")), 62 | func(i int) (s struct{}) { 63 | t.Fatal("unreachable") 64 | return 65 | }, 66 | func(err error) (s struct{}) { 67 | if err == nil { 68 | t.Errorf("expected not nil, got nil") 69 | } 70 | return 71 | }, 72 | ) 73 | }) 74 | } 75 | 76 | func TestFor(t *testing.T) { 77 | t.Run("ok", func(t *testing.T) { 78 | For(From(strconv.Atoi("10")), 79 | func(i int) { 80 | assert.Equal(t, i, 10) 81 | return 82 | }, 83 | func(err error) { 84 | t.Fatal("unreachable") 85 | return 86 | }, 87 | ) 88 | }) 89 | 90 | t.Run("err", func(t *testing.T) { 91 | For(From(strconv.Atoi("invalidstring")), 92 | func(i int) { 93 | t.Fatal("unreachable") 94 | return 95 | }, 96 | func(err error) { 97 | if err == nil { 98 | t.Errorf("expected not nil, got nil") 99 | } 100 | return 101 | }, 102 | ) 103 | }) 104 | } 105 | 106 | -------------------------------------------------------------------------------- /result/result.go2: -------------------------------------------------------------------------------- 1 | package result 2 | 3 | import "fmt" 4 | 5 | type Result(type T) interface { 6 | // IsOk returns true if the result is Ok. 7 | IsOk() bool 8 | 9 | // IsErr returns true if the result is Err. 10 | IsErr() bool 11 | 12 | // And returns res if the result is Ok, otherwise returns the Err value of self. 13 | And(res Result(T)) Result(T) 14 | 15 | // OrElse Calls fn if the result is Err, otherwise returns the Ok value of self. 16 | OrElse(func() Result(T)) Result(T) 17 | 18 | // Returns res if the result is Err, otherwise returns the Ok value of self. 19 | Or(res Result(T)) Result(T) 20 | 21 | // Ok returns the contained Ok value. 22 | // Panics if the result is an Err 23 | Ok() T 24 | 25 | // Err returns the contained Err value. Panics if the result is Ok 26 | Err() error 27 | 28 | // OkOr returns the contained Ok value or a provided default. 29 | OkOr(T) T 30 | // OkOrElse returns the contained Ok value or calls provided fn. 31 | OkOrElse(fn func() T) T 32 | 33 | // Expect returns the contained Ok value. 34 | // Panics if the value is an Err, with a panic message including the passed message, and the content of the Err. 35 | Expect(msg string) T 36 | 37 | // Expect returns the contained Err value. 38 | // Panics if the value is an Ok, with a panic message including the passed 39 | // message, and the content of the Ok. 40 | ExpectErr(msg string) error 41 | } 42 | 43 | func From(type T)(value T, err error) Result(T) { 44 | if err != nil { 45 | return Err(T)(err) // can't infer T 46 | } 47 | return Ok(value) 48 | } 49 | 50 | func Ok(type T)(value T) Result(T) { 51 | return &result(T){ 52 | ok: true, 53 | value: value, 54 | } 55 | } 56 | 57 | func Err(type T)(err error) Result(T) { 58 | return &result(T){ 59 | err: err, 60 | } 61 | } 62 | 63 | type result(type T) struct { 64 | value T 65 | err error 66 | ok bool 67 | } 68 | 69 | func (result *result(T)) IsOk() bool { 70 | return result.ok 71 | } 72 | 73 | func (result *result(T)) IsErr() bool { 74 | return !result.ok 75 | } 76 | 77 | func (result *result(T)) And(res Result(T)) Result(T) { 78 | if result.IsErr() { 79 | return Err(T)(result.Err()) 80 | } 81 | if res.IsErr() { 82 | return Err(T)(res.Err()) 83 | } 84 | return res 85 | } 86 | 87 | func (result *result(T)) Expect(msg string) T { 88 | if result.ok { 89 | return result.value 90 | } 91 | 92 | panic(msg + ": " + result.err.Error()) 93 | } 94 | 95 | func (result *result(T)) ExpectErr(msg string) error { 96 | if !result.ok { 97 | return result.err 98 | } 99 | 100 | panic(fmt.Sprintf("%s: %v", msg, result.value)) 101 | } 102 | 103 | func (result *result(T)) Or(res Result(T)) Result(T) { 104 | if result.ok { 105 | return result 106 | } else { 107 | return res 108 | } 109 | } 110 | 111 | func (result *result(T)) OrElse(fn func() Result(T)) Result(T) { 112 | if result.ok { 113 | return result 114 | } else { 115 | return fn() 116 | } 117 | } 118 | 119 | func (result *result(T)) Ok() T { 120 | if !result.ok { 121 | panic(result.err) 122 | } 123 | 124 | return result.value 125 | } 126 | 127 | func (result *result(T)) Err() error { 128 | if result.ok { 129 | panic(result.ok) 130 | } 131 | 132 | return result.err 133 | } 134 | 135 | func (result *result(T)) OkOr(res T) T { 136 | if result.ok { 137 | return result.value 138 | } 139 | 140 | return res 141 | } 142 | 143 | func (result *result(T)) OkOrElse(fn func() T) T { 144 | if result.ok { 145 | return result.value 146 | } 147 | 148 | return fn() 149 | } 150 | 151 | // TODO: R type can't handle tuples 152 | func Match(type T, R)(r Result(T), ok func(T) R, err func(error) R) R { 153 | if r.IsErr() { 154 | return err(r.Err()) 155 | } 156 | 157 | return ok(r.Ok()) 158 | } 159 | 160 | func For(type T)(r Result(T), ok func(T), err func(error)) { 161 | if r.IsErr() { 162 | err(r.Err()) 163 | return 164 | } 165 | 166 | ok(r.Ok()) 167 | } 168 | 169 | 170 | -------------------------------------------------------------------------------- /stream/examples_test.go2: -------------------------------------------------------------------------------- 1 | package stream_test 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | "time" 7 | "sort" 8 | 9 | "github.com/reconquest/goava/collector" 10 | "github.com/reconquest/goava/stream" 11 | ) 12 | 13 | type Movie struct { 14 | Name string 15 | Rating int 16 | } 17 | 18 | type MoviesByRating map[int][]string 19 | 20 | func TestFilter(t *testing.T) { 21 | result := stream. 22 | Of([]int{1, 2, 3, 4, 5}). 23 | Filter(func(x int) bool { 24 | return x%2 == 0 25 | }). 26 | Slice() 27 | if !same(result, []int{2, 4}) { 28 | t.Fatal(result) 29 | } 30 | } 31 | 32 | func TestMap(t *testing.T) { 33 | result := stream.Map( 34 | stream.Of([]int{1, 2}), 35 | func(x int) string { 36 | return fmt.Sprint(x) 37 | }, 38 | ).Slice() 39 | if !same(result, []string{"1", "2"}) { 40 | t.Fatal(result) 41 | } 42 | } 43 | 44 | type MovieVote struct { 45 | Name string 46 | Score int 47 | } 48 | 49 | func TestToMap(t *testing.T) { 50 | items := stream.Of( 51 | []MovieVote{ 52 | {Name: "A", Score: 1}, 53 | {Name: "A", Score: 2}, 54 | {Name: "B", Score: 9}, 55 | {Name: "B", Score: 10}, 56 | {Name: "B", Score: 8}, 57 | {Name: "C", Score: 7}, 58 | {Name: "C", Score: 8}, 59 | {Name: "C", Score: 7}, 60 | }, 61 | ) 62 | 63 | collect := collector.ToMap(MovieVote, string, int)( 64 | func(movie MovieVote) (string, int) { 65 | // allow other threads to jump on entries 66 | // otherwise not all goroutines are involved 67 | time.Sleep(time.Millisecond * 30) 68 | 69 | return movie.Name, movie.Score 70 | }, 71 | func(total, score int) int { 72 | return total + score 73 | }, 74 | ) 75 | 76 | sequential := stream.Collect(items, collect) 77 | 78 | if len(sequential) != 3 { 79 | t.Errorf("[sequential] expected 3 items, got %d", len(sequential)) 80 | } 81 | 82 | if sequential["A"] != 3 { 83 | t.Error("[sequential] A: ", sequential["A"]) 84 | } 85 | if sequential["B"] != 27 { 86 | t.Error("[sequential] B: ", sequential["B"]) 87 | } 88 | if sequential["C"] != 22 { 89 | t.Error("[sequential] C: ", sequential["C"]) 90 | } 91 | 92 | items = items.Parallel() 93 | parallel := stream.Collect(items, collect) 94 | 95 | if len(parallel) != 3 { 96 | t.Errorf("[parallel] expected 3 items, got %d", len(parallel)) 97 | } 98 | 99 | if parallel["A"] != 3 { 100 | t.Error("[parallel] A: ", parallel["A"]) 101 | } 102 | if parallel["B"] != 27 { 103 | t.Error("[parallel] B: ", parallel["B"]) 104 | } 105 | if parallel["C"] != 22 { 106 | t.Error("[parallel] C: ", parallel["C"]) 107 | } 108 | } 109 | 110 | func TestStrangeMoviesCustomCollectorButAtLeastItIsParallel(t *testing.T) { 111 | movies := stream.Of( 112 | []Movie{ 113 | {Name: "1 star A", Rating: 1}, 114 | {Name: "1 star B", Rating: 1}, 115 | {Name: "1 star C", Rating: 1}, 116 | {Name: "3 stars A", Rating: 3}, 117 | {Name: "3 stars B", Rating: 3}, 118 | {Name: "5 stars A", Rating: 5}, 119 | {Name: "9 stars A", Rating: 9}, 120 | }, 121 | ).Parallel() 122 | 123 | collect := collector.Of( 124 | func() MoviesByRating { 125 | return MoviesByRating{} 126 | }, 127 | func(col MoviesByRating, movie Movie) MoviesByRating { 128 | names, _ := col[movie.Rating] 129 | col[movie.Rating] = append(names, movie.Name) 130 | return col 131 | }, 132 | func(col MoviesByRating) MoviesByRating { 133 | return col 134 | }, 135 | func(col1 MoviesByRating, col2 MoviesByRating) MoviesByRating { 136 | for k2, v2 := range col2 { 137 | v1, _ := col1[k2] 138 | col1[k2] = append(v1, v2...) 139 | } 140 | 141 | return col1 142 | }, 143 | ) 144 | 145 | result := stream.Collect(Movie, MoviesByRating)(movies, collect) 146 | if len(result) != 4 { 147 | t.Errorf("expected 4 items, got %d", len(result)) 148 | } 149 | 150 | sort.Strings(result[1]) 151 | if !same(result[1], []string{"1 star A", "1 star B", "1 star C"}) { 152 | t.Error(1, result[1]) 153 | } 154 | 155 | sort.Strings(result[3]) 156 | if !same(result[3], []string{"3 stars A", "3 stars B"}) { 157 | t.Error(3, result[3]) 158 | } 159 | 160 | if !same(result[5], []string{"5 stars A"}) { 161 | t.Error(5, result[5]) 162 | } 163 | if !same(result[9], []string{"9 stars A"}) { 164 | t.Error(9, result[9]) 165 | } 166 | } 167 | 168 | func same(type Type comparable)(a, b []Type) bool { 169 | if len(a) != len(b) { 170 | return false 171 | } 172 | for k, v := range b { 173 | if a[k] != v { 174 | return false 175 | } 176 | } 177 | for k, v := range a { 178 | if b[k] != v { 179 | return false 180 | } 181 | } 182 | return true 183 | } 184 | -------------------------------------------------------------------------------- /stream/stream.go2: -------------------------------------------------------------------------------- 1 | package stream 2 | 3 | import ( 4 | "github.com/reconquest/goava/collector" 5 | "runtime" 6 | "sync" 7 | ) 8 | 9 | type Stream(type Type) struct { 10 | slice []Type 11 | parallel bool 12 | } 13 | 14 | type ParallelStream(type Type) struct { 15 | Stream (Type) 16 | } 17 | 18 | func Of(type Type)(slice []Type) *Stream(Type) { 19 | return &Stream(Type){ 20 | slice: slice, 21 | } 22 | } 23 | 24 | func (stream *Stream(Type)) Parallel() *Stream(Type) { 25 | stream.parallel = true 26 | return stream 27 | } 28 | 29 | func (stream *Stream(Type)) Sequential() *Stream(Type) { 30 | stream.parallel = false 31 | return stream 32 | } 33 | 34 | func (stream *Stream(Type)) Filter(predicate func(Type) bool) *Stream(Type) { 35 | filtered := []Type{} 36 | for _, item := range stream.slice { 37 | if predicate(item) { 38 | filtered = append(filtered, item) 39 | } 40 | } 41 | stream.slice = filtered 42 | return stream 43 | } 44 | 45 | func (stream *Stream(Type)) AllMatch(predicate func(Type) bool) bool { 46 | if len(stream.slice) == 0 { 47 | return false 48 | } 49 | 50 | for _, item := range stream.slice { 51 | if !predicate(item) { 52 | return false 53 | } 54 | } 55 | 56 | return true 57 | } 58 | 59 | func (stream *Stream(Type)) AnyMatch(predicate func(Type) bool) bool { 60 | if len(stream.slice) == 0 { 61 | return false 62 | } 63 | 64 | for _, item := range stream.slice { 65 | if predicate(item) { 66 | return true 67 | } 68 | } 69 | 70 | return false 71 | } 72 | 73 | func (stream *Stream(Type)) NoneMatch(predicate func(Type) bool) bool { 74 | if len(stream.slice) == 0 { 75 | return true 76 | } 77 | 78 | for _, item := range stream.slice { 79 | if predicate(item) { 80 | return false 81 | } 82 | } 83 | 84 | return true 85 | } 86 | 87 | func (stream *Stream(Type)) Len() int { 88 | return len(stream.slice) 89 | } 90 | 91 | func (stream *Stream(Type)) ForEach(predicate func(Type)) *Stream(Type) { 92 | for _, item := range stream.slice { 93 | predicate(item) 94 | } 95 | return stream 96 | } 97 | 98 | func (stream *Stream(Type)) Slice() []Type { 99 | return stream.slice 100 | } 101 | 102 | func (stream *Stream(Type)) Skip(n int) *Stream(Type) { 103 | if n > cap(stream.slice) { 104 | n = cap(stream.slice) 105 | } 106 | stream.slice = stream.slice[n:] 107 | return stream 108 | } 109 | 110 | func Map(type Type, R)(stream *Stream(Type), predicate func(Type) R) *Stream(R) { 111 | slice := make([]R, len(stream.slice)) 112 | for i, item := range stream.slice { 113 | slice[i] = predicate(item) 114 | } 115 | return Of(slice) 116 | } 117 | 118 | func Reduce(type Type, R)(stream *Stream(Type), predicate func(R, Type) R) R { 119 | var result R 120 | for _, item := range stream.slice { 121 | result = predicate(result, item) 122 | } 123 | return result 124 | } 125 | 126 | func Collect(type Type, Col)( 127 | stream *Stream(Type), 128 | collector collector.Collector(Type, Col), 129 | ) Col { 130 | if stream.parallel { 131 | return collectParallel(Type, Col)(stream, collector) 132 | } 133 | 134 | return collectSequential(Type, Col)(stream, collector) 135 | } 136 | 137 | func collectSequential(type Type, Col)( 138 | stream *Stream(Type), 139 | collector collector.Collector(Type, Col), 140 | ) Col { 141 | collection := collector.Supply() 142 | for _, item := range stream.slice { 143 | collection = collector.Accumulate(collection, item) 144 | } 145 | return collector.Finish(collection) 146 | } 147 | 148 | func collectParallel(type Type, Col)( 149 | stream *Stream(Type), 150 | collector collector.Collector(Type, Col), 151 | ) Col { 152 | cpu := runtime.NumCPU() 153 | 154 | pipe := make(chan Type) 155 | done := make(chan struct{}) 156 | mutex := sync.Mutex{} 157 | chunks := []Col{} 158 | 159 | workers := sync.WaitGroup{} 160 | workers.Add(cpu) 161 | for i := 0; i < cpu; i++ { 162 | go func() { 163 | defer workers.Done() 164 | 165 | chunk := collector.Supply() 166 | 167 | loop: 168 | for { 169 | select { 170 | case <-done: 171 | break loop 172 | case item := <-pipe: 173 | chunk = collector.Accumulate(chunk, item) 174 | } 175 | } 176 | 177 | mutex.Lock() 178 | chunks = append(chunks, chunk) 179 | mutex.Unlock() 180 | }() 181 | } 182 | 183 | for i, _ := range stream.slice { 184 | pipe <- stream.slice[i] 185 | } 186 | 187 | close(done) 188 | 189 | workers.Wait() 190 | 191 | collection := chunks[0] 192 | for _, chunk := range chunks[1:] { 193 | collection = collector.Combine(collection, chunk) 194 | } 195 | 196 | return collector.Finish(collection) 197 | } 198 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # goava 2 | 3 | Goava is a set of libraries based on draft version of Go 2 generics. 4 | 5 | [Read the blog post about this project](https://snake-ci.com/blog/go2go-stream) 6 | 7 | It's not ready for production or even development use. 8 | 9 | Bugs found while working on this project: 10 | * [39834][bug-1] 11 | * [39839][bug-2] 12 | * [39853][bug-3] 13 | * [39878][bug-4] 14 | 15 | Read more about the draft design of Go Generics: 16 | [The Next Step for Generics][golang-post] 17 | 18 | ## Table Of Contents 19 | 20 | * [Stream(T)](#streamt) 21 | * [Result(T)](#resultt) 22 | 23 | # Stream(T) 24 | 25 | ### Initialize stream 26 | 27 | ```go 28 | Of([]int{1, 2, 3, 4}) 29 | 30 | or 31 | 32 | Of([]string{"q", "w", "e"}) 33 | ``` 34 | 35 | ## Filtering a stream 36 | 37 | Let's get rid of some numbers: 38 | 39 | ```go 40 | stream. 41 | Of([]int{1, 2, 3, 4, 5}). 42 | Filter(func(x int) bool { 43 | return x%2 == 0 44 | }). 45 | Slice() 46 | ``` 47 | 48 | ## Mapping stream of A to stream of B 49 | 50 | Let's get rid of some numbers 51 | ```go 52 | stream.Map( 53 | stream.Of([]int{1, 2}), 54 | func(x int) string { return fmt.Sprint(x) }, 55 | ).Slice() 56 | ``` 57 | 58 | ## Matching 59 | 60 | * `Stream(Type).AnyMatch(func(Type) bool)` returns true if any of elements in stream match given predicate 61 | * `Stream(Type).NoneMatch(func(Type) bool)` returns true if none of elements in stream match given predicate 62 | * `Stream(Type).AllMatch(func(Type) bool)` returns true if all of elements in stream match given predicate 63 | 64 | ## Collecting Stream(Type) to a map[Key]Value 65 | 66 | Define a stream of movies with name and score that users gave: 67 | ```go 68 | items := stream.Of( 69 | []MovieVote{ 70 | {Name: "A", Score: 1}, 71 | {Name: "A", Score: 2}, 72 | {Name: "B", Score: 9}, 73 | {Name: "B", Score: 10}, 74 | {Name: "B", Score: 8}, 75 | {Name: "C", Score: 7}, 76 | {Name: "C", Score: 8}, 77 | {Name: "C", Score: 7}, 78 | }, 79 | ) 80 | ``` 81 | 82 | Now let's collect create a hashmap where key is a name of movie and value is sum of scores: 83 | 84 | ```go 85 | result := stream.Collect( 86 | items, 87 | collector.ToMap(MovieVote, string, int)( 88 | func(movie MovieVote) (string, int) { 89 | return movie.Name, movie.Score 90 | }, 91 | func(total, score int) int { 92 | return total + score 93 | }, 94 | ), 95 | ) 96 | ``` 97 | 98 | The first argument to `ToMap` is a mapper, it receives original item and returns a tuple of key and value. The second 99 | argument to `ToMap` is a accumulator, it current value and new value given by mapper or during combining parallel stream 100 | chunks. Yes, *parallel*. 101 | 102 | A stream can be switched to parallel mode, so mapping and accumulation will be done in concurrent mode. 103 | 104 | ```go 105 | items = items.Parallel() 106 | ``` 107 | 108 | A stream can be switched back to sequential mode by calling `Sequential()` function. 109 | 110 | # Result(T) 111 | 112 | The main goal was to bring Rust's powerful [Result](https://doc.rust-lang.org/std/result/enum.Result.html) to Go. 113 | Unfortunately, it can't be ported completely due to lack of pattern matching in Go. 114 | 115 | The only possible way was to use channels and select {}, but it would be too complicated and it would be just an imitation. 116 | 117 | Methods implemented so far: 118 | ### IsOk() bool 119 | IsOk returns true if the result is Ok. 120 | 121 | ### IsErr() bool 122 | IsErr returns true if the result is Err. 123 | 124 | ### And(res Result(T)) Result(T) 125 | And returns res if the result is Ok, otherwise returns the Err value of self. 126 | 127 | ### OrElse(func() Result(T)) Result(T) 128 | OrElse Calls fn if the result is Err, otherwise returns the Ok value of self. 129 | 130 | ### Or(res Result(T)) Result(T) 131 | Returns res if the result is Err, otherwise returns the Ok value of self. 132 | 133 | ### Ok() T 134 | OK returns the contained Ok value. 135 | Panics if the result is an Err 136 | 137 | ### Err() error 138 | Err returns the contained Err value. Panics if the result is Ok 139 | 140 | ### OkOr(T) T 141 | OkOr returns the contained Ok value or a provided default. 142 | ### OkOrElse(fn func() T) T 143 | OkOrElse returns the contained Ok value or calls provided fn. 144 | 145 | ### Expect(msg string) T 146 | Expect returns the contained Ok value. 147 | Panics if the value is an Err, with a panic message including the passed message, and the content of the Err. 148 | 149 | ### ExpectErr(msg string) error 150 | Expect returns the contained Err value. 151 | Panics if the value is an Ok, with a panic message including the passed 152 | message, and the content of the Ok. 153 | 154 | ## Example 155 | 156 | ```go 157 | func Success() Result(int) { 158 | return Ok(42) 159 | } 160 | 161 | func Fail() Result(int) { 162 | return Err(int)(errors.New("failure")) 163 | } 164 | 165 | func main() { 166 | fmt.Println(Fail().OrElse(Success)) 167 | 168 | result := Success().And(Fail()) 169 | if result.IsOk() { 170 | fmt.Println("it's ok", result.Ok()) 171 | } else { 172 | fmt.Println("it's not ok, but here is default value:", result.OkOr(1)) 173 | } 174 | } 175 | ``` 176 | 177 | 178 | ### License 179 | 180 | MIT 181 | 182 | [bug-1]: https://github.com/golang/go/issues/39834 183 | [bug-2]: https://github.com/golang/go/issues/39839 184 | [bug-3]: https://github.com/golang/go/issues/39853 185 | [bug-4]: https://github.com/golang/go/issues/39878 186 | [golang-post]: https://blog.golang.org/generics-next-step 187 | --------------------------------------------------------------------------------