Learning Functional Programming in Go
上QQ阅读APP看书,第一时间看更新

Predicates

We can use predicates to perform operations on input data. Predicates can be used to implement many of the functions that we apply to collections to transform input data into the result collection or value.

The predicate function is a function that takes one item as input and returns either true or false, based on whether the item satisfies some condition. They are often used conditionally to determine whether to apply certain operations in the execution chain.

Let's create some predicate functions that we can use to manipulate a collection of cars.

The All() function returns true only if all the values in the collection satisfy the predicate condition:

package predicate

func All(vals []string, predicate func(string) bool) bool {
for _, val := range vals {
if !predicate(val) {
return false
}
}
return true
}

The Any() function returns true as long as any one of the values in the collection satisfies the predicate condition:

func Any(vs []string, predicate func(string) bool) bool {
for _, val := range vs {
if predicate(val) {
return true
}
}
return false
}

The Filter() function returns a new, smaller, or equal-sized collection containing all the strings in the collection that satisfy the predicate condition:

func Filter(vals []string, predicate func(string) bool) []string {
filteredVals := make([]string, 0)
for _, v := range vals {
if predicate(v) {
filteredVals = append(filteredVals, v)
}
}
return filteredVals
}

The Count() function is a helper function:

func Count(vals []string) int {
return len(vals)
}

Now, let's use a Mocha-like BDD Go testing framework, named goblin, to test our predicates.

Declare the package and define the basic imports. We only need to define one function. Let's call it TestPredicateSucceed:

package predicate

import (
"testing"
"strings"
. "github.com/franela/goblin"
)


func TestPredicateSucceed(t *testing.T) {
fakeTest := testing.T{}
g := Goblin(&fakeTest)

Let's wrap all our unit tests with a Describe block named Predicate Tests, where we define the cars variable to hold a list of our car models:

     g.Describe("Predicate Tests", func() {
cars := []string{"CRV", "IS250", "Highlander"}

Here's our first test. It starts with a Describe block and contains one It block. Inside our It block, we assign our first-class function bs, the return value of calling the Any() function. Our predicate function is the function literal that calls the strings.HasPrefix() function. The last line of our unit test asserts that bs is true:

g.Describe("Starts High", func() {
g.It("Should be true", func() {
bs := Any(cars, func(v string) bool {
return strings.HasPrefix(v, "High")
})
g.Assert(bs).Equal(true)
})
})

Our next unit test says Highlander should be High and asserts that it should be true. We pass the strings.Contains() function as our predicate to the Filter() function to return only those items in the list that contain the High substring: 

g.Describe("Highlander should be High", func() {
high := Filter(cars, func(v string) bool {
return strings.Contains(v, "High")
})
highlander := []string{"Highlander"}
g.It("Should be true", func() {
g.Assert(high).Equal(highlander)
})
})

This test counts the number of cars that contain the High substring and asserts that the count should be 1:

g.Describe("One is High", func() {
high := Count(Filter(cars, func(v string) bool {
return strings.Contains(v, "High")
}))
g.It("Should be true", func() {
g.Assert(high).Equal(1)
})
})

Our last test asserts that not all cars contain the High substring:

g.Describe("All are High", func() {
high := All(cars, func(v string) bool {
return strings.Contains(v, "High")
})
g.It("Should be false", func() {
g.Assert(high).Equal(false)
})
})

Let's take a moment to reflect on this implementation.