Skip to content

Commit

Permalink
better reduce signature
Browse files Browse the repository at this point in the history
  • Loading branch information
quii committed Apr 13, 2022
1 parent 46c4843 commit 4bd9aac
Show file tree
Hide file tree
Showing 5 changed files with 33 additions and 30 deletions.
2 changes: 1 addition & 1 deletion arrays/v8/bad_bank.go
Expand Up @@ -18,8 +18,8 @@ type Account struct {
func NewBalanceFor(account Account, transactions []Transaction) Account {
return Reduce(
transactions,
account,
applyTransaction,
account,
)
}

Expand Down
7 changes: 4 additions & 3 deletions arrays/v8/collection_fun.go
Expand Up @@ -9,9 +9,10 @@ func Find[A any](items []A, predicate func(A) bool) (value A, found bool) {
return
}

func Reduce[A, B any](collection []A, accumulator B, f func(B, A) B) B {
func Reduce[A, B any](collection []A, accumulator func(B, A) B, initialValue B) B {
var result = initialValue
for _, x := range collection {
accumulator = f(accumulator, x)
result = accumulator(result, x)
}
return accumulator
return result
}
4 changes: 2 additions & 2 deletions arrays/v8/sum.go
Expand Up @@ -3,7 +3,7 @@ package main
// Sum calculates the total from a slice of numbers.
func Sum(numbers []int) int {
add := func(acc, x int) int { return acc + x }
return Reduce(numbers, 0, add)
return Reduce(numbers, add, 0)
}

// SumAllTails calculates the sums of all but the first number given a collection of slices.
Expand All @@ -17,5 +17,5 @@ func SumAllTails(numbers ...[]int) []int {
}
}

return Reduce(numbers, []int{}, sumTail)
return Reduce(numbers, sumTail, []int{})
}
4 changes: 2 additions & 2 deletions arrays/v8/sum_test.go
Expand Up @@ -50,15 +50,15 @@ func TestReduce(t *testing.T) {
return x * y
}

AssertEqual(t, Reduce([]int{1, 2, 3}, 1, multiply), 6)
AssertEqual(t, Reduce([]int{1, 2, 3}, multiply, 1), 6)
})

t.Run("concatenate strings", func(t *testing.T) {
concatenate := func(x, y string) string {
return x + y
}

AssertEqual(t, Reduce([]string{"a", "b", "c"}, "", concatenate), "abc")
AssertEqual(t, Reduce([]string{"a", "b", "c"}, concatenate, ""), "abc")
})
}

Expand Down
46 changes: 24 additions & 22 deletions revisiting-arrays-and-slices-with-generics.md
@@ -1,4 +1,4 @@
# Revisiting arrays and slices with generics (DRAFT)
# Revisiting arrays and slices with generics

**[The code for this chapter is a continuation from Arrays and Slices, found here](https://github.com/quii/learn-go-with-tests/tree/main/arrays)**

Expand Down Expand Up @@ -32,9 +32,9 @@ func SumAllTails(numbersToSum ...[]int) []int {

Do you see a recurring pattern?

- Create some kind of "initial" value, or accumulator.
- Iterate over the collection, applying some kind of operation (or function) to the accumulator and the next item in the slice.
- Return the accumulator.
- Create some kind of "initial" result value.
- Iterate over the collection, applying some kind of operation (or function) to the result and the next item in the slice, setting a new value for the result
- Return the result.

This idea is commonly talked about in functional programming circles, often times called 'reduce' or [fold](https://en.wikipedia.org/wiki/Fold_(higher-order_function)).

Expand Down Expand Up @@ -66,24 +66,25 @@ You should be familiar with the generics syntax [from the previous chapter](gene

If you think about the arguments to your function first, it'll give you a very small set of valid solutions
- The array you want to reduce
- Some kind of combining function
- Some kind of combining function, or _accumulator_

"Reduce" is an incredibly well documented pattern, there's no need to re-invent the wheel. [Read the wiki, in particular the lists section](https://en.wikipedia.org/wiki/Fold_(higher-order_function)), it should prompt you for another argument you'll need.

> In practice, it is convenient and natural to have an initial value
### My reduce function
### My first-pass of `Reduce`

```go
func Reduce[A any](collection []A, accumulator A, f func(A, A) A) A {
func Reduce[A any](collection []A, accumulator func(A, A) A, initialValue A) A {
var result = initialValue
for _, x := range collection {
accumulator = f(accumulator, x)
result = accumulator(result, x)
}
return accumulator
return result
}
```

Reduce captures the _essence_ of the pattern, it's a function that takes a collection, an initial value and a combining function, and returns a single value. There's no messy distractions around concrete types.
Reduce captures the _essence_ of the pattern, it's a function that takes a collection, an accumulating function, an initial value, and returns a single value. There's no messy distractions around concrete types.

If you understand generics syntax, you should have no problem understanding what this function does. By using the recognised term `Reduce`, programmers from other languages understand the intent too.

Expand All @@ -93,7 +94,7 @@ If you understand generics syntax, you should have no problem understanding what
// Sum calculates the total from a slice of numbers.
func Sum(numbers []int) int {
add := func(acc, x int) int { return acc + x }
return Reduce(numbers, 0, add)
return Reduce(numbers, add, 0)
}

// SumAllTails calculates the sums of all but the first number given a collection of slices.
Expand All @@ -107,7 +108,7 @@ func SumAllTails(numbers ...[]int) []int {
}
}

return Reduce(numbers, []int{}, sumTail)
return Reduce(numbers, sumTail, []int{})
}
```

Expand All @@ -124,22 +125,22 @@ func TestReduce(t *testing.T) {
return x * y
}

AssertEqual(t, Reduce([]int{1, 2, 3}, 1, multiply), 6)
AssertEqual(t, Reduce([]int{1, 2, 3}, multiply, 1), 6)
})

t.Run("concatenate strings", func(t *testing.T) {
concatenate := func(x, y string) string {
return x + y
}

AssertEqual(t, Reduce([]string{"a", "b", "c"}, "", concatenate), "abc")
AssertEqual(t, Reduce([]string{"a", "b", "c"}, concatenate, ""), "abc")
})
}
```

### The zero value

In the multiplication example, we show the reason for having a default value as an argument to `Reduce`. If we relied on Go's default value of 0, we'd multiply our initial value by 0, and then the following ones, so you'd only ever get 0. By setting it to 1, the first element in the slice will stay the same, and the rest will multiply by the next elements.
In the multiplication example, we show the reason for having a default value as an argument to `Reduce`. If we relied on Go's default value of 0 for `int`, we'd multiply our initial value by 0, and then the following ones, so you'd only ever get 0. By setting it to 1, the first element in the slice will stay the same, and the rest will multiply by the next elements.

If you wish to sound clever with your nerd friends, you'd call this [The Identity Element](https://en.wikipedia.org/wiki/Identity_element).

Expand Down Expand Up @@ -251,7 +252,7 @@ func BalanceFor(transactions []Transaction, name string) float64 {
}
return currentBalance
}
return Reduce(transactions, 0.0, adjustBalance)
return Reduce(transactions, adjustBalance, 0.0)
}
```

Expand All @@ -264,11 +265,12 @@ But this won't compile.
The reason is we're trying to reduce to a _different_ type than the type of the collection. This sounds scary, but actually just requires us to adjust the type signature of `Reduce` to make it work. We won't have to change the function body, and we won't have to change any of our existing callers.

```go
func Reduce[A, B any](collection []A, accumulator B, f func(B, A) B) B {
func Reduce[A any](collection []A, accumulator func(A, A) A, initialValue A) A {
var result = initialValue
for _, x := range collection {
accumulator = f(accumulator, x)
result = accumulator(result, x)
}
return accumulator
return result
}
```

Expand Down Expand Up @@ -326,8 +328,8 @@ type Account struct {
func NewBalanceFor(account Account, transactions []Transaction) Account {
return Reduce(
transactions,
account,
applyTransaction,
applyTransaction,
account,
)
}

Expand Down Expand Up @@ -447,4 +449,4 @@ Discuss with your colleagues patterns and style of code based on their merits ra

Fold is a real fundamental in computer science. Here's some interesting resources if you wish to dig more into it
- [Wikipedia: Fold](https://en.wikipedia.org/wiki/Fold)
- [A tutorial on the universality and expressiveness of fold](http://www.cs.nott.ac.uk/~pszgmh/fold.pdf)
- [A tutorial on the universality and expressiveness of fold](http://www.cs.nott.ac.uk/~pszgmh/fold.pdf)

0 comments on commit 4bd9aac

Please sign in to comment.