├── Chapter10 ├── go.mod ├── main.go └── pkg │ ├── concurrentfunctions.go │ └── pipeline.go ├── Chapter11 ├── postGenerics │ ├── go.mod │ ├── go.sum │ └── main.go └── preGenerics │ ├── go.mod │ ├── go.sum │ ├── main.go │ └── pkg │ ├── dog.go │ └── dogs_pie.go ├── Chapter2 ├── Examples │ └── TestingExample │ │ ├── cli │ │ └── main.go │ │ ├── go.mod │ │ └── pkg │ │ ├── db.go │ │ ├── todo.go │ │ └── todo_test.go ├── go.mod └── main.go ├── Chapter3 ├── Examples │ └── Example1 │ │ ├── go.mod │ │ └── main.go ├── FunctionCurrying │ ├── go.mod │ └── main.go └── PartialApplication │ ├── go.mod │ └── main.go ├── Chapter4 ├── Examples │ └── HotdogShop │ │ ├── ImpureHotdogShop │ │ ├── go.mod │ │ ├── main.go │ │ └── pkg │ │ │ └── hotdogshop.go │ │ └── PureHotdogShop │ │ ├── go.mod │ │ ├── main.go │ │ └── pkg │ │ ├── hotdogshop.go │ │ └── hotdogshop_test.go ├── ReferentialTransparency │ └── go.mod └── TestableCode │ ├── go.mod │ ├── main.go │ └── player │ ├── player.go │ └── player_test.go ├── Chapter5 ├── Benchmark │ ├── go.mod │ ├── main.go │ └── pkg │ │ ├── person.go │ │ └── person_test.go └── Monads │ ├── go.mod │ ├── main.go │ └── pkg │ └── maybe.go ├── Chapter6 ├── go.mod ├── main.go ├── pkg │ ├── maps.go │ ├── predicates.go │ └── reducers.go └── resources │ └── airlines.json ├── Chapter7 └── Examples │ ├── Performance │ ├── Performance │ ├── go.mod │ ├── main.go │ └── pkg │ │ ├── performance.go │ │ └── performance_test.go │ ├── RecursionFirstClassCitizens │ ├── go.mod │ └── main.go │ └── Trees │ ├── go.mod │ └── main.go ├── Chapter8 ├── BuilderExample │ ├── go.mod │ └── main.go ├── CPS │ ├── go.mod │ └── main.go ├── CodeGenerator │ └── go.mod ├── FunctionChaining │ ├── go.mod │ ├── main.go │ └── pkg │ │ ├── maps.go │ │ ├── predicates.go │ │ ├── primes.go │ │ └── reducers.go └── LazyEvaluation │ ├── go.mod │ ├── main.go │ └── pkg │ ├── maps.go │ ├── maybe.go │ ├── predicates.go │ └── reducers.go ├── Chapter9 ├── go.mod ├── main.go └── pkg │ ├── fp │ ├── decorator.go │ └── strategy.go │ └── oop │ ├── decorator.go │ └── strategy.go ├── LICENSE └── README.md /Chapter10/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/PacktPublishing/Chapter10 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /Chapter10/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | "strings" 7 | 8 | "github.com/PacktPublishing/Chapter10/pkg" 9 | ) 10 | 11 | type ResponseFunc func(*http.Response) 12 | 13 | func main() { 14 | /* 15 | generated := pkg.Generator(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) 16 | 17 | filtered := pkg.ConcurrentFilter(generated, func(i int) bool { return i%2 == 0 }, 3) 18 | fmt.Printf("%v\n", output) 19 | mapped = pkg.ConcurrentMap(filtered, func(i int) int { return i * 2 }, 2) 20 | fmt.Printf("%v\n", output) 21 | collected := pkg.Collect(mapped) 22 | fmt.Printf("%v\n", collected) 23 | 24 | result := pkg.ConcurrentFMap(output, func(i int) string { return "number: " + strconv.Itoa(i) }, 2) 25 | fmt.Printf("%v\n", result) 26 | */ 27 | 28 | fmt.Printf("\n\n\n===pipelines===\n") 29 | generated := pkg.Generator(1, 2, 3, 4) 30 | filtered := pkg.FilterNode(generated, func(i int) bool { return i%2 == 0 }) 31 | mapped := pkg.MapNode(filtered, func(i int) int { return i * 2 }) 32 | collected := pkg.Collector(mapped) 33 | fmt.Printf("%v\n", collected) 34 | 35 | fmt.Println("chaining") 36 | out := pkg.ChainPipes(pkg.Generator(1, 2, 3, 4), 37 | pkg.CurriedFilterNode(func(i int) bool { return i%2 == 0 }), 38 | pkg.CurriedMapNode(func(i int) int { return i * i })) 39 | 40 | fmt.Println(out) 41 | 42 | fmt.Println("chain pipes 2") 43 | out2 := pkg.ChainPipes2[string](pkg.CurriedCat("./main.go"), 44 | pkg.CurriedFilterNode(func(s string) bool { return strings.Contains(s, "func") }), 45 | pkg.CurriedMapNode(func(i string) string { return "line contains func: " + i })) 46 | 47 | fmt.Printf("%v\n", out2) 48 | } 49 | 50 | /* 51 | func main() { 52 | success := func(response *http.Response) { 53 | fmt.Println("success") 54 | content, err := ioutil.ReadAll(response.Body) 55 | if err != nil { 56 | panic(err) 57 | } 58 | fmt.Printf("%v\n", string(content)) 59 | } 60 | failure := func(response *http.Response) { 61 | fmt.Printf("something went wrong, received: %d\n", response.StatusCode) 62 | } 63 | go getURL("https://news.ycombinator.com", success, failure) 64 | go getURL("https://news.ycombinator.com/ThisPageDoesNotExist", success, failure) 65 | done := make(chan bool) 66 | <-done // keep main alive 67 | } 68 | 69 | func getURL(url string, onSuccess, onFailure ResponseFunc) { 70 | resp, err := http.Get(url) 71 | if err != nil { 72 | panic(err) 73 | } 74 | if resp.StatusCode >= 200 && resp.StatusCode < 300 { 75 | onSuccess(resp) 76 | } else { 77 | onFailure(resp) 78 | } 79 | } 80 | */ 81 | -------------------------------------------------------------------------------- /Chapter10/pkg/concurrentfunctions.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | ) 7 | 8 | type ( 9 | Predicate[A any] func(A) bool 10 | MapFunc[A any] func(A) A 11 | FMapFunc[A, B any] func(A) B 12 | ) 13 | 14 | func Filter[A any](input []A, p Predicate[A], out chan []A) { 15 | output := []A{} 16 | for _, element := range input { 17 | if p(element) { 18 | output = append(output, element) 19 | } 20 | } 21 | out <- output 22 | } 23 | 24 | func ConcurrentFilter[A any](input []A, p Predicate[A], batchSize int) []A { 25 | output := []A{} 26 | 27 | out := make(chan []A) 28 | threadCount := int(math.Ceil(float64(len(input)) / float64(batchSize))) 29 | fmt.Printf("goroutines: %d\n", threadCount) 30 | for i := 0; i < threadCount; i++ { 31 | fmt.Println("spun up thread") 32 | if ((i + 1) * batchSize) < len(input) { 33 | go Filter(input[i*batchSize:(i+1)*batchSize], p, out) 34 | } else { 35 | go Filter(input[i*batchSize:], p, out) 36 | } 37 | } 38 | 39 | for i := 0; i < threadCount; i++ { 40 | filtered := <-out 41 | fmt.Printf("got data: %v\n", filtered) 42 | output = append(output, filtered...) 43 | } 44 | return output 45 | } 46 | 47 | // Map is a transformation whereby the type of the element and structure of the container is preserved 48 | func Map[A any](input []A, m MapFunc[A], out chan []A) { 49 | output := make([]A, len(input)) 50 | for i, element := range input { 51 | output[i] = m(element) 52 | } 53 | out <- output 54 | } 55 | 56 | func ConcurrentMap[A any](input []A, mapFn MapFunc[A], batchSize int) []A { 57 | output := []A{} 58 | 59 | out := make(chan []A) 60 | threadCount := int(math.Ceil(float64(len(input)) / float64(batchSize))) 61 | fmt.Printf("goroutines: %d\n", threadCount) 62 | for i := 0; i < threadCount; i++ { 63 | fmt.Println("spun up thread") 64 | if ((i + 1) * batchSize) < len(input) { 65 | go Map(input[i*batchSize:(i+1)*batchSize], mapFn, out) 66 | } else { 67 | go Map(input[i*batchSize:], mapFn, out) 68 | } 69 | } 70 | 71 | for i := 0; i < threadCount; i++ { 72 | mapped := <-out 73 | fmt.Printf("got data: %v\n", mapped) 74 | output = append(output, mapped...) 75 | } 76 | return output 77 | } 78 | 79 | func FMap[A, B any](input []A, m func(A) B, out chan []B) { 80 | output := make([]B, len(input)) 81 | for i, element := range input { 82 | output[i] = m(element) 83 | } 84 | out <- output 85 | } 86 | 87 | func ConcurrentFMap[A, B any](input []A, fMapFn FMapFunc[A, B], batchSize int) []B { 88 | output := []B{} 89 | 90 | out := make(chan []B) 91 | threadCount := int(math.Ceil(float64(len(input)) / float64(batchSize))) 92 | fmt.Printf("goroutines: %d\n", threadCount) 93 | for i := 0; i < threadCount; i++ { 94 | fmt.Println("spun up thread") 95 | if ((i + 1) * batchSize) < len(input) { 96 | go FMap(input[i*batchSize:(i+1)*batchSize], fMapFn, out) 97 | } else { 98 | go FMap(input[i*batchSize:], fMapFn, out) 99 | } 100 | } 101 | 102 | for i := 0; i < threadCount; i++ { 103 | mapped := <-out 104 | fmt.Printf("got data: %v\n", mapped) 105 | output = append(output, mapped...) 106 | } 107 | return output 108 | } 109 | -------------------------------------------------------------------------------- /Chapter10/pkg/pipeline.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "io/ioutil" 5 | "strings" 6 | ) 7 | 8 | type ( 9 | Node[A any] func(<-chan A) <-chan A 10 | GeneratorNode[A any] func() <-chan A 11 | CollectorNode[A any] func(<-chan A) []A 12 | ) 13 | 14 | func CurriedFilterNode[A any](p Predicate[A]) Node[A] { 15 | return func(in <-chan A) <-chan A { 16 | out := make(chan A) 17 | go func() { 18 | for n := range in { 19 | if p(n) { 20 | out <- n 21 | } 22 | } 23 | close(out) 24 | }() 25 | return out 26 | } 27 | } 28 | 29 | func CurriedMapNode[A any](mapFn MapFunc[A]) Node[A] { 30 | return func(in <-chan A) <-chan A { 31 | out := make(chan A) 32 | go func() { 33 | for n := range in { 34 | out <- mapFn(n) 35 | } 36 | close(out) 37 | }() 38 | return out 39 | } 40 | } 41 | 42 | // could abstract generators and collectors 43 | func ChainPipes[A any](in <-chan A, nodes ...Node[A]) []A { 44 | for _, node := range nodes { 45 | in = node(in) 46 | } 47 | return Collector(in) 48 | } 49 | 50 | func ChainPipes2[A any](gn GeneratorNode[A], nodes ...Node[A]) []A { 51 | in := gn() 52 | for _, node := range nodes { 53 | in = node(in) 54 | } 55 | return Collector(in) 56 | } 57 | 58 | func Generator[A any](input ...A) <-chan A { 59 | out := make(chan A) 60 | go func() { 61 | for _, element := range input { 62 | out <- element 63 | } 64 | close(out) 65 | }() 66 | return out 67 | } 68 | 69 | func CurriedCat(filepath string) func() <-chan string { 70 | return func() <-chan string { 71 | out := make(chan string) 72 | f, err := ioutil.ReadFile(filepath) 73 | if err != nil { 74 | panic(err) 75 | } 76 | go func() { 77 | lines := strings.Split(string(f), "\n") 78 | for _, line := range lines { 79 | out <- line 80 | } 81 | close(out) 82 | }() 83 | return out 84 | } 85 | 86 | } 87 | 88 | func Cat(filepath string) <-chan string { 89 | out := make(chan string) 90 | f, err := ioutil.ReadFile(filepath) 91 | if err != nil { 92 | panic(err) 93 | } 94 | go func() { 95 | lines := strings.Split(string(f), "\n") 96 | for _, line := range lines { 97 | out <- line 98 | } 99 | close(out) 100 | }() 101 | return out 102 | } 103 | 104 | func FilterNode[A any](in <-chan A, predicate Predicate[A]) <-chan A { 105 | out := make(chan A) 106 | go func() { 107 | for n := range in { 108 | if predicate(n) { 109 | out <- n 110 | } 111 | } 112 | close(out) 113 | }() 114 | return out 115 | } 116 | 117 | func MapNode[A any](in <-chan A, mapf MapFunc[A]) <-chan A { 118 | out := make(chan A) 119 | go func() { 120 | for n := range in { 121 | out <- mapf(n) 122 | } 123 | close(out) 124 | }() 125 | return out 126 | } 127 | 128 | func Collector[A any](in <-chan A) []A { 129 | output := []A{} 130 | for n := range in { 131 | output = append(output, n) 132 | } 133 | return output 134 | } 135 | -------------------------------------------------------------------------------- /Chapter11/postGenerics/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/PacktPublishing/Chapter11/postgenerics 2 | 3 | go 1.18 4 | 5 | require ( 6 | github.com/elliotchance/pie/v2 v2.3.0 7 | github.com/samber/lo v1.37.0 8 | github.com/samber/mo v1.8.0 9 | ) 10 | 11 | require golang.org/x/exp v0.0.0-20220321173239-a90fa8a75705 // indirect 12 | -------------------------------------------------------------------------------- /Chapter11/postGenerics/go.sum: -------------------------------------------------------------------------------- 1 | github.com/elliotchance/pie/v2 v2.3.0 h1:ImQUkdyhUSX6C9JF/5k0yn2hbxzA+JB/cSDMAMO0Zfk= 2 | github.com/elliotchance/pie/v2 v2.3.0/go.mod h1:18t0dgGFH006g4eVdDtWfgFZPQEgl10IoEO8YWEq3Og= 3 | github.com/samber/lo v1.37.0 h1:XjVcB8g6tgUp8rsPsJ2CvhClfImrpL04YpQHXeHPhRw= 4 | github.com/samber/lo v1.37.0/go.mod h1:9vaz2O4o8oOnK23pd2TrXufcbdbJIa3b6cstBWKpopA= 5 | github.com/samber/mo v1.8.0 h1:vYjHTfg14JF9tD2NLhpoUsRi9bjyRoYwa4+do0nvbVw= 6 | github.com/samber/mo v1.8.0/go.mod h1:BfkrCPuYzVG3ZljnZB783WIJIGk1mcZr9c9CPf8tAxs= 7 | golang.org/x/exp v0.0.0-20220321173239-a90fa8a75705 h1:ba9YlqfDGTTQ5aZ2fwOoQ1hf32QySyQkR6ODGDzHlnE= 8 | golang.org/x/exp v0.0.0-20220321173239-a90fa8a75705/go.mod h1:lgLbSvA5ygNOMpwM/9anMpWVlVJ7Z+cHWq/eFuinpGE= 9 | -------------------------------------------------------------------------------- /Chapter11/postGenerics/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/elliotchance/pie/v2" 9 | "github.com/samber/lo" 10 | lop "github.com/samber/lo/parallel" 11 | "github.com/samber/mo" 12 | ) 13 | 14 | func main() { 15 | names := pie.FilterNot([]string{"Bob", "Sally", "John", "Jane"}, 16 | func(name string) bool { 17 | return strings.HasPrefix(name, "J") 18 | }) 19 | 20 | fmt.Println(names) // "[Bob Sally]" 21 | 22 | MyDogs := []Dog{ 23 | Dog{ 24 | "Bucky", 25 | 1, 26 | }, 27 | Dog{ 28 | "Keeno", 29 | 15, 30 | }, 31 | Dog{ 32 | "Tala", 33 | 16, 34 | }, 35 | Dog{ 36 | "Amigo", 37 | 7, 38 | }, 39 | Dog{ 40 | "Keeno", 41 | 15, 42 | }, 43 | } 44 | result := pie.Of(MyDogs). 45 | Filter(func(d Dog) bool { 46 | return d.Age > 10 47 | }).Map(func(d Dog) Dog { 48 | d.Name = strings.ToUpper(d.Name) 49 | return d 50 | }). 51 | SortUsing(func(a, b Dog) bool { 52 | return a.Age < b.Age 53 | }) 54 | fmt.Printf("out: %v\n", result) 55 | 56 | fmt.Println("examples with lo") 57 | 58 | result2 := 59 | lo.Map(lo.Uniq(MyDogs), func(d Dog, i int) Dog { 60 | d.Name = strings.ToUpper(d.Name) 61 | return d 62 | }) 63 | fmt.Printf("%v\n", result2) 64 | 65 | fmt.Println("examples with Lo in parallel") 66 | result3 := 67 | lop.Map(lo.Uniq(MyDogs), func(d Dog, i int) Dog { 68 | d.Name = strings.ToUpper(d.Name) 69 | return d 70 | }) 71 | 72 | fmt.Printf("%v\n", result3) 73 | 74 | fmt.Println("examples with Mo") 75 | 76 | maybe := mo.Some(Dog{"Bucky", 1}) 77 | getOrElse := maybe.OrElse(Dog{}) 78 | fmt.Println(getOrElse) 79 | 80 | maybe2 := mo.None[Dog]() 81 | getOrElse2 := maybe2.OrElse(Dog{"Default", -1}) 82 | fmt.Println(getOrElse2) 83 | 84 | ok := mo.Ok(MyDogs[0]) 85 | result1 := ok.OrElse(Dog{}) 86 | err1 := ok.Error() 87 | 88 | fmt.Println(result1, err1) 89 | 90 | err := errors.New("dog not found") 91 | ok2 := mo.Err[Dog](err) 92 | result4 := ok2.OrElse(Dog{"Default", -1}) 93 | err2 := ok2.Error() 94 | 95 | fmt.Println(result4, err2) 96 | 97 | } 98 | 99 | type Dogs []Dog 100 | 101 | type Dog struct { 102 | Name string 103 | Age int 104 | } 105 | -------------------------------------------------------------------------------- /Chapter11/preGenerics/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/PacktPublishing/Chapter11/pregenerics 2 | 3 | go 1.18 4 | 5 | require github.com/elliotchance/pie v1.39.0 6 | -------------------------------------------------------------------------------- /Chapter11/preGenerics/go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 2 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/elliotchance/pie v1.39.0 h1:oudoOFLPYvWwsJ/J2dFv3uHkdHdq7oSv8aNMWqUQVv8= 4 | github.com/elliotchance/pie v1.39.0/go.mod h1:W/nLuTGZ1dLKzRS0Z2g2N2evWzMenuDnBhk0s6Y9k54= 5 | github.com/elliotchance/testify-stats v1.0.0/go.mod h1:Mc25k7L4E65uf6CfW+s/pY04XcoiqQBrfIRsWQcgweA= 6 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 7 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 8 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 9 | -------------------------------------------------------------------------------- /Chapter11/preGenerics/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/PacktPublishing/Chapter11/pregenerics/pkg" 7 | "github.com/elliotchance/pie/pie" 8 | ) 9 | 10 | func main() { 11 | out := pie.Ints{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}. 12 | Filter(func(i int) bool { 13 | return i%2 == 0 14 | }). 15 | Map(func(i int) int { return i * i }) 16 | 17 | fmt.Printf("result: %v\n", out) 18 | 19 | MyDogs := []pkg.Dog{ 20 | pkg.Dog{ 21 | "Bucky", 22 | 1, 23 | }, 24 | pkg.Dog{ 25 | "Keeno", 26 | 15, 27 | }, 28 | pkg.Dog{ 29 | "Tala", 30 | 16, 31 | }, 32 | pkg.Dog{ 33 | "Amigo", 34 | 7, 35 | }, 36 | } 37 | 38 | results := pkg.Dogs(MyDogs). 39 | Filter(func(d pkg.Dog) bool { 40 | return d.Age > 10 41 | }).SortUsing(func(a, b pkg.Dog) bool { 42 | return a.Age < b.Age 43 | }) 44 | fmt.Printf("results: %v\n", results) 45 | } 46 | -------------------------------------------------------------------------------- /Chapter11/preGenerics/pkg/dog.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | //go:generate pie Dogs.* 4 | type Dogs []Dog 5 | 6 | type Dog struct { 7 | Name string 8 | Age int 9 | } 10 | -------------------------------------------------------------------------------- /Chapter11/preGenerics/pkg/dogs_pie.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "fmt" 7 | "github.com/elliotchance/pie/pie" 8 | "github.com/elliotchance/pie/pie/util" 9 | "math/rand" 10 | "sort" 11 | "strconv" 12 | "strings" 13 | ) 14 | 15 | // All will return true if all callbacks return true. It follows the same logic 16 | // as the all() function in Python. 17 | // 18 | // If the list is empty then true is always returned. 19 | func (ss Dogs) All(fn func(value Dog) bool) bool { 20 | for _, value := range ss { 21 | if !fn(value) { 22 | return false 23 | } 24 | } 25 | 26 | return true 27 | } 28 | 29 | // Any will return true if any callbacks return true. It follows the same logic 30 | // as the any() function in Python. 31 | // 32 | // If the list is empty then false is always returned. 33 | func (ss Dogs) Any(fn func(value Dog) bool) bool { 34 | for _, value := range ss { 35 | if fn(value) { 36 | return true 37 | } 38 | } 39 | 40 | return false 41 | } 42 | 43 | // Append will return a new slice with the elements appended to the end. 44 | // 45 | // It is acceptable to provide zero arguments. 46 | func (ss Dogs) Append(elements ...Dog) Dogs { 47 | // Copy ss, to make sure no memory is overlapping between input and 48 | // output. See issue #97. 49 | result := append(Dogs{}, ss...) 50 | 51 | result = append(result, elements...) 52 | return result 53 | } 54 | 55 | // Bottom will return n elements from bottom 56 | // 57 | // that means that elements is taken from the end of the slice 58 | // for this [1,2,3] slice with n == 2 will be returned [3,2] 59 | // if the slice has less elements then n that'll return all elements 60 | // if n < 0 it'll return empty slice. 61 | func (ss Dogs) Bottom(n int) (top Dogs) { 62 | var lastIndex = len(ss) - 1 63 | for i := lastIndex; i > -1 && n > 0; i-- { 64 | top = append(top, ss[i]) 65 | n-- 66 | } 67 | 68 | return 69 | } 70 | 71 | // Contains returns true if the element exists in the slice. 72 | // 73 | // When using slices of pointers it will only compare by address, not value. 74 | func (ss Dogs) Contains(lookingFor Dog) bool { 75 | for _, s := range ss { 76 | if lookingFor == s { 77 | return true 78 | } 79 | } 80 | 81 | return false 82 | } 83 | 84 | // Diff returns the elements that needs to be added or removed from the first 85 | // slice to have the same elements in the second slice. 86 | // 87 | // The order of elements is not taken into consideration, so the slices are 88 | // treated sets that allow duplicate items. 89 | // 90 | // The added and removed returned may be blank respectively, or contain upto as 91 | // many elements that exists in the largest slice. 92 | func (ss Dogs) Diff(against Dogs) (added, removed Dogs) { 93 | // This is probably not the best way to do it. We do an O(n^2) between the 94 | // slices to see which items are missing in each direction. 95 | 96 | diffOneWay := func(ss1, ss2raw Dogs) (result Dogs) { 97 | ss2 := make(Dogs, len(ss2raw)) 98 | copy(ss2, ss2raw) 99 | 100 | for _, s := range ss1 { 101 | found := false 102 | 103 | for i, element := range ss2 { 104 | if s == element { 105 | ss2 = append(ss2[:i], ss2[i+1:]...) 106 | found = true 107 | break 108 | } 109 | } 110 | 111 | if !found { 112 | result = append(result, s) 113 | } 114 | } 115 | 116 | return 117 | } 118 | 119 | removed = diffOneWay(ss, against) 120 | added = diffOneWay(against, ss) 121 | 122 | return 123 | } 124 | 125 | // DropTop will return the rest slice after dropping the top n elements 126 | // if the slice has less elements then n that'll return empty slice 127 | // if n < 0 it'll return empty slice. 128 | func (ss Dogs) DropTop(n int) (drop Dogs) { 129 | if n < 0 || n >= len(ss) { 130 | return 131 | } 132 | 133 | // Copy ss, to make sure no memory is overlapping between input and 134 | // output. See issue #145. 135 | drop = make([]Dog, len(ss)-n) 136 | copy(drop, ss[n:]) 137 | 138 | return 139 | } 140 | 141 | // Drop items from the slice while f(item) is true. 142 | // Afterwards, return every element until the slice is empty. It follows the same logic as the dropwhile() function from itertools in Python. 143 | func (ss Dogs) DropWhile(f func(s Dog) bool) (ss2 Dogs) { 144 | ss2 = make([]Dog, len(ss)) 145 | copy(ss2, ss) 146 | for i, value := range ss2 { 147 | if !f(value) { 148 | return ss2[i:] 149 | } 150 | } 151 | return Dogs{} 152 | } 153 | 154 | // Each is more condensed version of Transform that allows an action to happen 155 | // on each elements and pass the original slice on. 156 | // 157 | // cars.Each(func (car *Car) { 158 | // fmt.Printf("Car color is: %s\n", car.Color) 159 | // }) 160 | // 161 | // Pie will not ensure immutability on items passed in so they can be 162 | // manipulated, if you choose to do it this way, for example: 163 | // 164 | // // Set all car colors to Red. 165 | // cars.Each(func (car *Car) { 166 | // car.Color = "Red" 167 | // }) 168 | // 169 | func (ss Dogs) Each(fn func(Dog)) Dogs { 170 | for _, s := range ss { 171 | fn(s) 172 | } 173 | 174 | return ss 175 | } 176 | 177 | // Equals compare elements from the start to the end, 178 | // 179 | // if they are the same is considered the slices are equal if all elements are the same is considered the slices are equal 180 | // if each slice == nil is considered that they're equal 181 | // 182 | // if element realizes Equals interface it uses that method, in other way uses default compare 183 | func (ss Dogs) Equals(rhs Dogs) bool { 184 | if len(ss) != len(rhs) { 185 | return false 186 | } 187 | 188 | for i := range ss { 189 | if !(ss[i] == rhs[i]) { 190 | return false 191 | } 192 | } 193 | 194 | return true 195 | } 196 | 197 | // Extend will return a new slice with the slices of elements appended to the 198 | // end. 199 | // 200 | // It is acceptable to provide zero arguments. 201 | func (ss Dogs) Extend(slices ...Dogs) (ss2 Dogs) { 202 | ss2 = ss 203 | 204 | for _, slice := range slices { 205 | ss2 = ss2.Append(slice...) 206 | } 207 | 208 | return ss2 209 | } 210 | 211 | // Filter will return a new slice containing only the elements that return 212 | // true from the condition. The returned slice may contain zero elements (nil). 213 | // 214 | // FilterNot works in the opposite way of Filter. 215 | func (ss Dogs) Filter(condition func(Dog) bool) (ss2 Dogs) { 216 | for _, s := range ss { 217 | if condition(s) { 218 | ss2 = append(ss2, s) 219 | } 220 | } 221 | return 222 | } 223 | 224 | // FilterNot works the same as Filter, with a negated condition. That is, it will 225 | // return a new slice only containing the elements that returned false from the 226 | // condition. The returned slice may contain zero elements (nil). 227 | func (ss Dogs) FilterNot(condition func(Dog) bool) (ss2 Dogs) { 228 | for _, s := range ss { 229 | if !condition(s) { 230 | ss2 = append(ss2, s) 231 | } 232 | } 233 | 234 | return 235 | } 236 | 237 | // FindFirstUsing will return the index of the first element when the callback returns true or -1 if no element is found. 238 | // It follows the same logic as the findIndex() function in Javascript. 239 | // 240 | // If the list is empty then -1 is always returned. 241 | func (ss Dogs) FindFirstUsing(fn func(value Dog) bool) int { 242 | for idx, value := range ss { 243 | if fn(value) { 244 | return idx 245 | } 246 | } 247 | 248 | return -1 249 | } 250 | 251 | // First returns the first element, or zero. Also see FirstOr(). 252 | func (ss Dogs) First() Dog { 253 | return ss.FirstOr(Dog{}) 254 | } 255 | 256 | // FirstOr returns the first element or a default value if there are no 257 | // elements. 258 | func (ss Dogs) FirstOr(defaultValue Dog) Dog { 259 | if len(ss) == 0 { 260 | return defaultValue 261 | } 262 | 263 | return ss[0] 264 | } 265 | 266 | // Float64s transforms each element to a float64. 267 | func (ss Dogs) Float64s() pie.Float64s { 268 | l := len(ss) 269 | 270 | // Avoid the allocation. 271 | if l == 0 { 272 | return nil 273 | } 274 | 275 | result := make(pie.Float64s, l) 276 | for i := 0; i < l; i++ { 277 | mightBeString := ss[i] 278 | result[i], _ = strconv.ParseFloat(fmt.Sprintf("%v", mightBeString), 64) 279 | } 280 | 281 | return result 282 | } 283 | 284 | // Insert a value at an index 285 | func (ss Dogs) Insert(index int, values ...Dog) Dogs { 286 | if index >= ss.Len() { 287 | return Dogs.Extend(ss, Dogs(values)) 288 | } 289 | 290 | return Dogs.Extend(ss[:index], Dogs(values), ss[index:]) 291 | } 292 | 293 | // Ints transforms each element to an integer. 294 | func (ss Dogs) Ints() pie.Ints { 295 | l := len(ss) 296 | 297 | // Avoid the allocation. 298 | if l == 0 { 299 | return nil 300 | } 301 | 302 | result := make(pie.Ints, l) 303 | for i := 0; i < l; i++ { 304 | mightBeString := ss[i] 305 | f, _ := strconv.ParseFloat(fmt.Sprintf("%v", mightBeString), 64) 306 | result[i] = int(f) 307 | } 308 | 309 | return result 310 | } 311 | 312 | // Join returns a string from joining each of the elements. 313 | func (ss Dogs) Join(glue string) (s string) { 314 | var slice interface{} = []Dog(ss) 315 | 316 | if y, ok := slice.([]string); ok { 317 | // The stdlib is efficient for type []string 318 | return strings.Join(y, glue) 319 | } else { 320 | // General case 321 | parts := make([]string, len(ss)) 322 | for i, element := range ss { 323 | mightBeString := element 324 | parts[i] = fmt.Sprintf("%v", mightBeString) 325 | } 326 | return strings.Join(parts, glue) 327 | } 328 | } 329 | 330 | // JSONBytes returns the JSON encoded array as bytes. 331 | // 332 | // One important thing to note is that it will treat a nil slice as an empty 333 | // slice to ensure that the JSON value return is always an array. 334 | func (ss Dogs) JSONBytes() []byte { 335 | if ss == nil { 336 | return []byte("[]") 337 | } 338 | 339 | // An error should not be possible. 340 | data, _ := json.Marshal(ss) 341 | 342 | return data 343 | } 344 | 345 | // JSONBytesIndent returns the JSON encoded array as bytes with indent applied. 346 | // 347 | // One important thing to note is that it will treat a nil slice as an empty 348 | // slice to ensure that the JSON value return is always an array. See 349 | // json.MarshalIndent for details. 350 | func (ss Dogs) JSONBytesIndent(prefix, indent string) []byte { 351 | if ss == nil { 352 | return []byte("[]") 353 | } 354 | 355 | // An error should not be possible. 356 | data, _ := json.MarshalIndent(ss, prefix, indent) 357 | 358 | return data 359 | } 360 | 361 | // JSONString returns the JSON encoded array as a string. 362 | // 363 | // One important thing to note is that it will treat a nil slice as an empty 364 | // slice to ensure that the JSON value return is always an array. 365 | func (ss Dogs) JSONString() string { 366 | if ss == nil { 367 | return "[]" 368 | } 369 | 370 | // An error should not be possible. 371 | data, _ := json.Marshal(ss) 372 | 373 | return string(data) 374 | } 375 | 376 | // JSONStringIndent returns the JSON encoded array as a string with indent applied. 377 | // 378 | // One important thing to note is that it will treat a nil slice as an empty 379 | // slice to ensure that the JSON value return is always an array. See 380 | // json.MarshalIndent for details. 381 | func (ss Dogs) JSONStringIndent(prefix, indent string) string { 382 | if ss == nil { 383 | return "[]" 384 | } 385 | 386 | // An error should not be possible. 387 | data, _ := json.MarshalIndent(ss, prefix, indent) 388 | 389 | return string(data) 390 | } 391 | 392 | // Last returns the last element, or zero. Also see LastOr(). 393 | func (ss Dogs) Last() Dog { 394 | return ss.LastOr(Dog{}) 395 | } 396 | 397 | // LastOr returns the last element or a default value if there are no elements. 398 | func (ss Dogs) LastOr(defaultValue Dog) Dog { 399 | if len(ss) == 0 { 400 | return defaultValue 401 | } 402 | 403 | return ss[len(ss)-1] 404 | } 405 | 406 | // Len returns the number of elements. 407 | func (ss Dogs) Len() int { 408 | return len(ss) 409 | } 410 | 411 | // Map will return a new slice where each element has been mapped (transformed). 412 | // The number of elements returned will always be the same as the input. 413 | // 414 | // Be careful when using this with slices of pointers. If you modify the input 415 | // value it will affect the original slice. Be sure to return a new allocated 416 | // object or deep copy the existing one. 417 | func (ss Dogs) Map(fn func(Dog) Dog) (ss2 Dogs) { 418 | if ss == nil { 419 | return nil 420 | } 421 | 422 | ss2 = make([]Dog, len(ss)) 423 | for i, s := range ss { 424 | ss2[i] = fn(s) 425 | } 426 | 427 | return 428 | } 429 | 430 | // Mode returns a new slice containing the most frequently occuring values. 431 | // 432 | // The number of items returned may be the same as the input or less. It will 433 | // never return zero items unless the input slice has zero items. 434 | func (ss Dogs) Mode() Dogs { 435 | if len(ss) == 0 { 436 | return nil 437 | } 438 | values := make(map[Dog]int) 439 | for _, s := range ss { 440 | values[s]++ 441 | } 442 | 443 | var maxFrequency int 444 | for _, v := range values { 445 | if v > maxFrequency { 446 | maxFrequency = v 447 | } 448 | } 449 | 450 | var maxValues Dogs 451 | for k, v := range values { 452 | if v == maxFrequency { 453 | maxValues = append(maxValues, k) 454 | } 455 | } 456 | 457 | return maxValues 458 | } 459 | 460 | // Pop the first element of the slice 461 | // 462 | // Usage Example: 463 | // 464 | // type knownGreetings []string 465 | // greetings := knownGreetings{"ciao", "hello", "hola"} 466 | // for greeting := greetings.Pop(); greeting != nil; greeting = greetings.Pop() { 467 | // fmt.Println(*greeting) 468 | // } 469 | func (ss *Dogs) Pop() (popped *Dog) { 470 | 471 | if len(*ss) == 0 { 472 | return 473 | } 474 | 475 | popped = &(*ss)[0] 476 | *ss = (*ss)[1:] 477 | return 478 | } 479 | 480 | // Random returns a random element by your rand.Source, or zero 481 | func (ss Dogs) Random(source rand.Source) Dog { 482 | n := len(ss) 483 | 484 | // Avoid the extra allocation. 485 | if n < 1 { 486 | return Dog{} 487 | } 488 | if n < 2 { 489 | return ss[0] 490 | } 491 | rnd := rand.New(source) 492 | i := rnd.Intn(n) 493 | return ss[i] 494 | } 495 | 496 | // Reverse returns a new copy of the slice with the elements ordered in reverse. 497 | // This is useful when combined with Sort to get a descending sort order: 498 | // 499 | // ss.Sort().Reverse() 500 | // 501 | func (ss Dogs) Reverse() Dogs { 502 | // Avoid the allocation. If there is one element or less it is already 503 | // reversed. 504 | if len(ss) < 2 { 505 | return ss 506 | } 507 | 508 | sorted := make([]Dog, len(ss)) 509 | for i := 0; i < len(ss); i++ { 510 | sorted[i] = ss[len(ss)-i-1] 511 | } 512 | 513 | return sorted 514 | } 515 | 516 | // Send sends elements to channel 517 | // in normal act it sends all elements but if func canceled it can be less 518 | // 519 | // it locks execution of gorutine 520 | // it doesn't close channel after work 521 | // returns sended elements if len(this) != len(old) considered func was canceled 522 | func (ss Dogs) Send(ctx context.Context, ch chan<- Dog) Dogs { 523 | for i, s := range ss { 524 | select { 525 | case <-ctx.Done(): 526 | return ss[:i] 527 | default: 528 | ch <- s 529 | } 530 | } 531 | 532 | return ss 533 | } 534 | 535 | // SequenceUsing generates slice in range using creator function 536 | // 537 | // There are 3 variations to generate: 538 | // 1. [0, n). 539 | // 2. [min, max). 540 | // 3. [min, max) with step. 541 | // 542 | // if len(params) == 1 considered that will be returned slice between 0 and n, 543 | // where n is the first param, [0, n). 544 | // if len(params) == 2 considered that will be returned slice between min and max, 545 | // where min is the first param, max is the second, [min, max). 546 | // if len(params) > 2 considered that will be returned slice between min and max with step, 547 | // where min is the first param, max is the second, step is the third one, [min, max) with step, 548 | // others params will be ignored 549 | func (ss Dogs) SequenceUsing(creator func(int) Dog, params ...int) Dogs { 550 | var seq = func(min, max, step int) (seq Dogs) { 551 | lenght := int(util.Round(float64(max-min) / float64(step))) 552 | if lenght < 1 { 553 | return 554 | } 555 | 556 | seq = make(Dogs, lenght) 557 | for i := 0; i < lenght; min += step { 558 | seq[i] = creator(min) 559 | i++ 560 | } 561 | 562 | return seq 563 | } 564 | 565 | if len(params) > 2 { 566 | return seq(params[0], params[1], params[2]) 567 | } else if len(params) == 2 { 568 | return seq(params[0], params[1], 1) 569 | } else if len(params) == 1 { 570 | return seq(0, params[0], 1) 571 | } else { 572 | return nil 573 | } 574 | } 575 | 576 | // Shift will return two values: the shifted value and the rest slice. 577 | func (ss Dogs) Shift() (Dog, Dogs) { 578 | return ss.First(), ss.DropTop(1) 579 | } 580 | 581 | // Shuffle returns shuffled slice by your rand.Source 582 | func (ss Dogs) Shuffle(source rand.Source) Dogs { 583 | n := len(ss) 584 | 585 | // Avoid the extra allocation. 586 | if n < 2 { 587 | return ss 588 | } 589 | 590 | // go 1.10+ provides rnd.Shuffle. However, to support older versions we copy 591 | // the algorithm directly from the go source: src/math/rand/rand.go below, 592 | // with some adjustments: 593 | shuffled := make([]Dog, n) 594 | copy(shuffled, ss) 595 | 596 | rnd := rand.New(source) 597 | 598 | util.Shuffle(rnd, n, func(i, j int) { 599 | shuffled[i], shuffled[j] = shuffled[j], shuffled[i] 600 | }) 601 | 602 | return shuffled 603 | } 604 | 605 | // SortStableUsing works similar to sort.SliceStable. However, unlike sort.SliceStable the 606 | // slice returned will be reallocated as to not modify the input slice. 607 | func (ss Dogs) SortStableUsing(less func(a, b Dog) bool) Dogs { 608 | // Avoid the allocation. If there is one element or less it is already 609 | // sorted. 610 | if len(ss) < 2 { 611 | return ss 612 | } 613 | 614 | sorted := make(Dogs, len(ss)) 615 | copy(sorted, ss) 616 | sort.SliceStable(sorted, func(i, j int) bool { 617 | return less(sorted[i], sorted[j]) 618 | }) 619 | 620 | return sorted 621 | } 622 | 623 | // SortUsing works similar to sort.Slice. However, unlike sort.Slice the 624 | // slice returned will be reallocated as to not modify the input slice. 625 | func (ss Dogs) SortUsing(less func(a, b Dog) bool) Dogs { 626 | // Avoid the allocation. If there is one element or less it is already 627 | // sorted. 628 | if len(ss) < 2 { 629 | return ss 630 | } 631 | 632 | sorted := make(Dogs, len(ss)) 633 | copy(sorted, ss) 634 | sort.Slice(sorted, func(i, j int) bool { 635 | return less(sorted[i], sorted[j]) 636 | }) 637 | 638 | return sorted 639 | } 640 | 641 | // Strings transforms each element to a string. 642 | // 643 | // If the element type implements fmt.Stringer it will be used. Otherwise it 644 | // will fallback to the result of: 645 | // 646 | // fmt.Sprintf("%v") 647 | // 648 | func (ss Dogs) Strings() pie.Strings { 649 | l := len(ss) 650 | 651 | // Avoid the allocation. 652 | if l == 0 { 653 | return nil 654 | } 655 | 656 | result := make(pie.Strings, l) 657 | for i := 0; i < l; i++ { 658 | mightBeString := ss[i] 659 | result[i] = fmt.Sprintf("%v", mightBeString) 660 | } 661 | 662 | return result 663 | } 664 | 665 | // SubSlice will return the subSlice from start to end(excluded) 666 | // 667 | // Condition 1: If start < 0 or end < 0, nil is returned. 668 | // Condition 2: If start >= end, nil is returned. 669 | // Condition 3: Return all elements that exist in the range provided, 670 | // if start or end is out of bounds, zero items will be placed. 671 | func (ss Dogs) SubSlice(start int, end int) (subSlice Dogs) { 672 | if start < 0 || end < 0 { 673 | return 674 | } 675 | 676 | if start >= end { 677 | return 678 | } 679 | 680 | length := ss.Len() 681 | if start < length { 682 | if end <= length { 683 | subSlice = ss[start:end] 684 | } else { 685 | zeroArray := make([]Dog, end-length) 686 | subSlice = ss[start:length].Append(zeroArray[:]...) 687 | } 688 | } else { 689 | zeroArray := make([]Dog, end-start) 690 | subSlice = zeroArray[:] 691 | } 692 | 693 | return 694 | } 695 | 696 | // Top will return n elements from head of the slice 697 | // if the slice has less elements then n that'll return all elements 698 | // if n < 0 it'll return empty slice. 699 | func (ss Dogs) Top(n int) (top Dogs) { 700 | for i := 0; i < len(ss) && n > 0; i++ { 701 | top = append(top, ss[i]) 702 | n-- 703 | } 704 | 705 | return 706 | } 707 | 708 | // StringsUsing transforms each element to a string. 709 | func (ss Dogs) StringsUsing(transform func(Dog) string) pie.Strings { 710 | l := len(ss) 711 | 712 | // Avoid the allocation. 713 | if l == 0 { 714 | return nil 715 | } 716 | 717 | result := make(pie.Strings, l) 718 | for i := 0; i < l; i++ { 719 | result[i] = transform(ss[i]) 720 | } 721 | 722 | return result 723 | } 724 | 725 | // Unshift adds one or more elements to the beginning of the slice 726 | // and returns the new slice. 727 | func (ss Dogs) Unshift(elements ...Dog) (unshift Dogs) { 728 | unshift = append(Dogs{}, elements...) 729 | unshift = append(unshift, ss...) 730 | 731 | return 732 | } 733 | -------------------------------------------------------------------------------- /Chapter2/Examples/TestingExample/cli/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/DylanMeeus/Book-FP/pkg" 4 | 5 | func main() { 6 | t := pkg.NewTodo() 7 | t.Write("hello world") 8 | } 9 | -------------------------------------------------------------------------------- /Chapter2/Examples/TestingExample/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/DylanMeeus/Book-FP 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /Chapter2/Examples/TestingExample/pkg/db.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "os" 5 | ) 6 | 7 | type authorizationFunc func() bool 8 | 9 | type Db struct { 10 | AuthorizationF authorizationFunc 11 | } 12 | 13 | func (d *Db) IsAuthorized() bool { 14 | return d.AuthorizationF() 15 | } 16 | 17 | func NewDB() *Db { 18 | return &Db{ 19 | AuthorizationF: argsAuthorization, 20 | } 21 | } 22 | 23 | func argsAuthorization() bool { 24 | user := os.Args[1] 25 | // super secure authorization 26 | // in reality, this would be a database call 27 | if user == "admin" { 28 | return true 29 | } 30 | return false 31 | } 32 | -------------------------------------------------------------------------------- /Chapter2/Examples/TestingExample/pkg/todo.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | fmt.Println("vim-go") 7 | } 8 | 9 | type Todo struct { 10 | Text string 11 | Db *Db 12 | } 13 | 14 | func NewTodo() Todo { 15 | return Todo{ 16 | Text: "", 17 | Db: NewDB(), 18 | } 19 | } 20 | 21 | func (t *Todo) Write(s string) { 22 | if t.Db.IsAuthorized() { 23 | t.Text = s 24 | } else { 25 | panic("user not authorized to write") 26 | } 27 | } 28 | 29 | func (t *Todo) Append(s string) { 30 | if t.Db.IsAuthorized() { 31 | t.Text += s 32 | } else { 33 | panic("user not authorized to append") 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Chapter2/Examples/TestingExample/pkg/todo_test.go: -------------------------------------------------------------------------------- 1 | package pkg_test 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/DylanMeeus/Book-FP/pkg" 7 | ) 8 | 9 | func TestTodoWrite(t *testing.T) { 10 | todo := pkg.Todo{ 11 | Db: &pkg.Db{ 12 | AuthorizationF: func() bool { return true }, 13 | }, 14 | } 15 | todo.Write("hello") 16 | if todo.Text != "hello" { 17 | t.Errorf("Expected 'hello' but got %v\n", todo.Text) 18 | } 19 | todo.Append(" world") 20 | if todo.Text != "hello world" { 21 | t.Errorf("Expected 'hello world' but got %v\n", todo.Text) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Chapter2/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/DylanMeeus/book 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /Chapter2/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type calculator struct { 6 | x int 7 | y int 8 | } 9 | 10 | /* 11 | calculate supports operations: +,-,/,* 12 | */ 13 | func calculate(a, b int, operation string) int { 14 | switch operation { 15 | case "+": 16 | return add(a, b) 17 | case "-": 18 | return sub(a, b) 19 | case "*": 20 | return mult(a, b) 21 | case "/": 22 | return div(a, b) 23 | default: 24 | panic("operation not supported") 25 | } 26 | } 27 | 28 | type calculateFunc func(int, int) int 29 | 30 | var ( 31 | operations = map[string]calculateFunc{ 32 | "+": add, 33 | "-": sub, 34 | "*": mult, 35 | "/": div, 36 | } 37 | ) 38 | 39 | func add(a, b int) int { 40 | return a + b 41 | } 42 | 43 | func sub(a, b int) int { 44 | return a - b 45 | } 46 | 47 | func mult(a, b int) int { 48 | return a + b 49 | } 50 | 51 | func div(a, b int) int { 52 | if b == 0 { 53 | panic("divide by zero") 54 | } 55 | return a / b 56 | } 57 | 58 | func calculateWithMap(a, b int, opString string) int { 59 | if operation, ok := operations[opString]; ok { 60 | return operation(a, b) 61 | } 62 | panic("operation not supported") 63 | } 64 | 65 | func main() { 66 | fmt.Println("Hello Go, from iPad Pro!") 67 | } 68 | -------------------------------------------------------------------------------- /Chapter3/Examples/Example1/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/DylanMeeus/Book-FP/Chapter3/Examples/Example1 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /Chapter3/Examples/Example1/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type ( 6 | ServerOptions func(options) options 7 | TransportType int 8 | ) 9 | 10 | const ( 11 | UDP TransportType = iota 12 | TCP 13 | ) 14 | 15 | type Server struct { 16 | options 17 | isAlive bool 18 | } 19 | 20 | type options struct { 21 | MaxConnection int 22 | TransportType TransportType 23 | Name string 24 | } 25 | 26 | func NewServer(os ...ServerOptions) Server { 27 | opts := options{ 28 | TransportType: TCP, 29 | } 30 | for _, option := range os { 31 | opts = option(opts) 32 | } 33 | return Server{ 34 | options: opts, 35 | isAlive: true, 36 | } 37 | } 38 | 39 | func MaxConnection(n int) ServerOptions { 40 | return func(o options) options { 41 | o.MaxConnection = n 42 | return o 43 | } 44 | } 45 | 46 | func ServerName(n string) ServerOptions { 47 | return func(o options) options { 48 | o.Name = n 49 | return o 50 | } 51 | } 52 | 53 | func Transport(t TransportType) ServerOptions { 54 | return func(o options) options { 55 | o.TransportType = t 56 | return o 57 | } 58 | } 59 | func main() { 60 | server := NewServer(MaxConnection(10), ServerName("MyFirstServer")) 61 | fmt.Printf("%+v\n", server) 62 | } 63 | -------------------------------------------------------------------------------- /Chapter3/FunctionCurrying/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/DylanMeeus/Book-FP/Chapter3/FunctionCurrying 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /Chapter3/FunctionCurrying/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // type aliases 4 | type ( 5 | Name string 6 | Breed int 7 | Gender int 8 | NameToDogFunc func(Name) Dog 9 | ) 10 | 11 | // define possible breeds 12 | const ( 13 | Bulldog Breed = iota 14 | Havanese 15 | Cavalier 16 | Poodle 17 | ) 18 | 19 | // define possible genders 20 | const ( 21 | Male Gender = iota 22 | Female 23 | ) 24 | 25 | var ( 26 | maleHavaneseSpawner = DogSpawner(Havanese, Male) 27 | femalePoodleSpawner = DogSpawner(Poodle, Female) 28 | ) 29 | 30 | type Dog struct { 31 | Name Name 32 | Breed Breed 33 | Gender Gender 34 | } 35 | 36 | /* 37 | DogSpawnerCurry is a function which takes a Breed as input, 38 | and returns a function which takes a gender as input 39 | which in turn returns a function which takes a name as input. 40 | 41 | the final output is a Dog 42 | */ 43 | func DogSpawnerCurry(breed Breed) func(Gender) NameToDogFunc { 44 | return func(gender Gender) NameToDogFunc { 45 | return func(name Name) Dog { 46 | return Dog{ 47 | Breed: breed, 48 | Gender: gender, 49 | Name: name, 50 | } 51 | } 52 | } 53 | } 54 | 55 | func DogSpawner(breed Breed, gender Gender) NameToDogFunc { 56 | return func(n Name) Dog { 57 | return Dog{ 58 | Breed: breed, 59 | Gender: gender, 60 | Name: n, 61 | } 62 | } 63 | } 64 | 65 | func main() { 66 | bucky := DogSpawnerCurry(Havanese)(Male)("Bucky") 67 | 68 | havaneseSpawner := DogSpawnerCurry(Havanese) 69 | rocky := havaneseSpawner(Male)("Rocky") 70 | 71 | femaleHavanese := havaneseSpawner(Female) 72 | lola := femaleHavanese("Lola") 73 | dotty := femaleHavanese("Dotty") 74 | 75 | rocky := maleHavaneseSpawner("rocky") 76 | tipsy := femalePoodleSpawner("tipsy") 77 | 78 | } 79 | 80 | func createDogsWithoutPartialApplication() { 81 | bucky := Dog{ 82 | Name: "Bucky", 83 | Breed: Havanese, 84 | Gender: Male, 85 | } 86 | 87 | rocky := Dog{ 88 | Name: "Rocky", 89 | Breed: Havanese, 90 | Gender: Male, 91 | } 92 | 93 | tipsy := Dog{ 94 | Name: "Tipsy", 95 | Breed: Poodle, 96 | Gender: Female, 97 | } 98 | _ = bucky 99 | _ = rocky 100 | _ = tipsy 101 | } 102 | -------------------------------------------------------------------------------- /Chapter3/PartialApplication/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/DylanMeeus/Book-FP/Chapter3/PartialApplication 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /Chapter3/PartialApplication/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | // type aliases 6 | type ( 7 | Name string 8 | Breed int 9 | Gender int 10 | NameToDogFunc func(Name) Dog 11 | ) 12 | 13 | // define possible breeds 14 | const ( 15 | Bulldog Breed = iota 16 | Havanese 17 | Cavalier 18 | Poodle 19 | ) 20 | 21 | // define possible genders 22 | const ( 23 | Male Gender = iota 24 | Female 25 | ) 26 | 27 | var ( 28 | maleHavaneseSpawner = DogSpawner(Havanese, Male) 29 | femalePoodleSpawner = DogSpawner(Poodle, Female) 30 | ) 31 | 32 | type Dog struct { 33 | Name Name 34 | Breed Breed 35 | Gender Gender 36 | } 37 | 38 | func DogSpawner(breed Breed, gender Gender) NameToDogFunc { 39 | return func(n Name) Dog { 40 | return Dog{ 41 | Breed: breed, 42 | Gender: gender, 43 | Name: n, 44 | } 45 | } 46 | } 47 | 48 | func main() { 49 | bucky := maleHavaneseSpawner("bucky") 50 | rocky := maleHavaneseSpawner("rocky") 51 | tipsy := femalePoodleSpawner("tipsy") 52 | 53 | fmt.Printf("%v\n", bucky) 54 | fmt.Printf("%v\n", rocky) 55 | fmt.Printf("%v\n", tipsy) 56 | } 57 | 58 | func createDogsWithoutPartialApplication() { 59 | bucky := Dog{ 60 | Name: "Bucky", 61 | Breed: Havanese, 62 | Gender: Male, 63 | } 64 | 65 | rocky := Dog{ 66 | Name: "Rocky", 67 | Breed: Havanese, 68 | Gender: Male, 69 | } 70 | 71 | tipsy := Dog{ 72 | Name: "Tipsy", 73 | Breed: Poodle, 74 | Gender: Female, 75 | } 76 | _ = bucky 77 | _ = rocky 78 | _ = tipsy 79 | } 80 | -------------------------------------------------------------------------------- /Chapter4/Examples/HotdogShop/ImpureHotdogShop/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/PacktPublishing/Chapter4/Examples/HotdogShop/ImpureHotdogShop 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /Chapter4/Examples/HotdogShop/ImpureHotdogShop/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | func main() {} 4 | -------------------------------------------------------------------------------- /Chapter4/Examples/HotdogShop/ImpureHotdogShop/pkg/hotdogshop.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | const ( 4 | HOTDOG_PRICE = 4 5 | ) 6 | 7 | type CreditCard struct { 8 | credit int 9 | } 10 | 11 | type Hotdog struct{} 12 | 13 | func (c *CreditCard) charge(amount int) { 14 | if amount <= c.credit { 15 | c.credit -= amount 16 | } else { 17 | panic("no more credit") 18 | } 19 | } 20 | 21 | func orderHotdog(c CreditCard) Hotdog { 22 | c.charge(HOTDOG_PRICE) 23 | return Hotdog{} 24 | } 25 | -------------------------------------------------------------------------------- /Chapter4/Examples/HotdogShop/PureHotdogShop/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/PacktPublishing/Chapter4/Examples/HotdogShop/PureHotdogShop 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /Chapter4/Examples/HotdogShop/PureHotdogShop/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/PacktPublishing/Chapter4/Examples/HotdogShop/PureHotdogShop/pkg" 7 | ) 8 | 9 | func main() { 10 | myCard := pkg.NewCreditCard(1000) 11 | hotdog, creditFunc := pkg.OrderHotdog(myCard, pkg.Charge) 12 | fmt.Printf("%+v\n", hotdog) 13 | newCard, err := creditFunc() 14 | if err != nil { 15 | panic("User has no credit") 16 | } 17 | myCard = newCard 18 | fmt.Printf("%+v\n", myCard) 19 | } 20 | -------------------------------------------------------------------------------- /Chapter4/Examples/HotdogShop/PureHotdogShop/pkg/hotdogshop.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import "errors" 4 | 5 | type CreditCard struct { 6 | credit int 7 | } 8 | 9 | type Hotdog struct { 10 | price int 11 | } 12 | 13 | type CreditError error 14 | 15 | type PaymentFunc func(CreditCard, int) (CreditCard, CreditError) 16 | 17 | var ( 18 | NOT_ENOUGH_CREDIT CreditError = CreditError(errors.New("not enough credit")) 19 | ) 20 | 21 | func NewCreditCard(initialCredit int) CreditCard { 22 | return CreditCard{credit: initialCredit} 23 | } 24 | 25 | func NewHotdog() Hotdog { 26 | return Hotdog{price: 4} 27 | } 28 | 29 | func Charge(c CreditCard, amount int) (CreditCard, CreditError) { 30 | if amount <= c.credit { 31 | c.credit -= amount 32 | return c, nil 33 | } 34 | return c, NOT_ENOUGH_CREDIT 35 | } 36 | 37 | func OrderHotdog(c CreditCard, pay PaymentFunc) (Hotdog, func() (CreditCard, error)) { 38 | hotdog := NewHotdog() 39 | chargeFunc := func() (CreditCard, error) { 40 | return pay(c, hotdog.price) 41 | } 42 | return hotdog, chargeFunc 43 | } 44 | -------------------------------------------------------------------------------- /Chapter4/Examples/HotdogShop/PureHotdogShop/pkg/hotdogshop_test.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | ) 7 | 8 | var ( 9 | testChargeStruct = []struct { 10 | inputCard CreditCard 11 | amount int 12 | outputCard CreditCard 13 | err CreditError 14 | }{ 15 | { 16 | CreditCard{1000}, 17 | 500, 18 | CreditCard{500}, 19 | nil, 20 | }, 21 | { 22 | CreditCard{20}, 23 | 20, 24 | CreditCard{0}, 25 | nil, 26 | }, 27 | { 28 | CreditCard{150}, 29 | 1000, 30 | CreditCard{150}, // no money is withdrawn 31 | NOT_ENOUGH_CREDIT, // payment fails with this error 32 | }, 33 | } 34 | ) 35 | 36 | func TestCharge(t *testing.T) { 37 | for _, test := range testChargeStruct { 38 | t.Run("", func(t *testing.T) { 39 | output, err := Charge(test.inputCard, test.amount) 40 | if output != test.outputCard || !errors.Is(err, test.err) { 41 | t.Errorf("expected %v but got %v\n, error expected %v but got %v", 42 | test.outputCard, output, test.err, err) 43 | } 44 | }) 45 | } 46 | } 47 | 48 | func TestOrderHotdog(t *testing.T) { 49 | testCC := CreditCard{1000} 50 | calledInnerFunction := false 51 | mockPayment := func(c CreditCard, input int) (CreditCard, CreditError) { 52 | calledInnerFunction = true 53 | testCC.credit -= input 54 | return testCC, nil 55 | } 56 | 57 | hotdog, resultF := OrderHotdog(testCC, mockPayment) 58 | if hotdog != NewHotdog() { 59 | t.Errorf("expected %v but got %v\n", NewHotdog(), hotdog) 60 | } 61 | 62 | _, err := resultF() 63 | if err != nil { 64 | t.Errorf("encountered %v but expected no error\n", err) 65 | } 66 | if calledInnerFunction == false { 67 | t.Errorf("Inner function did not get called\n") 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /Chapter4/ReferentialTransparency/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/PacktPublishing/Chapter4/RefentialTransparency 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /Chapter4/TestableCode/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/PacktPublishing/Chapter4/TestableCode 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /Chapter4/TestableCode/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "math/rand" 5 | 6 | "github.com/PacktPublishing/Chapter4/TestableCode/player" 7 | ) 8 | 9 | func main() { 10 | random := rand.Intn(2) 11 | player.PlayerSelectPure(random) 12 | // start the game 13 | } 14 | -------------------------------------------------------------------------------- /Chapter4/TestableCode/player/player.go: -------------------------------------------------------------------------------- 1 | package player 2 | 3 | import "fmt" 4 | 5 | type Player string 6 | 7 | const ( 8 | PlayerOne Player = "Remi" 9 | PlayerTwo Player = "Yvonne" 10 | ) 11 | 12 | func PlayerSelectPure(i int) (Player, error) { 13 | switch i { 14 | case 0: 15 | return PlayerOne, nil 16 | case 1: 17 | return PlayerTwo, nil 18 | } 19 | return Player(""), fmt.Errorf("no player matching input %v", i) 20 | } 21 | -------------------------------------------------------------------------------- /Chapter4/TestableCode/player/player_test.go: -------------------------------------------------------------------------------- 1 | package player 2 | 3 | import "testing" 4 | 5 | func TestPlayerSelectionPure(t *testing.T) { 6 | selectPlayerOne, err := PlayerSelectPure(0) 7 | if selectPlayerOne != PlayerOne || err != nil { 8 | t.Errorf("expected %v but got %v\n", PlayerOne, selectPlayerOne) 9 | } 10 | 11 | selectPlayerTwo, err := PlayerSelectPure(1) 12 | if selectPlayerTwo != PlayerTwo || err != nil { 13 | t.Errorf("expected %v but got %v\n", PlayerOne, selectPlayerTwo) 14 | } 15 | 16 | _, err = PlayerSelectPure(2) 17 | if err == nil { 18 | t.Error("Expected error but received nil") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Chapter5/Benchmark/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/PacktPublishing/Chapter5/Benchmark 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /Chapter5/Benchmark/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func main() { 6 | fmt.Println("vim-go") 7 | } 8 | -------------------------------------------------------------------------------- /Chapter5/Benchmark/pkg/person.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | type Person struct { 4 | name string 5 | age int 6 | } 7 | 8 | //go:noinline 9 | func immutableCreatePerson() Person { 10 | p := Person{} 11 | p = immutableSetName(p, "Sean") 12 | p = immutableSetAge(p, 29) 13 | return p 14 | } 15 | 16 | //go:noinline 17 | func immutableSetName(p Person, name string) Person { 18 | p.name = name 19 | return p 20 | } 21 | 22 | //go:noinline 23 | func immutableSetAge(p Person, age int) Person { 24 | p.age = age 25 | return p 26 | } 27 | 28 | //go:noinline 29 | func mutableCreatePerson() *Person { 30 | p := &Person{} 31 | mutableSetName(p, "Tom") 32 | mutableSetAge(p, 31) 33 | return p 34 | } 35 | 36 | //go:noinline 37 | func mutableSetName(p *Person, name string) { 38 | p.name = name 39 | } 40 | 41 | //go:noinline 42 | func mutableSetAge(p *Person, age int) { 43 | p.age = age 44 | } 45 | -------------------------------------------------------------------------------- /Chapter5/Benchmark/pkg/person_test.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import "testing" 4 | 5 | func BenchmarkImmutablePerson(b *testing.B) { 6 | for n := 0; n < b.N; n++ { 7 | immutableCreatePerson() 8 | } 9 | } 10 | 11 | func BenchmarkMutablePerson(b *testing.B) { 12 | for n := 0; n < b.N; n++ { 13 | mutableCreatePerson() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Chapter5/Monads/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/PacktPublishing/Chapter5/Monads 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /Chapter5/Monads/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "github.com/PacktPublishing/Chapter5/Monads/pkg" 4 | 5 | func main() { 6 | pkg.Just(1) 7 | } 8 | 9 | func getFromMap(m map[string]int, key string) pkg.Maybe[int] { 10 | if value, ok := m[key]; ok { 11 | return pkg.Just[int](value) 12 | } else { 13 | return pkg.Nothing[int]() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Chapter5/Monads/pkg/maybe.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import "errors" 4 | 5 | type Maybe[A any] interface { 6 | Get() (A, error) 7 | GetOrElse(def A) A // gets A, or returns a default value 8 | IsPresent() bool 9 | } 10 | 11 | type JustMaybe[A any] struct { 12 | value A 13 | } 14 | 15 | type NothingMaybe[A any] struct{} 16 | 17 | func (j JustMaybe[A]) Get() (A, error) { 18 | return j.value, nil 19 | } 20 | 21 | func (j JustMaybe[A]) GetOrElse(def A) A { 22 | return j.value 23 | } 24 | func (j JustMaybe[A]) IsPresent() bool { 25 | return true 26 | } 27 | 28 | func Just[A any](a A) JustMaybe[A] { 29 | return JustMaybe[A]{value: a} 30 | } 31 | 32 | func Nothing[A any]() Maybe[A] { 33 | return NothingMaybe[A]{} 34 | } 35 | 36 | func (n NothingMaybe[A]) Get() (A, error) { 37 | return *new(A), errors.New("no value") 38 | } 39 | 40 | func (n NothingMaybe[A]) GetOrElse(def A) A { 41 | return def 42 | } 43 | 44 | func (n NothingMaybe[A]) IsPresent() bool { 45 | return false 46 | } 47 | 48 | func fmap[A, B any](m Maybe[A], mapFunc func(A) B) Maybe[B] { 49 | switch m.(type) { 50 | case JustMaybe[A]: 51 | j := m.(JustMaybe[A]) 52 | return JustMaybe[B]{ 53 | value: mapFunc(j.value), 54 | } 55 | case NothingMaybe[A]: 56 | return NothingMaybe[B]{} 57 | default: 58 | panic("unknown type") 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /Chapter6/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/PacktPublishing/Chapter6 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /Chapter6/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "io/ioutil" 7 | 8 | "github.com/PacktPublishing/Chapter6/pkg" 9 | ) 10 | 11 | type ( 12 | Name string 13 | Breed int 14 | Gender int 15 | ) 16 | 17 | type Dog struct { 18 | Name Name 19 | Breed Breed 20 | Gender Gender 21 | } 22 | 23 | // define possible breeds 24 | const ( 25 | Bulldog Breed = iota 26 | Havanese 27 | Cavalier 28 | Poodle 29 | ) 30 | 31 | // define possible genders 32 | const ( 33 | Male Gender = iota 34 | Female 35 | ) 36 | 37 | func main() { 38 | dogs := []Dog{ 39 | Dog{"Bucky", Havanese, Male}, 40 | Dog{"Tipsy", Poodle, Female}, 41 | } 42 | result := pkg.Filter(dogs, func(d Dog) bool { 43 | return d.Breed == Havanese 44 | }) 45 | fmt.Printf("%v\n", result) 46 | 47 | takeWhileDemo() 48 | dropWhileDemo() 49 | multiplyMapDemo() 50 | dogMapDemo() 51 | flatMapDemo() 52 | sumDemo() 53 | reduceWithStartDemo() 54 | 55 | exampleAirlineData() 56 | } 57 | 58 | func takeWhileDemo() { 59 | ints := []int{1, 1, 2, 3, 5, 8, 13} 60 | result := pkg.TakeWhile(ints, func(i int) bool { 61 | return i%2 != 0 62 | }) 63 | fmt.Printf("%v\n", result) 64 | } 65 | 66 | func dropWhileDemo() { 67 | ints := []int{1, 1, 2, 3, 5, 8, 13} 68 | result := pkg.DropWhile(ints, func(i int) bool { 69 | return i%2 != 0 70 | }) 71 | fmt.Printf("%v\n", result) 72 | } 73 | 74 | func multiplyMapDemo() { 75 | ints := []int{1, 1, 2, 3, 5, 8, 13} 76 | result := pkg.Map(ints, func(i int) int { 77 | return i * 2 78 | }) 79 | fmt.Printf("%v\n", result) 80 | } 81 | 82 | func dogMapDemo() { 83 | dogs := []Dog{ 84 | Dog{"Bucky", Havanese, Male}, 85 | Dog{"Tipsy", Poodle, Female}, 86 | } 87 | result := pkg.Map(dogs, func(d Dog) Dog { 88 | if d.Gender == Male { 89 | d.Name = "Mr. " + d.Name 90 | } else { 91 | d.Name = "Mrs. " + d.Name 92 | } 93 | return d 94 | }) 95 | fmt.Printf("%v\n", result) 96 | } 97 | 98 | func flatMapDemo() { 99 | ints := []int{1, 2, 3} 100 | result := pkg.FlatMap(ints, func(n int) []int { 101 | out := []int{} 102 | for i := 0; i < n; i++ { 103 | out = append(out, i) 104 | } 105 | return out 106 | }) 107 | fmt.Printf("%v\n", result) 108 | } 109 | 110 | func sumDemo() { 111 | ints := []int{1, 2, 3, 4} 112 | result := pkg.Sum(ints) 113 | fmt.Printf("%v\n", result) 114 | } 115 | 116 | func reduceWithStartDemo() { 117 | words := []string{"hello", "world", "universe"} 118 | result := pkg.ReduceWithStart(words, "first", func(s1, s2 string) string { 119 | return s1 + ", " + s2 120 | }) 121 | fmt.Printf("%v\n", result) 122 | } 123 | 124 | type Entry struct { 125 | Airport struct { 126 | Code string `json:"Code"` 127 | Name string `json:"Name"` 128 | } `json:"Airport"` 129 | 130 | // time 131 | Time struct { 132 | Label string `json:"Label"` 133 | Month uint `json:"Month"` 134 | MonthName string `json:"Month Name"` 135 | Year uint `json:"Year"` 136 | } `json:"Time"` 137 | 138 | // statistics 139 | Statistics struct { 140 | NumberOfDelays struct { 141 | Carrier int `json:"Carrier"` 142 | LateAircraft int `json:"Late Aircraft"` 143 | NationalAviationSystem int `json:"National Aviation System"` 144 | Security int `json:"Security"` 145 | Weather int `json:"Weather"` 146 | } `json:"# of Delays"` 147 | // Carriers 148 | Carriers struct { 149 | Names string `json:"Names"` 150 | Total int `json:"Total"` 151 | } `json:"Carriers"` 152 | 153 | // Flights 154 | Flights struct { 155 | Cancelled int `json:"Cancelled"` 156 | Delayed int `json:"Delayed"` 157 | Diverted int `json:"Diverted"` 158 | OnTime int `json:"On Time"` 159 | Total int `json:"Total"` 160 | } `json:"Flights"` 161 | 162 | MinutesDelayed struct { 163 | Carrier int `json:"Carrier"` 164 | LateAircraft int `json:"Late Aircraft"` 165 | NationalAviationSystem int `json:"National Aviation System"` 166 | Security int `json:"Security"` 167 | Weather int `json:"Weather"` 168 | } `json:"Minutes Delayed"` 169 | } `json:"Statistics"` 170 | } 171 | 172 | func getEntries() []Entry { 173 | bytes, err := ioutil.ReadFile("./resources/airlines.json") 174 | if err != nil { 175 | panic(err) 176 | } 177 | 178 | var entries []Entry 179 | err = json.Unmarshal(bytes, &entries) 180 | if err != nil { 181 | panic(err) 182 | } 183 | return entries 184 | } 185 | 186 | func exampleAirlineData() { 187 | entries := getEntries() 188 | SEA := pkg.Filter(entries, func(e Entry) bool { 189 | return e.Airport.Code == "SEA" 190 | }) 191 | 192 | WeatherDelayHours := pkg.FMap(SEA, func(e Entry) int { 193 | return e.Statistics.MinutesDelayed.Weather / 60 194 | }) 195 | 196 | totalWeatherDelay := pkg.Sum(WeatherDelayHours) 197 | 198 | fmt.Printf("%v\n", totalWeatherDelay) 199 | 200 | } 201 | -------------------------------------------------------------------------------- /Chapter6/pkg/maps.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | type MapFunc[A any] func(A) A 4 | 5 | // Map is a transformation whereby the type of the element and structure of the container is preserved 6 | func Map[A any](input []A, m MapFunc[A]) []A { 7 | output := make([]A, len(input)) 8 | 9 | for i, element := range input { 10 | output[i] = m(element) 11 | } 12 | 13 | return output 14 | } 15 | 16 | // F stands for "Functor", not "Flat", it maps between types and maintains structure of the 17 | // container 18 | func FMap[A, B any](input []A, m func(A) B) []B { 19 | output := make([]B, len(input)) 20 | for i, element := range input { 21 | output[i] = m(element) 22 | } 23 | return output 24 | } 25 | 26 | // First maps a single element to a slice of elements (new container structure) 27 | // then flattens the slice by a single level (same container structure as input) 28 | func FlatMap[A any](input []A, m func(A) []A) []A { 29 | output := []A{} 30 | 31 | for _, element := range input { 32 | newElements := m(element) 33 | for _, newEl := range newElements { 34 | output = append(output, newEl) 35 | } 36 | } 37 | 38 | return output 39 | } 40 | -------------------------------------------------------------------------------- /Chapter6/pkg/predicates.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | /* 4 | 5 | this package contains predicate based functions 6 | 7 | */ 8 | 9 | type Predicate[A any] func(A) bool 10 | 11 | func Filter[A any](input []A, pred Predicate[A]) []A { 12 | output := []A{} 13 | for _, element := range input { 14 | if pred(element) { 15 | output = append(output, element) 16 | } 17 | } 18 | 19 | return output 20 | } 21 | 22 | func Any[A any](input []A, pred Predicate[A]) bool { 23 | for _, element := range input { 24 | if pred(element) { 25 | return true 26 | } 27 | } 28 | return false 29 | } 30 | 31 | func All[A any](input []A, pred Predicate[A]) bool { 32 | for _, element := range input { 33 | if !pred(element) { 34 | return false 35 | } 36 | } 37 | return true 38 | } 39 | 40 | func DropWhile[A any](input []A, pred Predicate[A]) []A { 41 | out := []A{} 42 | drop := true 43 | for _, element := range input { 44 | if !pred(element) { 45 | drop = false 46 | } 47 | if !drop { 48 | out = append(out, element) 49 | } 50 | } 51 | return out 52 | } 53 | 54 | func TakeWhile[A any](input []A, pred Predicate[A]) []A { 55 | out := []A{} 56 | for _, element := range input { 57 | if pred(element) { 58 | out = append(out, element) 59 | } else { 60 | return out 61 | } 62 | } 63 | return out 64 | } 65 | -------------------------------------------------------------------------------- /Chapter6/pkg/reducers.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | type Number interface { 4 | uint8 | uint16 | uint32 | uint64 | uint | 5 | int8 | int16 | int32 | int64 | int | 6 | float32 | float64 7 | } 8 | 9 | type ( 10 | reduceFunc[A any] func(a1, a2 A) A 11 | ) 12 | 13 | // Reduce iteratively applies a combining function to the elements of the slice 14 | func Reduce[A any](input []A, reducer reduceFunc[A]) A { 15 | if len(input) == 0 { 16 | // return default zero 17 | return *new(A) 18 | } 19 | if len(input) == 1 { 20 | return input[0] 21 | } 22 | 23 | result := input[0] 24 | 25 | for _, element := range input[1:] { 26 | result = reducer(result, element) 27 | } 28 | 29 | return result 30 | } 31 | 32 | func ReduceRight[A any](input []A, reducer reduceFunc[A]) A { 33 | if len(input) == 0 { 34 | // return default zero 35 | return *new(A) 36 | } 37 | if len(input) == 1 { 38 | return input[0] 39 | } 40 | 41 | result := input[len(input)-1] 42 | 43 | for i := len(input) - 2; i > 0; i-- { 44 | result = reducer(result, input[i]) 45 | } 46 | 47 | return result 48 | } 49 | 50 | func ReduceWithStart[A any](input []A, startValue A, reducer reduceFunc[A]) A { 51 | if len(input) == 0 { 52 | return startValue 53 | } 54 | if len(input) == 1 { 55 | return reducer(startValue, input[0]) 56 | } 57 | result := reducer(startValue, input[0]) 58 | for _, element := range input[1:] { 59 | result = reducer(result, element) 60 | } 61 | return result 62 | } 63 | 64 | func ReduceWithStartRight[A any](input []A, startValue A, reducer reduceFunc[A]) A { 65 | if len(input) == 0 { 66 | return startValue 67 | } 68 | if len(input) == 1 { 69 | return reducer(startValue, input[0]) 70 | } 71 | result := reducer(startValue, input[len(input)-1]) 72 | for i := len(input) - 2; i > 0; i-- { 73 | result = reducer(result, input[i]) 74 | } 75 | return result 76 | } 77 | 78 | func Sum[A Number](input []A) A { 79 | return Reduce(input, func(a1, a2 A) A { return a1 + a2 }) 80 | } 81 | 82 | func Prod[A Number](input []A) A { 83 | return Reduce(input, func(a1, a2 A) A { return a1 * a2 }) 84 | } 85 | -------------------------------------------------------------------------------- /Chapter7/Examples/Performance/Performance: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PacktPublishing/Functional-Programming-in-Go./251f03b4aba12177c34a9dcc1c703cb8705581b0/Chapter7/Examples/Performance/Performance -------------------------------------------------------------------------------- /Chapter7/Examples/Performance/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/PacktPublishing/Chapter7/Examples/Performance 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /Chapter7/Examples/Performance/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/PacktPublishing/Chapter7/Examples/Performance/pkg" 7 | ) 8 | 9 | func main() { 10 | fmt.Println(pkg.RecursiveFact(10)) 11 | //debug.SetMaxStack(100) 12 | stackOverflowExample(0) 13 | } 14 | 15 | 16 | -------------------------------------------------------------------------------- /Chapter7/Examples/Performance/pkg/performance.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | func IterativeFact(n int) int { 4 | result := 1 5 | for i := 2; i <= n; i++ { 6 | result *= i 7 | } 8 | return result 9 | } 10 | 11 | func RecursiveFact(n int) int { 12 | if n == 0 { 13 | return 1 14 | } 15 | return n * RecursiveFact(n-1) 16 | } 17 | -------------------------------------------------------------------------------- /Chapter7/Examples/Performance/pkg/performance_test.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import "testing" 4 | 5 | const RUNS = 20 6 | 7 | func BenchmarkIterative(b *testing.B) { 8 | for n := 0; n < b.N; n++ { 9 | IterativeFact(RUNS) 10 | } 11 | } 12 | 13 | func BenchmarkRecursive(b *testing.B) { 14 | for n := 0; n < b.N; n++ { 15 | RecursiveFact(RUNS) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Chapter7/Examples/RecursionFirstClassCitizens/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/PacktPublishing/Chapter7/Examples/RecursionFirstClassCitizens 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /Chapter7/Examples/RecursionFirstClassCitizens/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type city uint 6 | 7 | var ( 8 | Brussels city = iota 9 | Paris city 10 | London city 11 | Glasgow city 12 | Madrid city 13 | ) 14 | 15 | var ( 16 | railroads = [][]city{ 17 | Brussels: []city{Paris}, 18 | Paris: []city{Brussels, London, Madrid}, 19 | London: []city{Paris, Glasgow}, 20 | } 21 | ) 22 | 23 | func main() { 24 | fmt.Println("hello world") 25 | } 26 | -------------------------------------------------------------------------------- /Chapter7/Examples/Trees/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/PacktPublishing/Chapter7/Examples/Trees 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /Chapter7/Examples/Trees/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "runtime/debug" 6 | ) 7 | 8 | type node struct { 9 | value int 10 | left *node 11 | right *node 12 | } 13 | 14 | var ( 15 | ExampleTree = &node{ 16 | value: 1, 17 | left: &node{ 18 | value: 2, 19 | left: &node{ 20 | value: 3, 21 | }, 22 | right: &node{ 23 | value: 4, 24 | }, 25 | }, 26 | right: &node{ 27 | value: 5, 28 | }, 29 | } 30 | ) 31 | 32 | func main() { 33 | fmt.Println(sumIterative(ExampleTree)) 34 | fmt.Println(sumRecursive(ExampleTree)) 35 | fmt.Println(max(ExampleTree)) 36 | fmt.Println(MaxInline(ExampleTree)) 37 | 38 | debug.SetMaxStack(262144000 * 2) // twice the 32-bit limit 39 | infiniteCount(0) 40 | } 41 | 42 | func infiniteCount(i int) { 43 | if i%1000 == 0 { 44 | fmt.Println(i) 45 | } 46 | infiniteCount(i + 1) 47 | } 48 | 49 | func sumRecursive(node *node) int { 50 | if node == nil { 51 | return 0 52 | } 53 | return node.value + sumRecursive(node.left) + sumRecursive(node.right) 54 | } 55 | 56 | func sumIterative(root *node) int { 57 | // not an ideal queue, but acts like a queue 58 | queue := make(chan *node, 10) 59 | queue <- root 60 | var sum int 61 | for { 62 | select { 63 | case node := <-queue: 64 | sum += node.value 65 | if node.left != nil { 66 | queue <- node.left 67 | } 68 | if node.right != nil { 69 | queue <- node.right 70 | } 71 | default: 72 | return sum 73 | } 74 | } 75 | return sum 76 | } 77 | 78 | func max(root *node) int { 79 | currentMax := 0 80 | 81 | var inner func(node *node) 82 | inner = func(node *node) { 83 | if node == nil { 84 | return 85 | } 86 | if node.value > currentMax { 87 | currentMax = node.value 88 | } 89 | inner(node.left) 90 | inner(node.right) 91 | } 92 | 93 | inner(root) 94 | 95 | return currentMax 96 | } 97 | 98 | func MaxInline(root *node) int { 99 | return maxInline(root, 0) 100 | } 101 | 102 | func maxInline(node *node, maxValue int) int { 103 | if node == nil { 104 | return maxValue 105 | } 106 | 107 | if node.value > maxValue { 108 | maxValue = node.value 109 | } 110 | 111 | maxLeft := maxInline(node.left, maxValue) 112 | maxRight := maxInline(node.right, maxValue) 113 | if maxLeft > maxRight { 114 | return maxLeft 115 | } 116 | return maxRight 117 | } 118 | -------------------------------------------------------------------------------- /Chapter8/BuilderExample/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/PacktPublishing/Chapter8/BuilderExample 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /Chapter8/BuilderExample/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type person struct { 6 | firstName string 7 | lastName string 8 | age int 9 | } 10 | 11 | func newPerson() *person { 12 | return &person{} 13 | } 14 | 15 | func (p *person) SetFirstName(firstName string) { 16 | p.firstName = firstName 17 | } 18 | 19 | func (p *person) SetLastName(lastName string) { 20 | p.lastName = lastName 21 | } 22 | 23 | func (p *person) SetAge(age int) { 24 | p.age = age 25 | } 26 | 27 | func constructor(firstName, lastName string, age int) person { 28 | return person{firstName, lastName, age} 29 | } 30 | 31 | type personBuilder struct { 32 | person 33 | } 34 | 35 | func (pb personBuilder) FirstName(firstName string) personBuilder { 36 | pb.person.firstName = firstName 37 | return pb 38 | } 39 | func (pb personBuilder) LastName(lastName string) personBuilder { 40 | pb.person.lastName = lastName 41 | return pb 42 | } 43 | func (pb personBuilder) Age(age int) personBuilder { 44 | pb.person.age = age 45 | return pb 46 | } 47 | 48 | func (pb personBuilder) Build() person { 49 | return pb.person 50 | } 51 | 52 | func main() { 53 | alice := newPerson() 54 | alice.SetFirstName("alice") 55 | alice.SetLastName("elvi") 56 | alice.SetAge(30) 57 | fmt.Println(alice) 58 | 59 | bob := personBuilder{}.FirstName("bob").LastName("doog").Age(20).Build() 60 | fmt.Println(bob) 61 | } 62 | -------------------------------------------------------------------------------- /Chapter8/CPS/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/PacktPublishing/Chapter8/CPS 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /Chapter8/CPS/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | func factorial(n int, cont func(int)) { 6 | if n == 0 { 7 | cont(1) 8 | } else { 9 | factorial(n-1, func(v int) { 10 | cont(n * v) 11 | }) 12 | } 13 | } 14 | 15 | func main() { 16 | factorial(5, func(v int) { 17 | fmt.Println(v) 18 | }) 19 | 20 | callback := func(input int, b bool) { 21 | if b { 22 | fmt.Printf("the number %v is even\n", input) 23 | } else { 24 | fmt.Printf("the number %v is odd\n", input) 25 | } 26 | } 27 | 28 | for i := 0; i < 10; i++ { 29 | go isEven(i, callback) 30 | } 31 | _ := <-make(chan int) 32 | } 33 | 34 | func isEven(i int, callback func(int, bool)) { 35 | if i%2 == 0 { 36 | callback(i, true) 37 | } else { 38 | callback(i, false) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Chapter8/CodeGenerator/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/PacktPublishing/Chapter8/CodeGenerator 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /Chapter8/FunctionChaining/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/PacktPublishing/Chapter8/FunctionChaining 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /Chapter8/FunctionChaining/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/PacktPublishing/Chapter8/FunctionChaining/pkg" 7 | ) 8 | 9 | /* 10 | func main() { 11 | ints := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} 12 | 13 | doubled := pkg.Map(ints, func(i int) int { return i * 2 }) 14 | larger10 := pkg.Filter(doubled, func(i int) bool { return i >= 10 }) 15 | sum := pkg.Sum(larger10) 16 | 17 | fmt.Println(sum) 18 | 19 | fmt.Println(chaining()) 20 | 21 | } 22 | */ 23 | 24 | func oneliner() { 25 | ints := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10} 26 | sum := pkg.Sum( 27 | pkg.Filter( 28 | pkg.Map(ints, 29 | func(i int) int { return i * 2 }), 30 | func(i int) bool { return i >= 10 })) 31 | fmt.Println(sum) 32 | } 33 | 34 | type ints []int 35 | 36 | func (i ints) Map(f func(i int) int) ints { 37 | return pkg.Map(i, f) 38 | } 39 | 40 | func (i ints) Filter(f func(i int) bool) ints { 41 | return pkg.Filter(i, f) 42 | } 43 | 44 | func (i ints) Sum() int { 45 | return pkg.Sum(i) 46 | } 47 | 48 | func chaining() int { 49 | input := ints([]int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}) 50 | return input.Map(func(i int) int { return i * 2 }). 51 | Filter(func(i int) bool { return i >= 10 }). 52 | Sum() 53 | } 54 | -------------------------------------------------------------------------------- /Chapter8/FunctionChaining/pkg/maps.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | type MapFunc[A any] func(A) A 4 | 5 | // Map is a transformation whereby the type of the element and structure of the container is preserved 6 | func Map[A any](input []A, m MapFunc[A]) []A { 7 | output := make([]A, len(input)) 8 | 9 | for i, element := range input { 10 | output[i] = m(element) 11 | } 12 | 13 | return output 14 | } 15 | 16 | // F stands for "Functor", not "Flat", it maps between types and maintains structure of the 17 | // container 18 | func FMap[A, B any](input []A, m func(A) B) []B { 19 | output := make([]B, len(input)) 20 | for i, element := range input { 21 | output[i] = m(element) 22 | } 23 | return output 24 | } 25 | 26 | // First maps a single element to a slice of elements (new container structure) 27 | // then flattens the slice by a single level (same container structure as input) 28 | func FlatMap[A any](input []A, m func(A) []A) []A { 29 | output := []A{} 30 | 31 | for _, element := range input { 32 | newElements := m(element) 33 | for _, newEl := range newElements { 34 | output = append(output, newEl) 35 | } 36 | } 37 | 38 | return output 39 | } 40 | -------------------------------------------------------------------------------- /Chapter8/FunctionChaining/pkg/predicates.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | /* 4 | 5 | this package contains predicate based functions 6 | 7 | */ 8 | 9 | type Predicate[A any] func(A) bool 10 | 11 | func Filter[A any](input []A, pred Predicate[A]) []A { 12 | output := []A{} 13 | for _, element := range input { 14 | if pred(element) { 15 | output = append(output, element) 16 | } 17 | } 18 | 19 | return output 20 | } 21 | 22 | func Any[A any](input []A, pred Predicate[A]) bool { 23 | for _, element := range input { 24 | if pred(element) { 25 | return true 26 | } 27 | } 28 | return false 29 | } 30 | 31 | func All[A any](input []A, pred Predicate[A]) bool { 32 | for _, element := range input { 33 | if !pred(element) { 34 | return false 35 | } 36 | } 37 | return true 38 | } 39 | 40 | func DropWhile[A any](input []A, pred Predicate[A]) []A { 41 | out := []A{} 42 | drop := true 43 | for _, element := range input { 44 | if !pred(element) { 45 | drop = false 46 | } 47 | if !drop { 48 | out = append(out, element) 49 | } 50 | } 51 | return out 52 | } 53 | 54 | func TakeWhile[A any](input []A, pred Predicate[A]) []A { 55 | out := []A{} 56 | for _, element := range input { 57 | if pred(element) { 58 | out = append(out, element) 59 | } else { 60 | return out 61 | } 62 | } 63 | return out 64 | } 65 | -------------------------------------------------------------------------------- /Chapter8/FunctionChaining/pkg/primes.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | -------------------------------------------------------------------------------- /Chapter8/FunctionChaining/pkg/reducers.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | type Number interface { 4 | uint8 | uint16 | uint32 | uint64 | uint | 5 | int8 | int16 | int32 | int64 | int | 6 | float32 | float64 7 | } 8 | 9 | type ( 10 | reduceFunc[A any] func(a1, a2 A) A 11 | ) 12 | 13 | // Reduce iteratively applies a combining function to the elements of the slice 14 | func Reduce[A any](input []A, reducer reduceFunc[A]) A { 15 | if len(input) == 0 { 16 | // return default zero 17 | return *new(A) 18 | } 19 | if len(input) == 1 { 20 | return input[0] 21 | } 22 | 23 | result := input[0] 24 | 25 | for _, element := range input[1:] { 26 | result = reducer(result, element) 27 | } 28 | 29 | return result 30 | } 31 | 32 | func ReduceRight[A any](input []A, reducer reduceFunc[A]) A { 33 | if len(input) == 0 { 34 | // return default zero 35 | return *new(A) 36 | } 37 | if len(input) == 1 { 38 | return input[0] 39 | } 40 | 41 | result := input[len(input)-1] 42 | 43 | for i := len(input) - 2; i > 0; i-- { 44 | result = reducer(result, input[i]) 45 | } 46 | 47 | return result 48 | } 49 | 50 | func ReduceWithStart[A any](input []A, startValue A, reducer reduceFunc[A]) A { 51 | if len(input) == 0 { 52 | return startValue 53 | } 54 | if len(input) == 1 { 55 | return reducer(startValue, input[0]) 56 | } 57 | result := reducer(startValue, input[0]) 58 | for _, element := range input[1:] { 59 | result = reducer(result, element) 60 | } 61 | return result 62 | } 63 | 64 | func ReduceWithStartRight[A any](input []A, startValue A, reducer reduceFunc[A]) A { 65 | if len(input) == 0 { 66 | return startValue 67 | } 68 | if len(input) == 1 { 69 | return reducer(startValue, input[0]) 70 | } 71 | result := reducer(startValue, input[len(input)-1]) 72 | for i := len(input) - 2; i > 0; i-- { 73 | result = reducer(result, input[i]) 74 | } 75 | return result 76 | } 77 | 78 | func Sum[A Number](input []A) A { 79 | return Reduce(input, func(a1, a2 A) A { return a1 + a2 }) 80 | } 81 | 82 | func Prod[A Number](input []A) A { 83 | return Reduce(input, func(a1, a2 A) A { return a1 * a2 }) 84 | } 85 | -------------------------------------------------------------------------------- /Chapter8/LazyEvaluation/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/PacktPublishing/Chapter8/LazyEvaluation 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /Chapter8/LazyEvaluation/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/PacktPublishing/Chapter8/LazyEvaluation/pkg" 7 | ) 8 | 9 | func main() { 10 | largerThan10Mil := func(i int) bool { 11 | return i > 10_000_000 12 | } 13 | res := ints(IntRange(0, 100)). 14 | Map(Factorial). 15 | Filter(largerThan10Mil). 16 | Head() 17 | 18 | fmt.Printf("%v\n", res) 19 | 20 | i := 0 21 | is := LazyTake(10, func() int { 22 | curr := i 23 | i++ 24 | return curr 25 | }) 26 | fmt.Println(is) 27 | } 28 | 29 | type ints []int 30 | 31 | func (i ints) Head() pkg.Maybe[int] { 32 | return pkg.Head(i) 33 | } 34 | 35 | func (i ints) Map(f func(i int) int) ints { 36 | return pkg.Map(i, f) 37 | } 38 | 39 | func (i ints) Filter(f func(i int) bool) ints { 40 | return pkg.Filter(i, f) 41 | } 42 | func IntRange(start, end int) []int { 43 | out := []int{} 44 | for i := start; i <= end; i++ { 45 | out = append(out, i) 46 | } 47 | return out 48 | } 49 | 50 | func Factorial(n int) int { 51 | if n == 0 { 52 | return 1 53 | } 54 | return n * Factorial(n-1) 55 | } 56 | 57 | func LazyTake[A any](n int, getNext func() A) []A { 58 | collected := []A{} 59 | 60 | for len(collected) < n { 61 | collected = append(collected, getNext()) 62 | } 63 | 64 | return collected 65 | } 66 | 67 | func isPrime(input int) bool { 68 | for i := 2; i < i; i++ { 69 | if i%input == 0 { 70 | return false 71 | } 72 | } 73 | return true 74 | } 75 | -------------------------------------------------------------------------------- /Chapter8/LazyEvaluation/pkg/maps.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | type MapFunc[A any] func(A) A 4 | 5 | // Map is a transformation whereby the type of the element and structure of the container is preserved 6 | func Map[A any](input []A, m MapFunc[A]) []A { 7 | output := make([]A, len(input)) 8 | 9 | for i, element := range input { 10 | output[i] = m(element) 11 | } 12 | 13 | return output 14 | } 15 | 16 | // F stands for "Functor", not "Flat", it maps between types and maintains structure of the 17 | // container 18 | func FMap[A, B any](input []A, m func(A) B) []B { 19 | output := make([]B, len(input)) 20 | for i, element := range input { 21 | output[i] = m(element) 22 | } 23 | return output 24 | } 25 | 26 | // First maps a single element to a slice of elements (new container structure) 27 | // then flattens the slice by a single level (same container structure as input) 28 | func FlatMap[A any](input []A, m func(A) []A) []A { 29 | output := []A{} 30 | 31 | for _, element := range input { 32 | newElements := m(element) 33 | for _, newEl := range newElements { 34 | output = append(output, newEl) 35 | } 36 | } 37 | 38 | return output 39 | } 40 | 41 | func Head[A any](input []A) Maybe[A] { 42 | if len(input) == 0 { 43 | return Nothing[A]() 44 | } 45 | return Just(input[0]) 46 | } 47 | -------------------------------------------------------------------------------- /Chapter8/LazyEvaluation/pkg/maybe.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | import "errors" 4 | 5 | type Maybe[A any] interface { 6 | Get() (A, error) 7 | GetOrElse(def A) A // gets A, or returns a default value 8 | IsPresent() bool 9 | } 10 | 11 | type JustMaybe[A any] struct { 12 | value A 13 | } 14 | 15 | type NothingMaybe[A any] struct{} 16 | 17 | func (j JustMaybe[A]) Get() (A, error) { 18 | return j.value, nil 19 | } 20 | 21 | func (j JustMaybe[A]) GetOrElse(def A) A { 22 | return j.value 23 | } 24 | func (j JustMaybe[A]) IsPresent() bool { 25 | return true 26 | } 27 | 28 | func Just[A any](a A) JustMaybe[A] { 29 | return JustMaybe[A]{value: a} 30 | } 31 | 32 | func Nothing[A any]() Maybe[A] { 33 | return NothingMaybe[A]{} 34 | } 35 | 36 | func (n NothingMaybe[A]) Get() (A, error) { 37 | return *new(A), errors.New("no value") 38 | } 39 | 40 | func (n NothingMaybe[A]) GetOrElse(def A) A { 41 | return def 42 | } 43 | 44 | func (n NothingMaybe[A]) IsPresent() bool { 45 | return false 46 | } 47 | 48 | func fmap[A, B any](m Maybe[A], mapFunc func(A) B) Maybe[B] { 49 | switch m.(type) { 50 | case JustMaybe[A]: 51 | j := m.(JustMaybe[A]) 52 | return JustMaybe[B]{ 53 | value: mapFunc(j.value), 54 | } 55 | case NothingMaybe[A]: 56 | return NothingMaybe[B]{} 57 | default: 58 | panic("unknown type") 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /Chapter8/LazyEvaluation/pkg/predicates.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | /* 4 | 5 | this package contains predicate based functions 6 | 7 | */ 8 | 9 | type Predicate[A any] func(A) bool 10 | 11 | func Filter[A any](input []A, pred Predicate[A]) []A { 12 | output := []A{} 13 | for _, element := range input { 14 | if pred(element) { 15 | output = append(output, element) 16 | } 17 | } 18 | 19 | return output 20 | } 21 | 22 | func Any[A any](input []A, pred Predicate[A]) bool { 23 | for _, element := range input { 24 | if pred(element) { 25 | return true 26 | } 27 | } 28 | return false 29 | } 30 | 31 | func All[A any](input []A, pred Predicate[A]) bool { 32 | for _, element := range input { 33 | if !pred(element) { 34 | return false 35 | } 36 | } 37 | return true 38 | } 39 | 40 | func DropWhile[A any](input []A, pred Predicate[A]) []A { 41 | out := []A{} 42 | drop := true 43 | for _, element := range input { 44 | if !pred(element) { 45 | drop = false 46 | } 47 | if !drop { 48 | out = append(out, element) 49 | } 50 | } 51 | return out 52 | } 53 | 54 | func TakeWhile[A any](input []A, pred Predicate[A]) []A { 55 | out := []A{} 56 | for _, element := range input { 57 | if pred(element) { 58 | out = append(out, element) 59 | } else { 60 | return out 61 | } 62 | } 63 | return out 64 | } 65 | -------------------------------------------------------------------------------- /Chapter8/LazyEvaluation/pkg/reducers.go: -------------------------------------------------------------------------------- 1 | package pkg 2 | 3 | type Number interface { 4 | uint8 | uint16 | uint32 | uint64 | uint | 5 | int8 | int16 | int32 | int64 | int | 6 | float32 | float64 7 | } 8 | 9 | type ( 10 | reduceFunc[A any] func(a1, a2 A) A 11 | ) 12 | 13 | // Reduce iteratively applies a combining function to the elements of the slice 14 | func Reduce[A any](input []A, reducer reduceFunc[A]) A { 15 | if len(input) == 0 { 16 | // return default zero 17 | return *new(A) 18 | } 19 | if len(input) == 1 { 20 | return input[0] 21 | } 22 | 23 | result := input[0] 24 | 25 | for _, element := range input[1:] { 26 | result = reducer(result, element) 27 | } 28 | 29 | return result 30 | } 31 | 32 | func ReduceRight[A any](input []A, reducer reduceFunc[A]) A { 33 | if len(input) == 0 { 34 | // return default zero 35 | return *new(A) 36 | } 37 | if len(input) == 1 { 38 | return input[0] 39 | } 40 | 41 | result := input[len(input)-1] 42 | 43 | for i := len(input) - 2; i > 0; i-- { 44 | result = reducer(result, input[i]) 45 | } 46 | 47 | return result 48 | } 49 | 50 | func ReduceWithStart[A any](input []A, startValue A, reducer reduceFunc[A]) A { 51 | if len(input) == 0 { 52 | return startValue 53 | } 54 | if len(input) == 1 { 55 | return reducer(startValue, input[0]) 56 | } 57 | result := reducer(startValue, input[0]) 58 | for _, element := range input[1:] { 59 | result = reducer(result, element) 60 | } 61 | return result 62 | } 63 | 64 | func ReduceWithStartRight[A any](input []A, startValue A, reducer reduceFunc[A]) A { 65 | if len(input) == 0 { 66 | return startValue 67 | } 68 | if len(input) == 1 { 69 | return reducer(startValue, input[0]) 70 | } 71 | result := reducer(startValue, input[len(input)-1]) 72 | for i := len(input) - 2; i > 0; i-- { 73 | result = reducer(result, input[i]) 74 | } 75 | return result 76 | } 77 | 78 | func Sum[A Number](input []A) A { 79 | return Reduce(input, func(a1, a2 A) A { return a1 + a2 }) 80 | } 81 | 82 | func Prod[A Number](input []A) A { 83 | return Reduce(input, func(a1, a2 A) A { return a1 * a2 }) 84 | } 85 | -------------------------------------------------------------------------------- /Chapter9/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/PacktPublishing/Chapter9 2 | 3 | go 1.18 4 | -------------------------------------------------------------------------------- /Chapter9/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/PacktPublishing/Chapter9/pkg/fp" 7 | "github.com/PacktPublishing/Chapter9/pkg/oop" 8 | ) 9 | 10 | /* 11 | func main() { 12 | svc := oop.CipherService{} 13 | svc.Strategy = oop.CaesarCipher{Rotation: 10} 14 | fmt.Println(svc.Cipher("helloworld")) 15 | svc.Strategy = oop.AtbashCipher{} 16 | fmt.Println(svc.Cipher("helloworld")) 17 | 18 | fpSvc := fp.CipherService{ 19 | CipherFn: func(input string) string { 20 | return fp.CaesarCipher(input, 10) 21 | }, 22 | DecipherFn: func(input string) string { 23 | return fp.CaesarDecipher(input, 10) 24 | }, 25 | } 26 | fmt.Println(fpSvc.Cipher("helloworld")) 27 | 28 | fpSvc.CipherFn = fp.AtbashCipher 29 | fpSvc.DecipherFn = fp.AtbashDecipher 30 | 31 | fmt.Println(fpSvc.Cipher("helloworld")) 32 | fmt.Println(fpSvc.Decipher(fpSvc.Cipher("hello"))) 33 | } 34 | */ 35 | 36 | func main() { 37 | cld := oop.CipherLogDecorator{ 38 | CipherI: oop.CaesarCipher{Rotation: 10}, 39 | } 40 | svc := oop.CipherService{Strategy: cld} 41 | ciphered := svc.Cipher("helloworld") 42 | fmt.Println(ciphered) 43 | 44 | caesarCipher := func(input string) string { 45 | return fp.CaesarCipher(input, 10) 46 | } 47 | caesarDecipher := func(input string) string { 48 | return fp.CaesarDecipher(input, 10) 49 | } 50 | fpSvc := fp.CipherService{ 51 | CipherFn: fp.LogCipher(caesarCipher), 52 | DecipherFn: fp.LogDecipher(caesarDecipher), 53 | } 54 | 55 | fmt.Println(fpSvc.Cipher("hello")) 56 | } 57 | -------------------------------------------------------------------------------- /Chapter9/pkg/fp/decorator.go: -------------------------------------------------------------------------------- 1 | package fp 2 | 3 | import "log" 4 | 5 | func LogCipher(cipher CipherFunc) CipherFunc { 6 | return func(input string) string { 7 | log.Printf("ciphering: %s\n", input) 8 | return cipher(input) 9 | } 10 | } 11 | 12 | func LogDecipher(decipher DecipherFunc) DecipherFunc { 13 | return func(input string) string { 14 | log.Printf("deciphering: %s\n", input) 15 | return decipher(input) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Chapter9/pkg/fp/strategy.go: -------------------------------------------------------------------------------- 1 | package fp 2 | 3 | var ( 4 | alphabet [26]rune = [26]rune{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'} 5 | ) 6 | 7 | func indexOf(r rune, rs [26]rune) (int, bool) { 8 | for i := 0; i < len(rs); i++ { 9 | if r == rs[i] { 10 | return i, true 11 | } 12 | } 13 | return -1, false 14 | } 15 | 16 | type ( 17 | CipherFunc func(string) string 18 | DecipherFunc func(string) string 19 | ) 20 | 21 | type CipherService struct { 22 | CipherFn CipherFunc 23 | DecipherFn DecipherFunc 24 | } 25 | 26 | func (c CipherService) Cipher(input string) string { 27 | return c.CipherFn(input) 28 | } 29 | 30 | func (c CipherService) Decipher(input string) string { 31 | return c.DecipherFn(input) 32 | } 33 | 34 | func CaesarCipher(input string, rotation int) string { 35 | output := "" 36 | for _, r := range input { 37 | if idx, ok := indexOf(r, alphabet); ok { 38 | idx += rotation 39 | idx = idx % 26 40 | output += string(alphabet[idx]) 41 | } else { 42 | output += string(r) 43 | } 44 | } 45 | return output 46 | } 47 | 48 | func CaesarDecipher(input string, rotation int) string { 49 | output := "" 50 | for _, r := range input { 51 | if idx, ok := indexOf(r, alphabet); ok { 52 | idx += (26 - rotation) 53 | idx = idx % 26 54 | output += string(alphabet[idx]) 55 | } else { 56 | output += string(r) 57 | } 58 | } 59 | return output 60 | } 61 | 62 | func AtbashCipher(input string) string { 63 | output := "" 64 | for _, r := range input { 65 | if idx, ok := indexOf(r, alphabet); ok { 66 | idx = 25 - idx 67 | output += string(alphabet[idx]) 68 | } else { 69 | output += string(r) 70 | } 71 | } 72 | return output 73 | } 74 | 75 | var AtbashDecipher = AtbashCipher 76 | -------------------------------------------------------------------------------- /Chapter9/pkg/oop/decorator.go: -------------------------------------------------------------------------------- 1 | package oop 2 | 3 | import ( 4 | "log" 5 | ) 6 | 7 | type CipherLogDecorator struct { 8 | CipherI CipherStrategy 9 | } 10 | 11 | func (c CipherLogDecorator) Cipher(input string) string { 12 | log.Printf("ciphering: %s\n", input) 13 | return c.CipherI.Cipher(input) 14 | } 15 | 16 | func (c CipherLogDecorator) Decipher(input string) string { 17 | log.Printf("deciphering: %s\n", input) 18 | return c.CipherI.Decipher(input) 19 | } 20 | -------------------------------------------------------------------------------- /Chapter9/pkg/oop/strategy.go: -------------------------------------------------------------------------------- 1 | package oop 2 | 3 | var ( 4 | alphabet [26]rune = [26]rune{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'} 5 | ) 6 | 7 | func indexOf(r rune, rs [26]rune) (int, bool) { 8 | for i := 0; i < len(rs); i++ { 9 | if r == rs[i] { 10 | return i, true 11 | } 12 | } 13 | return -1, false 14 | } 15 | 16 | type CipherStrategy interface { 17 | Cipher(string) string 18 | Decipher(string) string 19 | } 20 | 21 | type CipherService struct { 22 | Strategy CipherStrategy 23 | } 24 | 25 | func (c CipherService) Cipher(input string) string { 26 | return c.Strategy.Cipher(input) 27 | } 28 | func (c CipherService) Decipher(input string) string { 29 | return c.Strategy.Decipher(input) 30 | } 31 | 32 | type CaesarCipher struct { 33 | Rotation int 34 | } 35 | 36 | func (c CaesarCipher) Cipher(input string) string { 37 | output := "" 38 | for _, r := range input { 39 | if idx, ok := indexOf(r, alphabet); ok { 40 | idx += c.Rotation 41 | idx = idx % 26 42 | output += string(alphabet[idx]) 43 | } else { 44 | output += string(r) 45 | } 46 | } 47 | return output 48 | } 49 | 50 | func (c CaesarCipher) Decipher(input string) string { 51 | output := "" 52 | for _, r := range input { 53 | if idx, ok := indexOf(r, alphabet); ok { 54 | idx += (26 - c.Rotation) 55 | idx = idx % 26 56 | output += string(alphabet[idx]) 57 | } else { 58 | output += string(r) 59 | } 60 | } 61 | return output 62 | } 63 | 64 | type AtbashCipher struct { 65 | } 66 | 67 | func (a AtbashCipher) Cipher(input string) string { 68 | output := "" 69 | for _, r := range input { 70 | if idx, ok := indexOf(r, alphabet); ok { 71 | idx = 25 - idx 72 | output += string(alphabet[idx]) 73 | } else { 74 | output += string(r) 75 | } 76 | } 77 | return output 78 | } 79 | 80 | func (a AtbashCipher) Decipher(input string) string { 81 | return a.Cipher(input) 82 | } 83 | 84 | type CustomCipher struct { 85 | } 86 | 87 | func (c CustomCipher) Cipher(input string) string { 88 | return "" 89 | } 90 | 91 | func (c CustomCipher) Decipher(input string) string { 92 | return "" 93 | } 94 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Packt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 2 | # Functional Programming in Go. 3 | 4 | 5 | 6 | 7 | This is the code repository for [ Functional Programming in Go ](https://www.packtpub.com/product/functional-programming-in-go/9781801811163?utm_source=github&utm_medium=repository&utm_campaign=), published by Packt. 8 | 9 | **Apply functional techniques in Golang to improve the testability, readability, and security of your code** 10 | 11 | ## What is this book about? 12 | Functional programming is a tool for every programmer’s toolbox. Go offers the flexibility to approach a problem with different methodologies, allowing you to tackle a problem in the style best suited for your solution. In this book, you’ll learn the what, when, and why of functional programming and see how to apply key concepts in Go. 13 | 14 | This book covers the following exciting features: 15 | * Gain a deeper understanding of functional programming through practical examples 16 | * Build a solid foundation in core FP concepts and see how they apply to Go code 17 | * Discover how FP can improve the testability of your code base 18 | * Apply functional design patterns for problem solving 19 | * Understand when to choose and not choose FP concepts 20 | * Discover the benefits of functional programming when dealing with concurrent code 21 | 22 | If you feel this book is for you, get your [copy](https://www.amazon.com/dp/1801811164) today! 23 | 24 | https://www.packtpub.com/ 26 | 27 | ## Instructions and Navigations 28 | All of the code is organized into folders. For example, Chapter04. 29 | 30 | The code will look like the following: 31 | ``` 32 | func rollDice() int { 33 | return rand.Intn(6) 34 | } 35 | ``` 36 | 37 | **Following is what you need for this book:** 38 | If you are a Go engineer with a background in traditionally object-oriented languages such as Java or C++ and want to broaden your knowledge of functional programming, this book is for you. 39 | 40 | With the following software and hardware list you can run all code files present in the book (Chapter 1-11). 41 | ### Software and Hardware List 42 | | Chapter | Software required | OS required | 43 | | -------- | ------------------------------------ | ----------------------------------- | 44 | | 1-11 | Go (pre- and post-generics) | Windows, Mac OS X, and Linux (Any) | 45 | 46 | 47 | We also provide a PDF file that has color images of the screenshots/diagrams used in this book. [Click here to download it](https://packt.link/5tPDg). 48 | 49 | ### Related products 50 | * Domain-Driven Design with Golang [[Packt]](https://www.packtpub.com/product/domain-driven-design-with-golang/9781804613450?utm_source=github&utm_medium=repository&utm_campaign=) [[Amazon]](https://www.amazon.com/dp/1804613452) 51 | 52 | * Event-Driven Architecture in Golang [[Packt]](https://www.packtpub.com/product/event-driven-architecture-in-golang/9781803238012?utm_source=github&utm_medium=repository&utm_campaign=) [[Amazon]](https://www.amazon.com/dp/1803238011) 53 | 54 | 55 | 56 | ## Get to Know the Author 57 | **Dylan Meeus** 58 | is a Software Engineer, with over a decade of experience in various functional and non-functional programming languages. He has used Go to develop systems in a variety of domains, from healthcare to frameworks for machine learning platforms and digital signal processing software. He developed a passion for Functional Programming when learning Haskell and applied this knowledge to traditionally non-functional languages like Java. Over the past several years, Dylan has been a speaker at various Go and Java-oriented conferences such as GopherCon and Devoxx 59 | 60 | 61 | 62 | 63 | ### Download a free PDF 64 | 65 | If you have already purchased a print or Kindle version of this book, you can get a DRM-free PDF version at no cost.
Simply click on the link to claim your free PDF.
66 |

https://packt.link/free-ebook/9781801811163

--------------------------------------------------------------------------------