├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── TODO.md ├── drop_chan.go ├── drop_chan_example_test.go ├── drop_chan_test.go ├── drop_while.go ├── drop_while_chan.go ├── drop_while_chan_example_test.go ├── drop_while_chan_test.go ├── drop_while_slice.go ├── drop_while_slice_example_test.go ├── drop_while_slice_test.go ├── field_by_name.go ├── field_by_name_example_test.go ├── field_by_name_test.go ├── filter.go ├── filter_chan.go ├── filter_chan_example_test.go ├── filter_chan_test.go ├── filter_slice.go ├── filter_slice_example_test.go ├── filter_slice_test.go ├── flatten_chan.go ├── flatten_chan_example_test.go ├── flatten_chan_test.go ├── flatten_slice.go ├── flatten_slice_example_test.go ├── flatten_slice_test.go ├── for_each.go ├── for_each_chan.go ├── for_each_chan_example_test.go ├── for_each_chan_test.go ├── for_each_slice.go ├── for_each_slice_example_test.go ├── for_each_slice_test.go ├── iterate.go ├── iterate_chan.go ├── iterate_chan_example_test.go ├── iterate_chan_test.go ├── map.go ├── map_cat_chan.go ├── map_cat_chan_example_test.go ├── map_cat_chan_test.go ├── map_cat_slice.go ├── map_cat_slice_example_test.go ├── map_cat_slice_test.go ├── map_chan.go ├── map_chan_example_test.go ├── map_chan_test.go ├── map_slice.go ├── map_slice_example_test.go ├── map_slice_test.go ├── map_test.go ├── onto_chan.go ├── onto_chan_example_test.go ├── onto_chan_test.go ├── reduce.go ├── reduce_chan.go ├── reduce_chan_example_test.go ├── reduce_chan_test.go ├── reduce_slice.go ├── reduce_slice_example_test.go ├── reduce_slice_test.go ├── repeatedly.go ├── repeatedly_chan.go ├── repeatedly_chan_example_test.go ├── repeatedly_chan_test.go ├── take_chan.go ├── take_chan_example_test.go ├── take_chan_test.go ├── take_while.go ├── take_while_chan.go ├── take_while_chan_example_test.go ├── take_while_chan_test.go ├── take_while_slice.go ├── take_while_slice_example_test.go ├── take_while_slice_test.go ├── util.go ├── zip_chan.go ├── zip_chan_example_test.go ├── zip_chan_test.go ├── zip_slice.go ├── zip_slice_example_test.go └── zip_slice_test.go /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | 24 | *.sw? 25 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | go: 3 | - 1.1 4 | - 1.2 5 | - release 6 | - tip 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (C) 2014 by Paul Bellamy 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # pipe [![Build Status](https://travis-ci.org/paulbellamy/pipe.png?branch=master)](https://travis-ci.org/paulbellamy/pipe) 2 | 3 | All the usual functional ways to work with sequences of data in Go. 4 | 5 | ## Usage 6 | 7 | ``` 8 | import "github.com/paulbellamy/pipe" 9 | ``` 10 | 11 | ## Why 12 | 13 | Because a bit of functional programming can make code a lot more readable and expressive. 14 | 15 | ## Should I Use This? 16 | 17 | It depends. It is pretty heavy on runtime-reflection. Are you ok with sacrificing some of Go's static-type-checking and compile-time safety for more expressiveness? 18 | 19 | ## Documentation 20 | 21 | * [API Docs](http://godoc.org/github.com/paulbellamy/pipe) 22 | 23 | ## Contributing 24 | 25 | Contributions are welcome via [pull requests](http://github.com/paulbellamy/pipe/issues). 26 | 27 | ## License 28 | 29 | Copyright © 2014 Paul Bellamy 30 | 31 | Released under the [MIT License](http://www.opensource.org/licenses/MIT). 32 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | * Finish ReverseSlice 4 | * Replace master branch 5 | * Try it in some small example programs. (find examples of functional usage) 6 | * Add others 7 | * Min? Max? (How to do comparators?) 8 | * MethodByName(name, ...args) helper 9 | * Test type coercion 10 | * Handle variadic functions 11 | * Check input types match expected (chan/slice/etc) 12 | * Drop Chan from the name of the chan methods. make them just the default? or auto-detect? 13 | * Add benchmarks to catch silly errors 14 | * Add Seq interface for custom classes to implement. 15 | -------------------------------------------------------------------------------- /drop_chan.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // DropChan is of type: func(num int, input chan T) chan T. 9 | // Drop a given number of items from the input chan. After that number has been 10 | // dropped, the rest are passed straight through. 11 | func DropChan(num int, input interface{}) interface{} { 12 | inputValue := reflect.ValueOf(input) 13 | 14 | if inputValue.Kind() != reflect.Chan { 15 | panic(fmt.Sprintf("DropChan called on invalid type: %s", inputValue.Type())) 16 | } 17 | 18 | output := reflect.MakeChan(inputValue.Type(), 0) 19 | var count int 20 | go func() { 21 | // drop num items 22 | for count = 0; count < num; count++ { 23 | _, ok := inputValue.Recv() 24 | if !ok { 25 | // channel closed early 26 | output.Close() 27 | return 28 | } 29 | } 30 | 31 | // Return the rest 32 | for { 33 | item, ok := inputValue.Recv() 34 | if !ok { 35 | break 36 | } 37 | 38 | output.Send(item) 39 | } 40 | output.Close() 41 | }() 42 | return output.Interface() 43 | } 44 | -------------------------------------------------------------------------------- /drop_chan_example_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func ExampleDropChan() { 8 | in := make(chan int, 10) 9 | out := DropChan(3, in).(chan int) 10 | 11 | for i := 0; i < 5; i++ { 12 | in <- i 13 | } 14 | 15 | fmt.Println(<-out) 16 | fmt.Println(<-out) 17 | 18 | // Output: 19 | // 3 20 | // 4 21 | } 22 | -------------------------------------------------------------------------------- /drop_chan_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestDropChan(t *testing.T) { 8 | in := make(chan int, 10) 9 | out := DropChan(3, in).(chan int) 10 | 11 | for i := 0; i < 5; i++ { 12 | in <- i 13 | } 14 | 15 | received := []int{} 16 | for len(received) < 2 { 17 | result, ok := <-out 18 | if !ok { 19 | break 20 | } 21 | received = append(received, result) 22 | } 23 | 24 | if len(received) != 2 || received[0] != 3 || received[1] != 4 { 25 | t.Fatal("DropChan(3) pipe received 1..4 but output ", received) 26 | } 27 | 28 | close(in) 29 | } 30 | -------------------------------------------------------------------------------- /drop_while.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | func checkDropWhileFuncType(fn, input interface{}) { 9 | fnType := reflect.TypeOf(fn) 10 | inputType := reflect.TypeOf(input) 11 | 12 | valid := fnType.NumOut() == 1 && 13 | fnType.NumIn() == 1 && 14 | inputType.Elem().ConvertibleTo(fnType.In(0)) && 15 | fnType.Out(0).ConvertibleTo(boolType) 16 | 17 | if !valid { 18 | panic(fmt.Sprintf("DropWhile fn must be of type func(%v) bool, but was %v", inputType.Elem(), fnType)) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /drop_while_chan.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // DropWhileChan is of type: func(fn func(T) bool, input chan T) chan T. 9 | // Drop the items from the input chan until the given function returns true. 10 | // After that, the rest are passed straight through. 11 | func DropWhileChan(fn, input interface{}) interface{} { 12 | checkDropWhileFuncType(fn, input) 13 | 14 | inputValue := reflect.ValueOf(input) 15 | fnValue := reflect.ValueOf(fn) 16 | 17 | if inputValue.Kind() != reflect.Chan { 18 | panic(fmt.Sprintf("DropWhileChan called on invalid type: %s", inputValue.Type())) 19 | } 20 | 21 | output := reflect.MakeChan(inputValue.Type(), 0) 22 | go func() { 23 | for { 24 | item, ok := inputValue.Recv() 25 | if !ok { 26 | // input closed, abort 27 | output.Close() 28 | return 29 | } 30 | 31 | // check if we should output this 32 | if !fnValue.Call([]reflect.Value{item})[0].Bool() { 33 | output.Send(item) 34 | break 35 | } 36 | } 37 | 38 | // send any messages after this 39 | for { 40 | item, ok := inputValue.Recv() 41 | if !ok { 42 | break 43 | } 44 | 45 | output.Send(item) 46 | } 47 | 48 | output.Close() 49 | 50 | }() 51 | return output.Interface() 52 | } 53 | -------------------------------------------------------------------------------- /drop_while_chan_example_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func ExampleDropWhileChan() { 8 | in := make(chan int, 5) 9 | 10 | lessThan := func(x int) func(int) bool { 11 | return func(item int) bool { 12 | return item < 3 13 | } 14 | } 15 | 16 | out := DropWhileChan(lessThan(3), in).(chan int) 17 | 18 | in <- 1 19 | in <- 2 20 | in <- 3 21 | in <- 2 22 | 23 | fmt.Println(<-out) 24 | fmt.Println(<-out) 25 | 26 | // Output: 27 | // 3 28 | // 2 29 | } 30 | -------------------------------------------------------------------------------- /drop_while_chan_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestDropWhileChan(t *testing.T) { 8 | in := make(chan int, 5) 9 | out := DropWhileChan(func(item int) bool { 10 | return item < 3 11 | }, in).(chan int) 12 | 13 | in <- 1 14 | in <- 2 15 | in <- 3 16 | in <- 2 17 | 18 | result := <-out 19 | if result != 3 { 20 | t.Fatal("DropWhileChan should have dropped all results until 3, but output", result) 21 | } 22 | 23 | result = <-out 24 | if result != 2 { 25 | t.Fatal("DropWhileChan should have kept all results after 3, but output", result) 26 | } 27 | 28 | close(in) 29 | } 30 | -------------------------------------------------------------------------------- /drop_while_slice.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // DropWhileSlice is of type: func(fn func(T) bool, input []T) []T. 9 | // Drop the items from the input slice until the given function returns true. 10 | // After that, the rest are passed straight through. 11 | func DropWhileSlice(fn, input interface{}) interface{} { 12 | checkFilterFuncType(fn, input) 13 | 14 | inputValue := reflect.ValueOf(input) 15 | fnValue := reflect.ValueOf(fn) 16 | 17 | if inputValue.Kind() != reflect.Slice && 18 | inputValue.Kind() != reflect.Array { 19 | panic(fmt.Sprintf("DropWhileChan called on invalid type: %s", inputValue.Type())) 20 | } 21 | 22 | i := 0 23 | for ; i < inputValue.Len(); i++ { 24 | if !fnValue.Call([]reflect.Value{inputValue.Index(i)})[0].Bool() { 25 | break 26 | } 27 | } 28 | 29 | return inputValue.Slice(i, inputValue.Len()).Interface() 30 | } 31 | -------------------------------------------------------------------------------- /drop_while_slice_example_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func ExampleDropWhileSlice() { 8 | in := []int{1, 2, 3, 2} 9 | 10 | lessThan := func(x int) func(int) bool { 11 | return func(item int) bool { 12 | return item < 3 13 | } 14 | } 15 | 16 | out := DropWhileSlice(lessThan(3), in).([]int) 17 | 18 | fmt.Println(out) 19 | 20 | // Output: 21 | // [3 2] 22 | } 23 | -------------------------------------------------------------------------------- /drop_while_slice_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestDropWhileSlice(t *testing.T) { 8 | in := []int{1, 2, 3, 2} 9 | 10 | lessThan := func(x int) func(int) bool { 11 | return func(item int) bool { 12 | return item < x 13 | } 14 | } 15 | 16 | result := DropWhileSlice(lessThan(3), in).([]int) 17 | 18 | if len(result) != 2 { 19 | t.Fatal("DropWhileSlice should have dropped all results until 3, but output", result) 20 | } 21 | 22 | if result[0] != 3 { 23 | t.Fatal("DropWhile should have dropped all results until 3, but output", result) 24 | } 25 | 26 | if result[1] != 2 { 27 | t.Fatal("DropWhile should have dropped all results until 3, but output", result) 28 | } 29 | } 30 | 31 | func TestDropWhileSliceNeverPassing(t *testing.T) { 32 | in := []int{1, 2, 3, 2} 33 | 34 | lessThan := func(x int) func(int) bool { 35 | return func(item int) bool { 36 | return item < x 37 | } 38 | } 39 | 40 | result := DropWhileSlice(lessThan(6), in).([]int) 41 | 42 | if len(result) != 0 { 43 | t.Fatal("DropWhileSlice should have dropped all results, but output", result) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /field_by_name.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | // FieldByName is a helper for fetching a named field from a struct. It is 8 | // useful in conjunction with Map, to retrieve many values at once. 9 | func FieldByName(name string) func(interface{}) interface{} { 10 | return func(record interface{}) interface{} { 11 | return reflect.ValueOf(record).FieldByName(name).Interface() 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /field_by_name_example_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func ExampleFieldByName() { 8 | type customer struct { 9 | Name string 10 | Age int 11 | } 12 | 13 | customers := []customer{{"Alice", 26}, {"Bob", 46}, {"Yousef", 37}} 14 | 15 | names := MapSlice(FieldByName("Name"), customers) 16 | ages := MapSlice(FieldByName("Age"), customers) 17 | 18 | fmt.Println(names) 19 | fmt.Println(ages) 20 | 21 | // Output: 22 | // [Alice Bob Yousef] 23 | // [26 46 37] 24 | } 25 | -------------------------------------------------------------------------------- /field_by_name_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | type customer struct { 8 | Name string 9 | } 10 | 11 | func TestFieldByName(t *testing.T) { 12 | alice := customer{"Alice"} 13 | 14 | getter := FieldByName("Name") 15 | result := getter(alice).(string) 16 | if result != "Alice" { 17 | t.Fatal("Expected getter to return 'Alice', but returned,", result) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /filter.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Paul Bellamy. All rights reserved. 2 | // Use of this source code is governed by an MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package pipe 6 | 7 | import ( 8 | "fmt" 9 | "reflect" 10 | ) 11 | 12 | var boolType = reflect.TypeOf(true) 13 | 14 | func checkFilterFuncType(fn, input interface{}) { 15 | fnType := reflect.TypeOf(fn) 16 | inputType := reflect.TypeOf(input) 17 | 18 | valid := fnType.NumOut() == 1 && 19 | fnType.NumIn() == 1 && 20 | inputType.Elem().ConvertibleTo(fnType.In(0)) && 21 | fnType.Out(0).ConvertibleTo(boolType) 22 | 23 | if !valid { 24 | panic(fmt.Sprintf("Filter fn must be of type func(%v) bool, but was %v", inputType.Elem(), fnType)) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /filter_chan.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // FilterChan is of type: func(fn func(T) bool, input chan T) chan T. 9 | // Apply a filtering function to a chan, which will only pass through 10 | // items when the filter func returns true. 11 | func FilterChan(fn, input interface{}) interface{} { 12 | checkFilterFuncType(fn, input) 13 | 14 | inputValue := reflect.ValueOf(input) 15 | fnValue := reflect.ValueOf(fn) 16 | 17 | if inputValue.Kind() != reflect.Chan { 18 | panic(fmt.Sprintf("FilterChan called on invalid type: %s", inputValue.Type())) 19 | } 20 | 21 | output := reflect.MakeChan(inputValue.Type(), 0) 22 | go func() { 23 | for { 24 | item, ok := inputValue.Recv() 25 | if !ok { 26 | break 27 | } 28 | 29 | if fnValue.Call([]reflect.Value{item})[0].Bool() { 30 | output.Send(item) 31 | } 32 | } 33 | output.Close() 34 | }() 35 | 36 | return output.Interface() 37 | } 38 | -------------------------------------------------------------------------------- /filter_chan_example_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func ExampleFilterChan() { 8 | even := func(item int) bool { 9 | return (item % 2) == 0 10 | } 11 | 12 | numbers := make(chan int) 13 | out := FilterChan(even, numbers).(chan int) 14 | 15 | go func() { 16 | numbers <- 1 17 | numbers <- 2 18 | numbers <- 3 19 | numbers <- 4 20 | numbers <- 5 21 | numbers <- 6 22 | numbers <- 7 23 | numbers <- 8 24 | numbers <- 9 25 | numbers <- 10 26 | close(numbers) 27 | }() 28 | 29 | // Print each output 30 | for result := range out { 31 | fmt.Println(result) 32 | } 33 | 34 | // Output: 35 | // 2 36 | // 4 37 | // 6 38 | // 8 39 | // 10 40 | } 41 | -------------------------------------------------------------------------------- /filter_chan_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestFilterChan(t *testing.T) { 9 | even := func(item int) bool { 10 | return (item % 2) == 0 11 | } 12 | 13 | in := make(chan int) 14 | out := FilterChan(even, in).(chan int) 15 | 16 | go func() { 17 | in <- 7 18 | in <- 4 19 | in <- 5 20 | in <- 2 21 | close(in) 22 | }() 23 | 24 | if result := <-out; result != 4 { 25 | t.Fatal("FilterChan(even, in) received 7,4,5,2, but output ", result) 26 | } 27 | 28 | if result := <-out; result != 2 { 29 | t.Fatal("FilterChan(even, in) received 7,4,5,2, but output ", result) 30 | } 31 | 32 | if _, ok := <-out; ok { 33 | t.Fatal("FilterChan(even, in) wasn't closed after in was closed") 34 | } 35 | } 36 | 37 | func TestFilterChanTypeCoercion(t *testing.T) { 38 | long_enough := func(item fmt.Stringer) bool { 39 | return len(item.String()) > 1 40 | } 41 | 42 | in := make(chan testStringer) 43 | out := FilterChan(long_enough, in).(chan testStringer) 44 | 45 | go func() { 46 | in <- 7 47 | in <- 42 48 | }() 49 | 50 | if result := <-out; result != 42 { 51 | t.Fatal("FilterChan(long_enough, in) received 7 and 42 but output ", out) 52 | } 53 | 54 | close(in) 55 | } 56 | -------------------------------------------------------------------------------- /filter_slice.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // FilterSlice is of type: func(fn func(T) bool, input []T) []T. 9 | // Apply a filtering function to a slice, which will only pass through items 10 | // when the filter func returns true. 11 | func FilterSlice(fn, input interface{}) interface{} { 12 | checkFilterFuncType(fn, input) 13 | 14 | inputValue := reflect.ValueOf(input) 15 | fnValue := reflect.ValueOf(fn) 16 | 17 | if inputValue.Kind() != reflect.Slice && 18 | inputValue.Kind() != reflect.Array { 19 | panic(fmt.Sprintf("FilterSlice called on invalid type: %s", inputValue.Type())) 20 | } 21 | 22 | outputType := reflect.SliceOf(inputValue.Type().Elem()) 23 | output := reflect.MakeSlice(outputType, 0, inputValue.Len()) 24 | 25 | for i := 0; i < inputValue.Len(); i++ { 26 | if fnValue.Call([]reflect.Value{inputValue.Index(i)})[0].Bool() { 27 | output = reflect.Append(output, inputValue.Index(i)) 28 | } 29 | } 30 | 31 | return output.Interface() 32 | } 33 | -------------------------------------------------------------------------------- /filter_slice_example_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func ExampleFilterSlice() { 8 | even := func(item int) bool { 9 | return (item % 2) == 0 10 | } 11 | 12 | numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} 13 | out := FilterSlice(even, numbers).([]int) 14 | 15 | fmt.Println(out) 16 | 17 | // Output: 18 | // [2 4 6 8 10] 19 | } 20 | -------------------------------------------------------------------------------- /filter_slice_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestFilterSlice(t *testing.T) { 9 | even := func(item int) bool { 10 | return (item % 2) == 0 11 | } 12 | 13 | in := []int{7, 4} 14 | out := FilterSlice(even, in).([]int) 15 | 16 | if len(out) != 1 || out[0] != 4 { 17 | t.Fatal("FilterSlice(even, in) received 7 and 4 but output ", out) 18 | } 19 | } 20 | 21 | func TestFilterSliceTypeCoercion(t *testing.T) { 22 | long_enough := func(item fmt.Stringer) bool { 23 | return len(item.String()) > 1 24 | } 25 | 26 | in := []testStringer{7, 42} 27 | out := FilterSlice(long_enough, in).([]testStringer) 28 | 29 | if len(out) != 1 || out[0] != 42 { 30 | t.Fatal("FilterSlice(long_enough, in) received 7 and 42 but output ", out) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /flatten_chan.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // FlattenChan is of type: func(input chan []T) chan T. 9 | // Takes a chan of arrays, and concatenates them together, putting each element 10 | // onto the output chan. After input is closed, output is also closed. If input 11 | // is chan T instead of type chan []T, then this is a no-op. 12 | func FlattenChan(input interface{}) interface{} { 13 | inputValue := reflect.ValueOf(input) 14 | 15 | if inputValue.Kind() != reflect.Chan { 16 | panic(fmt.Sprintf("FlattenChan called on invalid type: %s", inputValue.Type())) 17 | } 18 | 19 | elemType := inputValue.Type().Elem() 20 | if elemType.Kind() != reflect.Array && 21 | elemType.Kind() != reflect.Slice { 22 | return input 23 | } 24 | 25 | outputType := reflect.ChanOf(reflect.BothDir, elemType.Elem()) 26 | output := reflect.MakeChan(outputType, 0) 27 | go func() { 28 | for { 29 | value, ok := inputValue.Recv() 30 | if !ok { 31 | break 32 | } 33 | 34 | for i := 0; i < value.Len(); i++ { 35 | output.Send(value.Index(i)) 36 | } 37 | } 38 | output.Close() 39 | }() 40 | return output.Interface() 41 | } 42 | -------------------------------------------------------------------------------- /flatten_chan_example_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func ExampleFlattenChan() { 8 | in := make(chan []int, 5) 9 | out := FlattenChan(in).(chan int) 10 | 11 | in <- []int{1, 2} 12 | in <- []int{3, 4} 13 | in <- []int{5, 6} 14 | close(in) 15 | 16 | for result := range out { 17 | fmt.Println(result) 18 | } 19 | fmt.Println("Closed!") 20 | 21 | // Output: 22 | // 1 23 | // 2 24 | // 3 25 | // 4 26 | // 5 27 | // 6 28 | // Closed! 29 | } 30 | -------------------------------------------------------------------------------- /flatten_chan_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestFlattenChan(t *testing.T) { 8 | in := make(chan []int, 5) 9 | out := FlattenChan(in).(chan int) 10 | 11 | in <- []int{1, 2} 12 | in <- []int{3, 4} 13 | in <- []int{5, 6} 14 | close(in) 15 | 16 | count := 0 17 | for result := range out { 18 | count++ 19 | 20 | if result != count { 21 | t.Fatal("expected channel output to match", count, "but got", result) 22 | } 23 | } 24 | 25 | if count != 6 { 26 | t.Fatal("expected output to have 6 elements, but there were", count) 27 | } 28 | } 29 | 30 | func TestFlattenChanWhenAlreadyFlat(t *testing.T) { 31 | in := make(chan int, 5) 32 | out := FlattenChan(in).(chan int) 33 | 34 | in <- 1 35 | in <- 2 36 | in <- 3 37 | close(in) 38 | 39 | count := 0 40 | for result := range out { 41 | count++ 42 | 43 | if result != count { 44 | t.Fatal("expected channel output to match", count, "but got", result) 45 | } 46 | } 47 | 48 | if count != 3 { 49 | t.Fatal("expected output to have 3 elements, but there were", count) 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /flatten_slice.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // FlattenSlice is of type: func(input [][]T) []T. 9 | // Takes a chan of arrays, and concatenates them together, putting each element 10 | // onto the output chan. After input is closed, output is also closed. If input 11 | // is []T instead of type [][]T, then this is a no-op. 12 | func FlattenSlice(input interface{}) interface{} { 13 | inputValue := reflect.ValueOf(input) 14 | 15 | if inputValue.Kind() != reflect.Slice && 16 | inputValue.Kind() != reflect.Array { 17 | panic(fmt.Sprintf("FlattenSlice called on invalid type: %s", inputValue.Type())) 18 | } 19 | 20 | elemType := inputValue.Type().Elem() 21 | if elemType.Kind() != reflect.Array && 22 | elemType.Kind() != reflect.Slice { 23 | return input 24 | } 25 | 26 | outputType := reflect.SliceOf(elemType.Elem()) 27 | output := reflect.MakeSlice(outputType, 0, 1) 28 | 29 | for i := 0; i < inputValue.Len(); i++ { 30 | items := inputValue.Index(i) 31 | for j := 0; j < items.Len(); j++ { 32 | output = reflect.Append(output, items.Index(j)) 33 | } 34 | } 35 | return output.Interface() 36 | } 37 | -------------------------------------------------------------------------------- /flatten_slice_example_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func ExampleFlattenSlice() { 8 | in := [][]int{{1, 2}, {3, 4}, {5}} 9 | out := FlattenSlice(in).([]int) 10 | 11 | for _, result := range out { 12 | fmt.Println(result) 13 | } 14 | 15 | // Output: 16 | // 1 17 | // 2 18 | // 3 19 | // 4 20 | // 5 21 | } 22 | -------------------------------------------------------------------------------- /flatten_slice_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestFlattenSlice(t *testing.T) { 8 | in := [][]int{{1, 2}, {3, 4}, {5, 6}} 9 | result := FlattenSlice(in).([]int) 10 | 11 | expected := []int{1, 2, 3, 4, 5, 6} 12 | 13 | if len(expected) != len(result) { 14 | t.Fatal("expected output to have 6 elements, but there were", len(result)) 15 | } 16 | 17 | for i := 0; i < len(expected); i++ { 18 | expect(t, result[i], expected[i]) 19 | } 20 | } 21 | 22 | func TestFlattenSliceWhenAlreadyFlat(t *testing.T) { 23 | in := []int{1, 2, 3, 4, 5, 6} 24 | result := FlattenSlice(in).([]int) 25 | 26 | expected := []int{1, 2, 3, 4, 5, 6} 27 | 28 | if len(expected) != len(result) { 29 | t.Fatal("expected output to have 6 elements, but there were", len(result)) 30 | } 31 | 32 | for i := 0; i < len(expected); i++ { 33 | expect(t, result[i], expected[i]) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /for_each.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Paul Bellamy. All rights reserved. 2 | // Use of this source code is governed by an MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package pipe 6 | 7 | import ( 8 | "fmt" 9 | "reflect" 10 | ) 11 | 12 | func checkForEachFuncType(fn, input interface{}) { 13 | fnType := reflect.TypeOf(fn) 14 | inputType := reflect.TypeOf(input) 15 | 16 | valid := fnType.NumIn() == 1 17 | if fnType.IsVariadic() { 18 | valid = valid && inputType.Elem().ConvertibleTo(fnType.In(0).Elem()) 19 | } else { 20 | valid = valid && inputType.Elem().ConvertibleTo(fnType.In(0)) 21 | } 22 | 23 | if !valid { 24 | panic(fmt.Sprintf("ForEach fn must be of type func(%v), but was %v", inputType.Elem(), fnType)) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /for_each_chan.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | // ForEachChan is of type: func(fn func(T), input chan T) 8 | // Execute a function for each item. Useful 9 | // for monitoring, logging, or causing some side-effect. Returns nothing 10 | func ForEachChan(fn, input interface{}) { 11 | checkForEachFuncType(fn, input) 12 | 13 | fnValue := reflect.ValueOf(fn) 14 | inputValue := reflect.ValueOf(input) 15 | 16 | for { 17 | item, ok := inputValue.Recv() 18 | if !ok { 19 | break 20 | } 21 | 22 | fnValue.Call([]reflect.Value{item}) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /for_each_chan_example_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func ExampleForEachChan() { 8 | // Declare a chan of some things 9 | places := make(chan string, 5) 10 | 11 | places <- "Grantchester" 12 | places <- "Cambridge" 13 | places <- "Prague" 14 | close(places) 15 | 16 | ForEachChan(fmt.Println, places) 17 | 18 | // Output: 19 | // Grantchester 20 | // Cambridge 21 | // Prague 22 | } 23 | -------------------------------------------------------------------------------- /for_each_chan_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestForEachChan(t *testing.T) { 8 | count := 0 9 | 10 | in := make(chan int, 5) 11 | counter := func(item int) { 12 | count++ 13 | } 14 | 15 | in <- 5 16 | in <- 6 17 | in <- 7 18 | close(in) 19 | 20 | ForEachChan(counter, in) 21 | 22 | if count != 3 { 23 | t.Fatal("counting ForEachChan received 3 items but counted ", count, "/ 3") 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /for_each_slice.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | // ForEachSlice is of type: func(fn func(T), input []T). 8 | // Execute a function for each item. Returns nothing. 9 | func ForEachSlice(fn, input interface{}) { 10 | checkForEachFuncType(fn, input) 11 | 12 | fnValue := reflect.ValueOf(fn) 13 | inputValue := reflect.ValueOf(input) 14 | 15 | for i := 0; i < inputValue.Len(); i++ { 16 | fnValue.Call([]reflect.Value{inputValue.Index(i)}) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /for_each_slice_example_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func ExampleForEachSlice() { 8 | // Declare a slice of some things 9 | places := []string{"Grantchester", "Cambridge", "Prague"} 10 | 11 | ForEachSlice(fmt.Println, places) 12 | 13 | // Output: 14 | // Grantchester 15 | // Cambridge 16 | // Prague 17 | } 18 | -------------------------------------------------------------------------------- /for_each_slice_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestForEachSlice(t *testing.T) { 8 | count := 0 9 | 10 | in := []int{5, 6, 7} 11 | counter := func(item int) { 12 | count++ 13 | } 14 | ForEachSlice(counter, in) 15 | 16 | if count != 3 { 17 | t.Fatal("counting ForEachSlice received 3 items but counted ", count, "/ 3") 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /iterate.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | func checkIterateFuncType(fn reflect.Value, initialValues []reflect.Value) { 9 | fnType := fn.Type() 10 | 11 | argsCount := len(initialValues) 12 | valid := argsCount > 0 && 13 | fnType.NumOut() == argsCount && 14 | fnType.NumIn() == argsCount 15 | 16 | for i := 0; i < argsCount; i++ { 17 | if !initialValues[i].Type().ConvertibleTo(fnType.In(i)) || 18 | !fnType.Out(i).ConvertibleTo(initialValues[i].Type()) { 19 | fmt.Println(initialValues[i].Type(), "=>", fnType.In(i), ":", initialValues[i].Type().ConvertibleTo(fnType.In(i))) 20 | fmt.Println(fnType.Out(i), "=>", initialValues[i].Type(), ":", fnType.Out(i).ConvertibleTo(initialValues[i].Type())) 21 | valid = false 22 | break 23 | } 24 | } 25 | 26 | if !valid { 27 | panic(fmt.Sprintf("Iterate fn must be of type func(T) T, func(T, U) (T, U), etc., but was %v", fnType)) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /iterate_chan.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | // IterateChan is of type: func(fn func(T) T, initialArgs ...T) chan T. 8 | // Returns a channel with the values of x, fn(x), fn(fn(x)), etc... fn can take 9 | // multiple arguments, and return multiple values, but the types and counts of 10 | // arguments and return values must match. Only the first return value of each 11 | // call to fn will be printed. 12 | func IterateChan(fn interface{}, initialArgs ...interface{}) interface{} { 13 | fnValue := reflect.ValueOf(fn) 14 | initialValues := MapSlice(reflect.ValueOf, initialArgs).([]reflect.Value) 15 | 16 | checkIterateFuncType(fnValue, initialValues) 17 | 18 | outputType := reflect.ChanOf(reflect.BothDir, fnValue.Type().Out(0)) 19 | output := reflect.MakeChan(outputType, 0) 20 | 21 | go func() { 22 | args := initialValues 23 | for { 24 | output.Send(args[0]) 25 | args = fnValue.Call(args) 26 | } 27 | }() 28 | return output.Interface() 29 | } 30 | -------------------------------------------------------------------------------- /iterate_chan_example_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func ExampleIterateChan() { 8 | fib := func(f, s int) (int, int) { 9 | return s, f + s 10 | } 11 | 12 | out := IterateChan(fib, 0, 1).(chan int) 13 | 14 | for i := 0; i < 10; i++ { 15 | fmt.Println(<-out) 16 | } 17 | 18 | // Output: 19 | // 0 20 | // 1 21 | // 1 22 | // 2 23 | // 3 24 | // 5 25 | // 8 26 | // 13 27 | // 21 28 | // 34 29 | } 30 | -------------------------------------------------------------------------------- /iterate_chan_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestIterateChan(t *testing.T) { 9 | fib := func(f, s int) (int, int) { 10 | return s, f + s 11 | } 12 | 13 | out := IterateChan(fib, 0, 1).(chan int) 14 | 15 | expect(t, <-out, 0) 16 | expect(t, <-out, 1) 17 | expect(t, <-out, 1) 18 | expect(t, <-out, 2) 19 | expect(t, <-out, 3) 20 | expect(t, <-out, 5) 21 | expect(t, <-out, 8) 22 | expect(t, <-out, 13) 23 | } 24 | 25 | func TestIterateChanTypeCoercion(t *testing.T) { 26 | counter := func(output fmt.Stringer, state int) (testStringer, int) { 27 | return testStringer(state), state + 1 28 | } 29 | out := IterateChan(counter, testStringer(0), 1).(chan testStringer) 30 | 31 | for i := 0; i <= 3; i++ { 32 | if result := <-out; result.String() != fmt.Sprint(i) { 33 | t.Fatal("IterateChan output ", result, "but was expected to output", i) 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /map.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Paul Bellamy. All rights reserved. 2 | // Use of this source code is governed by an MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package pipe 6 | 7 | import ( 8 | "fmt" 9 | "reflect" 10 | ) 11 | 12 | func checkMapFuncType(fn, input interface{}) { 13 | inputType := reflect.TypeOf(input) 14 | fnType := reflect.TypeOf(fn) 15 | 16 | valid := fnType.NumOut() == 1 && 17 | fnType.NumIn() == 1 && 18 | inputType.Elem().ConvertibleTo(fnType.In(0)) 19 | 20 | if !valid { 21 | panic(fmt.Sprintf("Map fn must be of type func(%v) T, but was %v", inputType.Elem(), fnType)) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /map_cat_chan.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | // MapCatChan is of type: func(fn func(T) []U, input chan T) chan U. 4 | // It returns a chan which receives fn(item) for each item in input. 5 | func MapCatChan(fn, input interface{}) interface{} { 6 | return FlattenChan(MapChan(fn, input)) 7 | } 8 | -------------------------------------------------------------------------------- /map_cat_chan_example_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func ExampleMapCatChan() { 8 | printer := func(item int) []string { 9 | return []string{fmt.Sprintf("%d", item), fmt.Sprintf("%.3d", item)} 10 | } 11 | in := make(chan int, 5) 12 | out := MapCatChan(printer, in).(chan string) 13 | 14 | go func() { 15 | in <- 1 16 | in <- 2 17 | in <- 3 18 | close(in) 19 | }() 20 | 21 | for result := range out { 22 | fmt.Println(result) 23 | } 24 | fmt.Println("Closed!") 25 | 26 | // Output 27 | // 1 28 | // 001 29 | // 2 30 | // 002 31 | // 3 32 | // 003 33 | // Closed! 34 | } 35 | -------------------------------------------------------------------------------- /map_cat_chan_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestMapCatChan(t *testing.T) { 9 | printer := func(item int) []string { 10 | return []string{fmt.Sprintf("%d", item), fmt.Sprintf("%.3d", item)} 11 | } 12 | in := make(chan int, 5) 13 | out := MapCatChan(printer, in).(chan string) 14 | 15 | go func() { 16 | in <- 1 17 | in <- 2 18 | in <- 3 19 | close(in) 20 | }() 21 | 22 | count := 0 23 | for i := 1; i <= 3; i++ { 24 | count++ 25 | if result := <-out; result != fmt.Sprintf("%d", i) { 26 | t.Fatal("MapCatChan received ", i, " items but output ", result) 27 | } 28 | if result := <-out; result != fmt.Sprintf("%.3d", i) { 29 | t.Fatal("MapCatChan received ", i, " items but output ", result) 30 | } 31 | } 32 | 33 | if _, ok := <-out; ok { 34 | t.Fatal("Expected MapCatChan to be closed") 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /map_cat_slice.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | // MapCatSlice is of type: func(fn func(T) []U, input []T) []U. 4 | // It returns a slice with fn(item) for each item in input. 5 | func MapCatSlice(fn, input interface{}) interface{} { 6 | return FlattenSlice(MapSlice(fn, input)) 7 | } 8 | -------------------------------------------------------------------------------- /map_cat_slice_example_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func ExampleMapCatSlice() { 8 | printer := func(item int) []string { 9 | return []string{fmt.Sprintf("%d", item), fmt.Sprintf("%.3d", item)} 10 | } 11 | in := []int{7, 4, 5} 12 | result := MapCatSlice(printer, in).([]string) 13 | 14 | for _, item := range result { 15 | fmt.Println(item) 16 | } 17 | 18 | // Output: 19 | // 7 20 | // 007 21 | // 4 22 | // 004 23 | // 5 24 | // 005 25 | } 26 | -------------------------------------------------------------------------------- /map_cat_slice_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestMapCatSlice(t *testing.T) { 9 | printer := func(item int) []string { 10 | return []string{fmt.Sprintf("%d", item), fmt.Sprintf("%.3d", item)} 11 | } 12 | in := []int{7, 4, 5} 13 | result := MapCatSlice(printer, in).([]string) 14 | 15 | expected := []string{"7", "007", "4", "004", "5", "005"} 16 | expect(t, len(result), len(expected)) 17 | for i := 0; i < len(result); i++ { 18 | expect(t, result[i], expected[i]) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /map_chan.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | // MapChan is of type: func(fn func(T) U, input chan T) chan U. 8 | // It returns a chan which receives fn(item) for each item in input. 9 | func MapChan(fn, input interface{}) interface{} { 10 | checkMapFuncType(fn, input) 11 | 12 | inputValue := reflect.ValueOf(input) 13 | fnValue := reflect.ValueOf(fn) 14 | 15 | outputType := reflect.ChanOf(reflect.BothDir, fnValue.Type().Out(0)) 16 | output := reflect.MakeChan(outputType, 0) 17 | go func() { 18 | for { 19 | value, ok := inputValue.Recv() 20 | if !ok { 21 | break 22 | } 23 | 24 | output.Send(fnValue.Call([]reflect.Value{value})[0]) 25 | } 26 | output.Close() 27 | }() 28 | return output.Interface() 29 | } 30 | -------------------------------------------------------------------------------- /map_chan_example_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func ExampleMapChan() { 8 | // Declare a chan of some things 9 | numbers := make(chan int) 10 | 11 | // function to apply 12 | square := func(x int) int { 13 | return x * x 14 | } 15 | 16 | // Create a new chan which applies a function to the original 17 | squares := MapChan(square, numbers).(chan int) 18 | 19 | // Put some numbers in the original chan 20 | go func() { 21 | numbers <- 1 22 | numbers <- 2 23 | numbers <- 3 24 | }() 25 | 26 | fmt.Println(<-squares) 27 | fmt.Println(<-squares) 28 | fmt.Println(<-squares) 29 | 30 | // Output: 31 | // 1 32 | // 4 33 | // 9 34 | } 35 | -------------------------------------------------------------------------------- /map_chan_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestMapChan(t *testing.T) { 9 | count := 0 10 | counter := func(item int) string { 11 | count++ 12 | return fmt.Sprint(count) 13 | } 14 | in := make(chan int, 5) 15 | out := MapChan(counter, in).(chan string) 16 | 17 | go func() { 18 | in <- 7 19 | in <- 4 20 | in <- 5 21 | }() 22 | for i := 1; i <= 3; i++ { 23 | if result := <-out; result != fmt.Sprint(i) { 24 | t.Fatal("MapChan received ", i, " items but output ", result) 25 | } 26 | } 27 | 28 | close(in) 29 | } 30 | 31 | func TestMapChanTypeCoercion(t *testing.T) { 32 | count := 0 33 | counter := func(item fmt.Stringer) string { 34 | count++ 35 | return fmt.Sprint(count) 36 | } 37 | in := make(chan testStringer, 5) 38 | out := MapChan(counter, in).(chan string) 39 | 40 | go func() { 41 | in <- 7 42 | in <- 4 43 | in <- 5 44 | }() 45 | 46 | for i := 1; i <= 3; i++ { 47 | if result := <-out; result != fmt.Sprint(i) { 48 | t.Fatal("MapChan received ", i, " items but output ", result) 49 | } 50 | } 51 | 52 | close(in) 53 | } 54 | -------------------------------------------------------------------------------- /map_slice.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // MapSlice is of type: func(fn func(T) U, input []T) []U. 9 | // It returns a slice of fn(item) for each item in input. 10 | func MapSlice(fn, input interface{}) interface{} { 11 | checkMapFuncType(fn, input) 12 | 13 | inputValue := reflect.ValueOf(input) 14 | fnValue := reflect.ValueOf(fn) 15 | 16 | if inputValue.Kind() != reflect.Slice && 17 | inputValue.Kind() != reflect.Array { 18 | panic(fmt.Sprintf("MapSlice called on invalid type: %s", inputValue.Type())) 19 | } 20 | 21 | outputType := reflect.SliceOf(fnValue.Type().Out(0)) 22 | output := reflect.MakeSlice(outputType, 0, inputValue.Len()) 23 | 24 | for i := 0; i < inputValue.Len(); i++ { 25 | output = reflect.Append( 26 | output, 27 | fnValue.Call([]reflect.Value{inputValue.Index(i)})[0], 28 | ) 29 | } 30 | 31 | return output.Interface() 32 | } 33 | -------------------------------------------------------------------------------- /map_slice_example_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type place struct { 8 | name string 9 | population int 10 | } 11 | 12 | func statusByPopulation(p place) string { 13 | switch { 14 | case p.population > 1000000: 15 | return "City" 16 | case p.population > 5000: 17 | return "Town" 18 | default: 19 | return "Village" 20 | } 21 | } 22 | 23 | func ExampleMapSlice() { 24 | // Declare a slice of some things 25 | places := []place{ 26 | {"Grantchester", 552}, 27 | {"Cambridge", 117900}, 28 | {"Prague", 1188126}, 29 | } 30 | 31 | sizes := MapSlice(statusByPopulation, places).([]string) 32 | 33 | fmt.Println(sizes) 34 | 35 | // Output: 36 | // [Village Town City] 37 | } 38 | -------------------------------------------------------------------------------- /map_slice_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestMapSlice(t *testing.T) { 9 | count := 0 10 | counter := func(item int) string { 11 | count++ 12 | return fmt.Sprint(count) 13 | } 14 | in := []int{7, 4, 5} 15 | out := MapSlice(counter, in).([]string) 16 | 17 | for i := 1; i <= 3; i++ { 18 | if result := out[i-1]; result != fmt.Sprint(i) { 19 | t.Fatal("MapSlice received", in, "but output", out, "expected", []string{"1", "2", "3"}) 20 | } 21 | } 22 | } 23 | 24 | func TestMapSliceTypeCoercion(t *testing.T) { 25 | count := 0 26 | counter := func(item fmt.Stringer) string { 27 | count++ 28 | return fmt.Sprint(count) 29 | } 30 | in := []testStringer{7, 4, 5} 31 | out := MapSlice(counter, in).([]string) 32 | 33 | for i := 1; i <= 3; i++ { 34 | if result := out[i-1]; result != fmt.Sprint(i) { 35 | t.Fatal("MapSlice received", in, "but output", out, "expected", []string{"1", "2", "3"}) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /map_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Paul Bellamy. All rights reserved. 2 | // Use of this source code is governed by an MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package pipe 6 | 7 | import ( 8 | "fmt" 9 | ) 10 | 11 | type testStringer int 12 | 13 | func (t testStringer) String() string { 14 | return fmt.Sprintf("%d", t) 15 | } 16 | -------------------------------------------------------------------------------- /onto_chan.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // OntoChan is of type: func(input []T) chan T. 9 | // Puts the elements of the input collection onto a channel, which is then 10 | // closed. 11 | func OntoChan(input interface{}) interface{} { 12 | inputValue := reflect.ValueOf(input) 13 | 14 | if inputValue.Kind() != reflect.Slice && 15 | inputValue.Kind() != reflect.Array { 16 | panic(fmt.Sprintf("OntoChan called on invalid type: %s", inputValue.Type())) 17 | } 18 | 19 | outputType := reflect.ChanOf(reflect.BothDir, inputValue.Type().Elem()) 20 | output := reflect.MakeChan(outputType, 0) 21 | go func() { 22 | for i := 0; i < inputValue.Len(); i++ { 23 | output.Send(inputValue.Index(i)) 24 | } 25 | output.Close() 26 | }() 27 | return output.Interface() 28 | } 29 | -------------------------------------------------------------------------------- /onto_chan_example_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func ExampleOntoChan() { 8 | in := []int{0, 1, 2, 3, 4} 9 | out := OntoChan(in).(chan int) 10 | 11 | for x := range out { 12 | fmt.Println(x) 13 | } 14 | fmt.Println("Closed!") 15 | 16 | // Output: 17 | // 0 18 | // 1 19 | // 2 20 | // 3 21 | // 4 22 | // Closed! 23 | } 24 | -------------------------------------------------------------------------------- /onto_chan_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestOntoChan(t *testing.T) { 8 | in := []int{0, 1, 2, 3, 4} 9 | out := OntoChan(in).(chan int) 10 | 11 | result := []int{} 12 | for x := range out { 13 | result = append(result, x) 14 | } 15 | 16 | expect(t, len(result), len(in)) 17 | for i := 0; i < len(result); i++ { 18 | expect(t, result[i], in[i]) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /reduce.go: -------------------------------------------------------------------------------- 1 | // Copyright 2014 Paul Bellamy. All rights reserved. 2 | // Use of this source code is governed by an MIT-style 3 | // license that can be found in the LICENSE file. 4 | 5 | package pipe 6 | 7 | import ( 8 | "fmt" 9 | "reflect" 10 | ) 11 | 12 | func checkReduceFuncType(fn, initial, input interface{}) { 13 | fnType := reflect.TypeOf(fn) 14 | initialType := reflect.TypeOf(initial) 15 | inputType := reflect.TypeOf(input) 16 | 17 | valid := fnType.NumOut() == 1 && 18 | fnType.NumIn() == 2 && 19 | initialType.ConvertibleTo(fnType.In(0)) && 20 | inputType.Elem().ConvertibleTo(fnType.In(1)) && 21 | fnType.Out(0).ConvertibleTo(fnType.In(0)) 22 | 23 | if !valid { 24 | panic(fmt.Sprintf("Reduce fn must be of type func(%v, %v) T, but was %v", initialType, inputType.Elem(), fnType)) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /reduce_chan.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | // ReduceChan is of type: func(fn func(accumulator U, item T) U, initial U, input chan T) U. 8 | // It accumulates the result of the reduce function being called on each item, 9 | // then when the input channel is closed, return the result. 10 | func ReduceChan(fn, initial, input interface{}) interface{} { 11 | checkReduceFuncType(fn, initial, input) 12 | 13 | inputValue := reflect.ValueOf(input) 14 | fnValue := reflect.ValueOf(fn) 15 | 16 | result := reflect.ValueOf(initial) 17 | for { 18 | item, ok := inputValue.Recv() 19 | if !ok { 20 | break 21 | } 22 | 23 | result = fnValue.Call([]reflect.Value{result, item})[0] 24 | } 25 | return result.Interface() 26 | } 27 | -------------------------------------------------------------------------------- /reduce_chan_example_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func ExampleReduceChan() { 8 | // Declare a slice of some things 9 | chars := make(chan string) 10 | 11 | // Function to apply 12 | concat := func(a, b string) string { 13 | return fmt.Sprintf("%s%s", a, b) 14 | } 15 | 16 | // Push some values into the input 17 | go func() { 18 | chars <- "a" 19 | chars <- "b" 20 | chars <- "c" 21 | close(chars) 22 | }() 23 | 24 | sum := ReduceChan(concat, "", chars).(string) 25 | 26 | fmt.Println(sum) 27 | 28 | // Output: 29 | // abc 30 | } 31 | -------------------------------------------------------------------------------- /reduce_chan_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestReduceChan(t *testing.T) { 9 | in := make(chan int, 5) 10 | 11 | go func() { 12 | in <- 5 13 | in <- 10 14 | in <- 20 15 | close(in) 16 | }() 17 | 18 | out := ReduceChan(sum, 0, in).(int) 19 | 20 | if out != 35 { 21 | t.Fatal("ReduceChan(sum, 0, []int{5, 10, 20}) output ", out) 22 | } 23 | } 24 | 25 | func TestReduceChanTypeCoercion(t *testing.T) { 26 | appendToString := func(str string, item fmt.Stringer) string { 27 | return fmt.Sprintf("%s%s", str, item.String()) 28 | } 29 | 30 | in := make(chan testStringer, 5) 31 | 32 | go func() { 33 | in <- 1 34 | in <- 2 35 | in <- 3 36 | close(in) 37 | }() 38 | 39 | out := ReduceChan(appendToString, "a", in).(string) 40 | 41 | if out != "a123" { 42 | t.Fatal("ReduceChan(appendToString, \"a\", chan testStringer) output ", out) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /reduce_slice.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | // ReduceSlice is of type: func(fn func(accumulator U, item T) U, initial U, input []T) U. 8 | // It accumulates the result of the fn function being called on each item, with 9 | // the last value being returned. 10 | func ReduceSlice(fn, initial, input interface{}) interface{} { 11 | checkReduceFuncType(fn, initial, input) 12 | 13 | inputValue := reflect.ValueOf(input) 14 | fnValue := reflect.ValueOf(fn) 15 | 16 | result := reflect.ValueOf(initial) 17 | for i := 0; i < inputValue.Len(); i++ { 18 | result = fnValue.Call([]reflect.Value{result, inputValue.Index(i)})[0] 19 | } 20 | return result.Interface() 21 | } 22 | -------------------------------------------------------------------------------- /reduce_slice_example_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func ExampleReduceSlice() { 8 | // Declare a slice of some things 9 | chars := []string{"a", "b", "c"} 10 | 11 | // Function to apply 12 | concat := func(a, b string) string { 13 | return fmt.Sprintf("%s%s", a, b) 14 | } 15 | 16 | sum := ReduceSlice(concat, "", chars).(string) 17 | 18 | fmt.Println(sum) 19 | 20 | // Output: 21 | // abc 22 | } 23 | -------------------------------------------------------------------------------- /reduce_slice_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func sum(sum, item int) int { 9 | return sum + item 10 | } 11 | 12 | func TestReduceSlice(t *testing.T) { 13 | in := []int{5, 10, 20} 14 | out := ReduceSlice(sum, 0, in).(int) 15 | 16 | if out != 35 { 17 | t.Fatal("ReduceSlice(sum, 0, []int{5, 10, 20}) output ", out) 18 | } 19 | } 20 | 21 | func TestReduceSliceTypeCoercion(t *testing.T) { 22 | appendToString := func(str string, item fmt.Stringer) string { 23 | return fmt.Sprintf("%s%s", str, item.String()) 24 | } 25 | 26 | in := []testStringer{1, 2, 3} 27 | out := ReduceSlice(appendToString, "a", in).(string) 28 | 29 | if out != "a123" { 30 | t.Fatal("ReduceSlice(appendToString, \"a\", []int{1, 2, 3}) output ", out) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /repeatedly.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | func checkRepeatedlyFuncType(fn interface{}) { 9 | fnType := reflect.TypeOf(fn) 10 | 11 | valid := fnType.NumOut() == 1 && fnType.NumIn() == 0 12 | 13 | if !valid { 14 | panic(fmt.Sprintf("Repeatedly fn must be of type func() T, but was %v", fnType)) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /repeatedly_chan.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | // IterateChan is of type: func(fn func() T) chan T. 8 | // Take some function 'fn' (presumably with some side-effect). It is repeatedly 9 | // called, and the return values put onto the returned channel. 10 | func RepeatedlyChan(fn interface{}) interface{} { 11 | checkRepeatedlyFuncType(fn) 12 | 13 | fnValue := reflect.ValueOf(fn) 14 | 15 | outputType := reflect.ChanOf(reflect.BothDir, fnValue.Type().Out(0)) 16 | output := reflect.MakeChan(outputType, 0) 17 | 18 | go func() { 19 | args := []reflect.Value{} 20 | for { 21 | output.Send(fnValue.Call(args)[0]) 22 | } 23 | }() 24 | return output.Interface() 25 | } 26 | -------------------------------------------------------------------------------- /repeatedly_chan_example_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func ExampleRepeatedlyChan() { 8 | count := -1 9 | out := RepeatedlyChan(func() int { 10 | count++ 11 | return count 12 | }).(chan int) 13 | 14 | for i := 0; i < 3; i++ { 15 | fmt.Println(<-out) 16 | } 17 | 18 | // Output: 19 | // 0 20 | // 1 21 | // 2 22 | } 23 | -------------------------------------------------------------------------------- /repeatedly_chan_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestRepeatedlyChan(t *testing.T) { 8 | count := -1 9 | out := RepeatedlyChan(func() int { 10 | count++ 11 | return count 12 | }).(chan int) 13 | 14 | for i := 0; i < 5; i++ { 15 | result := <-out 16 | if result != i { 17 | t.Fatal("RepeatedlyChan was expected to output", i, "but output", result) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /take_chan.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // TakeChan is of type: func(num int, input chan T) chan T. 9 | // Accept only the given number of items from the input chan. After that number 10 | // has been received, all input messages will be ignored and the output channel 11 | // will be closed. 12 | func TakeChan(num int, input interface{}) interface{} { 13 | inputValue := reflect.ValueOf(input) 14 | 15 | if inputValue.Kind() != reflect.Chan { 16 | panic(fmt.Sprintf("DropChan called on invalid type: %s", inputValue.Type())) 17 | } 18 | 19 | output := reflect.MakeChan(inputValue.Type(), 0) 20 | var count int 21 | go func() { 22 | // only send num items 23 | for count = 0; count < num; count++ { 24 | item, ok := inputValue.Recv() 25 | if !ok { 26 | break 27 | } 28 | 29 | output.Send(item) 30 | } 31 | 32 | // sent our max, close the channel 33 | output.Close() 34 | }() 35 | return output.Interface() 36 | } 37 | -------------------------------------------------------------------------------- /take_chan_example_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func ExampleTakeChan() { 8 | in := make(chan int, 10) 9 | out := TakeChan(3, in).(chan int) 10 | 11 | for i := 0; i < 5; i++ { 12 | in <- i 13 | } 14 | 15 | for result := range out { 16 | fmt.Println(result) 17 | } 18 | fmt.Println("Closed!") 19 | 20 | // Output: 21 | // 0 22 | // 1 23 | // 2 24 | // Closed! 25 | } 26 | -------------------------------------------------------------------------------- /take_chan_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestTakeChan(t *testing.T) { 8 | in := make(chan int, 10) 9 | out := TakeChan(3, in).(chan int) 10 | 11 | for i := 0; i < 5; i++ { 12 | in <- i 13 | } 14 | 15 | count := 0 16 | for { 17 | _, ok := <-out 18 | if !ok { 19 | break 20 | } 21 | count++ 22 | } 23 | 24 | if count != 3 { 25 | t.Fatal("TakeChan(3) received 5 items but output ", count) 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /take_while.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | func checkTakeWhileFuncType(fn, input interface{}) { 9 | fnType := reflect.TypeOf(fn) 10 | inputType := reflect.TypeOf(input) 11 | 12 | valid := fnType.NumOut() == 1 && 13 | fnType.NumIn() == 1 && 14 | inputType.Elem().ConvertibleTo(fnType.In(0)) && 15 | fnType.Out(0).ConvertibleTo(boolType) 16 | 17 | if !valid { 18 | panic(fmt.Sprintf("TakeWhile fn must be of type func(%v) bool, but was %v", inputType.Elem(), fnType)) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /take_while_chan.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | // TakeWhileChan is of type: func(fn func(T) bool, input chan T) chan T. 8 | // Accept items from the input chan until the given function returns false. 9 | // After that, all input messages will be ignored and the output channel will 10 | // be closed. 11 | func TakeWhileChan(fn, input interface{}) interface{} { 12 | checkTakeWhileFuncType(fn, input) 13 | 14 | inputValue := reflect.ValueOf(input) 15 | fnValue := reflect.ValueOf(fn) 16 | 17 | output := reflect.MakeChan(inputValue.Type(), 0) 18 | go func() { 19 | for { 20 | item, ok := inputValue.Recv() 21 | if !ok { 22 | break 23 | } 24 | 25 | // check if we should continue 26 | if !fnValue.Call([]reflect.Value{item})[0].Bool() { 27 | break 28 | } 29 | 30 | output.Send(item) 31 | } 32 | 33 | // hit the toggle, close the channel 34 | output.Close() 35 | 36 | // drop any extra messages 37 | for { 38 | _, ok := inputValue.Recv() 39 | if !ok { 40 | break 41 | } 42 | } 43 | }() 44 | return output.Interface() 45 | } 46 | -------------------------------------------------------------------------------- /take_while_chan_example_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func ExampleTakeWhileChan() { 8 | in := make(chan int, 5) 9 | out := TakeWhileChan(func(item int) bool { 10 | return item != 5 11 | }, in).(chan int) 12 | 13 | in <- 7 14 | in <- 4 15 | in <- 5 16 | in <- 6 17 | 18 | for result := range out { 19 | fmt.Println(result) 20 | } 21 | 22 | fmt.Println("Closed!") 23 | 24 | // Output: 25 | // 7 26 | // 4 27 | // Closed! 28 | } 29 | -------------------------------------------------------------------------------- /take_while_chan_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestTakeWhileChan(t *testing.T) { 9 | take := true 10 | in := make(chan int, 5) 11 | out := TakeWhileChan(func(item int) bool { 12 | return take 13 | }, in).(chan int) 14 | 15 | in <- 7 16 | in <- 4 17 | take = false 18 | in <- 5 19 | in <- 6 20 | 21 | <-out 22 | <-out 23 | if _, ok := <-out; ok { 24 | t.Fatal("takewhile pipe should have closed the channel after turning it off") 25 | } 26 | 27 | close(in) 28 | } 29 | 30 | func TestTakeWhileChanTypeCoercion(t *testing.T) { 31 | strLenLessThan := func(length int) func(fmt.Stringer) bool { 32 | return func(x fmt.Stringer) bool { 33 | return len(x.String()) < length 34 | } 35 | } 36 | 37 | in := make(chan testStringer, 5) 38 | out := TakeWhileChan(strLenLessThan(2), in).(chan testStringer) 39 | 40 | in <- 8 41 | in <- 9 42 | in <- 10 43 | in <- 9 44 | 45 | if result := <-out; result != 8 { 46 | t.Fatal("Expected:", 8, "\nGot:", result) 47 | } 48 | 49 | if result := <-out; result != 9 { 50 | t.Fatal("Expected:", 9, "\nGot:", result) 51 | } 52 | 53 | if _, ok := <-out; ok { 54 | t.Fatal("takewhile pipe should have closed the channel after turning it off") 55 | } 56 | 57 | close(in) 58 | } 59 | -------------------------------------------------------------------------------- /take_while_slice.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // TakeWhileSlice is of type: func(fn func(T) bool, input []T) []T. 9 | // Accept items from the input slice until the given function returns false. 10 | // After that, the rest are passed straight through. 11 | func TakeWhileSlice(fn, input interface{}) interface{} { 12 | checkTakeWhileFuncType(fn, input) 13 | 14 | inputValue := reflect.ValueOf(input) 15 | fnValue := reflect.ValueOf(fn) 16 | 17 | if inputValue.Kind() != reflect.Slice && 18 | inputValue.Kind() != reflect.Array { 19 | panic(fmt.Sprintf("TakeWhileChan called on invalid type: %s", inputValue.Type())) 20 | } 21 | 22 | i := 0 23 | for ; i < inputValue.Len(); i++ { 24 | if !fnValue.Call([]reflect.Value{inputValue.Index(i)})[0].Bool() { 25 | break 26 | } 27 | } 28 | 29 | return inputValue.Slice(0, i).Interface() 30 | } 31 | -------------------------------------------------------------------------------- /take_while_slice_example_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func ExampleTakeWhileSlice() { 8 | in := []int{7, 4, 5, 6} 9 | out := TakeWhileSlice(func(item int) bool { 10 | return item != 5 11 | }, in).([]int) 12 | 13 | fmt.Println(out) 14 | 15 | // Output: 16 | // [7 4] 17 | } 18 | -------------------------------------------------------------------------------- /take_while_slice_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | "testing" 6 | ) 7 | 8 | func TestTakeWhileSlice(t *testing.T) { 9 | in := []int{7, 4, 5, 6} 10 | out := TakeWhileSlice(func(item int) bool { 11 | return item != 5 12 | }, in).([]int) 13 | 14 | if len(out) != 2 || out[0] != 7 || out[1] != 4 { 15 | t.Fatal("takewhile should have returned [7 4], but returned", out) 16 | } 17 | } 18 | 19 | func TestTakeWhileSliceTypeCoercion(t *testing.T) { 20 | strLenLessThan := func(length int) func(fmt.Stringer) bool { 21 | return func(x fmt.Stringer) bool { 22 | return len(x.String()) < length 23 | } 24 | } 25 | 26 | in := []testStringer{8, 9, 10, 9} 27 | out := TakeWhileSlice(strLenLessThan(2), in).([]testStringer) 28 | 29 | if len(out) != 2 || out[0] != 8 || out[1] != 9 { 30 | t.Fatal("Expected:", []testStringer{8, 9}, "\nGot:", out) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /util.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func expect(t *testing.T, result, expected interface{}) { 8 | if result != expected { 9 | t.Fatal("Expected:", expected, "\nGot:", result) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /zip_chan.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // ZipChan is of type: func(input chan T, others ...chan T) chan []T. 9 | // Group each message from the input channel with it's corresponding message(s) 10 | // from the other channel(s). This will block on the first channel until it 11 | // receives a message, then block on the second until it gets one from there. 12 | // At that point an array containing both will be sent to the output channel. 13 | func ZipChan(input interface{}, others ...interface{}) interface{} { 14 | inputType := reflect.TypeOf(input) 15 | 16 | inputs := append([]interface{}{input}, others...) 17 | inputValues := MapSlice(reflect.ValueOf, inputs).([]reflect.Value) 18 | 19 | for i := 0; i < len(inputValues); i++ { 20 | if inputValues[i].Kind() != reflect.Chan { 21 | panic(fmt.Sprintf("ZipChan called on invalid type: %s", inputValues[i].Type())) 22 | } 23 | 24 | if inputValues[i].Type() != inputType { 25 | panic(fmt.Sprintf("Zip input types must match, but they were %v and %v", inputType, inputValues[i].Type())) 26 | } 27 | } 28 | 29 | zippedType := reflect.SliceOf(inputType.Elem()) 30 | outputType := reflect.ChanOf(reflect.BothDir, zippedType) 31 | output := reflect.MakeChan(outputType, 0) 32 | go func() { 33 | i := 0 34 | for { 35 | zipped := reflect.MakeSlice(zippedType, 0, len(inputValues)) 36 | 37 | for i = 0; i < len(inputValues); i++ { 38 | item, ok := inputValues[i].Recv() 39 | if !ok { 40 | break 41 | } 42 | zipped = reflect.Append(zipped, item) 43 | } 44 | 45 | if i < len(inputValues) { 46 | break 47 | } 48 | 49 | output.Send(zipped) 50 | } 51 | 52 | output.Close() 53 | }() 54 | return output.Interface() 55 | } 56 | -------------------------------------------------------------------------------- /zip_chan_example_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func ExampleZipChan() { 8 | other := make(chan int, 5) 9 | in := make(chan int, 5) 10 | out := ZipChan(in, other).(chan []int) 11 | 12 | in <- 5 13 | in <- 10 14 | in <- 20 15 | other <- 6 16 | other <- 11 17 | close(other) 18 | 19 | for result := range out { 20 | fmt.Println(result) 21 | } 22 | fmt.Println("Closed!") 23 | 24 | // Output: 25 | // [5 6] 26 | // [10 11] 27 | // Closed! 28 | } 29 | -------------------------------------------------------------------------------- /zip_chan_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestZipChan(t *testing.T) { 8 | other := make(chan int, 5) 9 | in := make(chan int, 5) 10 | out := ZipChan(in, other).(chan []int) 11 | 12 | in <- 5 13 | in <- 10 14 | in <- 20 15 | other <- 6 16 | other <- 11 17 | close(other) 18 | 19 | count := 0 20 | for result := range out { 21 | count++ 22 | 23 | expected := []int{count * 5, (count * 5) + 1} 24 | if len(result) != len(expected) { 25 | t.Fatal("expected channel output to match", expected, "but got", result) 26 | } 27 | 28 | for j := 0; j < len(result); j++ { 29 | if result[j] != expected[j] { 30 | t.Fatal("expected channel output to match", expected, "but got", result) 31 | } 32 | } 33 | } 34 | 35 | if count != 2 { 36 | t.Fatal("expected output to have 2 elements, but there were", count) 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /zip_slice.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | ) 7 | 8 | // ZipSlice is of type: func(input []T, others ...[]T) [][]T. 9 | // Group each item from the input with it's corresponding item(s) from the others. 10 | func ZipSlice(input interface{}, others ...interface{}) interface{} { 11 | inputType := reflect.TypeOf(input) 12 | 13 | inputs := append([]interface{}{input}, others...) 14 | inputValues := MapSlice(reflect.ValueOf, inputs).([]reflect.Value) 15 | 16 | for i := 0; i < len(inputValues); i++ { 17 | if inputValues[i].Kind() != reflect.Slice && 18 | inputValues[i].Kind() != reflect.Array { 19 | panic(fmt.Sprintf("ZipSlice called on invalid type: %s", inputValues[i].Type())) 20 | } 21 | 22 | if inputValues[i].Type() != inputType { 23 | panic(fmt.Sprintf("Zip input types must match, but they were %v and %v", inputType, inputValues[i].Type())) 24 | } 25 | } 26 | 27 | outputLength := inputValues[0].Len() 28 | for i := 0; i < len(inputValues); i++ { 29 | length := inputValues[i].Len() 30 | if length < outputLength { 31 | outputLength = length 32 | } 33 | } 34 | 35 | elemType := inputType.Elem() 36 | zippedType := reflect.SliceOf(elemType) 37 | outputType := reflect.SliceOf(zippedType) 38 | output := reflect.MakeSlice(outputType, 0, outputLength) 39 | 40 | for i := 0; i < outputLength; i++ { 41 | zipped := reflect.MakeSlice(zippedType, 0, len(inputValues)) 42 | 43 | for j := 0; j < len(inputValues); j++ { 44 | zipped = reflect.Append(zipped, inputValues[j].Index(i)) 45 | } 46 | 47 | output = reflect.Append(output, zipped) 48 | } 49 | 50 | return output.Interface() 51 | } 52 | -------------------------------------------------------------------------------- /zip_slice_example_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | func ExampleZipSlice() { 8 | in := []int{5, 10, 20} 9 | other := []int{6, 11} 10 | out := ZipSlice(in, other).([][]int) 11 | 12 | for _, result := range out { 13 | fmt.Println(result) 14 | } 15 | 16 | // Output: 17 | // [5 6] 18 | // [10 11] 19 | } 20 | -------------------------------------------------------------------------------- /zip_slice_test.go: -------------------------------------------------------------------------------- 1 | package pipe 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestZipSlice(t *testing.T) { 8 | in := []int{5, 10, 20} 9 | other := []int{6, 11} 10 | result := ZipSlice(in, other).([][]int) 11 | 12 | expected := [][]int{{5, 6}, {10, 11}} 13 | if len(result) != len(expected) { 14 | t.Fatal("expected output to match", expected, "but got", result) 15 | } 16 | 17 | for i := 0; i < len(expected); i++ { 18 | if len(result[i]) != len(expected[i]) { 19 | t.Fatal("expected output to match", expected, "but got", result) 20 | } 21 | 22 | for j := 0; j < len(result[i]); j++ { 23 | if result[i][j] != expected[i][j] { 24 | t.Fatal("expected channel output to match", expected, "but got", result) 25 | } 26 | } 27 | } 28 | } 29 | --------------------------------------------------------------------------------