├── 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 |
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 |