Abstracting Application IO Using Higher Order Functions
Tags: functional, go, programming
Published: 2021-02-20 19:49:58

In a previous blog post I examined how [interfaces can be used to abstract away the details of an application's IO](https://dev.to/jakewitcher/abstracting-application-io-using-interfaces-59gi) creating code that is easier to test and easier to extend. Although interfaces are a very effective tool for abstraction, they are not the only tool available for separating business logic and IO. Higher order functions are another method we can use to separate the details about file systems or databases from our core business logic.

Higher order functions are functions that rely on other functions as either a parameter type or a return type. Functions like `map` and `filter` in Java, Python and many other languages fit the definition of a higher order function. Both of these functions accept another function as an argument; one that either describes how the elements of a collection should be filtered or how the elements should be transformed.

```python
nums = [1, 2, 3, 4, 5]

# using higher order functions in Python
squared_nums = map(lambda n: n * n, nums)
even_nums = filter(lambda n: n % 2 == 0, nums)
```

Or if you have used the `http` package in Go, you may be familiar with another higher order function, the `http.HandleFunc` method. This method takes a function that defines how an http request should be handled for a given route as its second argument.

```golang
func handleHomePage(writer http.ResponseWriter, _ *http.Request) {
	_, err := fmt.Fprintf(writer, "This is the Home Page")
	if err != nil {
		log.Fatalln(err)
	}
}

func main() {
	http.HandleFunc("/", handleHomePage)
    log.Fatalln(http.ListenAndServe(":8080", nil))
}
```

We can illustrate how higher order functions are able to hide IO details using the same example from the previous blog post; an application that calculates an order total from a list of line items. We'll start by writing a version of the `CalculateOrderTotal` function that takes a function as an argument rather than a struct that satisfies the `OrderProvider` interface.

```golang
func CalculateOrderTotal(providerFunc func() []LineItem) float64 {
	lineItems := providerFunc()

	var orderTotal float64
	for _, lineItem := range lineItems {
		orderTotal += lineItem.Price
	}

	return orderTotal
}
```

When using higher order functions, the type signature of the function is key. The new parameter `providerFunc` is a function that takes no arguments and returns an array slice of line items. Any function that matches the type signature `func() []LineItem` can be used as an argument for this version of `CalculateOrderTotal`.

The implementation details regarding how this function will retrieve and then return a list of line items is kept hidden from `CalculateOrderTotal` just as it was before when using the `OrderProvider` interface.

To test out this new version of `CalculateOrderTotal`, we can define a function that returns a list of line items stored in memory. However instead of just writing a function that returns a predetermined list of line items, we'll create a provider function factory that creates a function with the desired signature `func() []LineItem` using a list of line items passed as an argument to the factory function.

```golang
func InMemoryLineItemsProviderFuncFactory(lineItems []LineItem) func() []LineItem {
	return func() []LineItem {
		return lineItems
	}
}
```

This implementation may seem unnecessarily complex, and if all we needed was a static list of line items it certainly would be. But the goal is to keep IO details away from `CalculateOrderTotal` and to have the freedom to swap out those details on the fly. This factory approach will allow us to test the new version of `CalculateOrderTotal` with different sets of values stored in memory to make sure it still works the same as before.

```golang
var testCases = []struct {
	lineItems []LineItem
	expected  float64
}{
	{
		lineItems: []LineItem{
			{Description: "A", Price: 85},
			{Description: "B", Price: 15},
		},
		expected: 100,
	},
	{
		lineItems: []LineItem{
			{Description: "A", Price: 35.25},
			{Description: "B", Price: 95.5},
		},
		expected: 130.75,
	},
}

func TestHigherOrderFunctionCalculateOrderTotal(t *testing.T) {
	for _, test := range testCases {
		providerFunc := InMemoryLineItemsProviderFuncFactory(test.lineItems)

		if actual := CalculateOrderTotal(providerFunc); actual != test.expected {
			t.Fatalf("expected %.2f, actual %.2f", test.expected, actual)
		}
	}
}
```

This test suite is nearly identical to the one we used to test the final version of `CalculateOrderTotal` in the previous blog post. The line items and expected output are the same however instead of encapsulating the line items with an `InMemoryOrderProvider` struct that implements the `OrderProvider` interface we are using `InMemoryLineItemsProviderFuncFactory` to encapsulate the details in a function. 

When running the test suite using the new `CalculateOrderTotal` it produces the same results as the previous version. The new implementation required very few changes to our code and still prevented the details of our IO from leaking into `CalculateOrderTotal`.

Now lets take a look at how the same function signature can be used to retrieve the line item data from a JSON file.

```golang
func FileBasedLineItemsProviderFuncFactory(filepath string) func() []LineItem {
	return func() []LineItem {
		file, err := ioutil.ReadFile(filepath)
		if err != nil {
			log.Fatalf("unable to read file %s", filepath)
		}

		var lineItems []LineItem
		err = json.Unmarshal(file, &lineItems)
		if err != nil {
			log.Fatalf("unable to parse json for file %s", filepath)
		}

		return lineItems
	}
}
```

The function `FileBasedLineItemsProviderFuncFactory` is another provider function factory that returns a function with the signature `func() []LineItem` based on the `filepath` parameter of the factory function.

```golang
func main() {
	providerFunc := FileBasedLineItemsProviderFuncFactory("data/order_items.json")
	orderTotal := CalculateOrderTotal(providerFunc)

	fmt.Printf("Your total comes to %.2f", orderTotal)
}
```

At this point our higher order function solution is providing more or less the same functionality as the interface solution from the previous blog post. For most programming languages this would be a good place to stop, however Go has a unique language feature that allows us to use function types as receiver arguments for methods opening up an additional means of abstraction using both higher order functions and interfaces together.

A receiver argument determines the type that can invoke a method in Go in the same way that defining a public method in a C# or Java class allows you to invoke the method from an instance of that class  type using the `receiver.methodName()` syntax. 

In Go a receiver can be any type, not just a struct. This allows us to attach a method to a function type and then use that method to satisfy the requirements of an interface.

To start off, we'll need to take our function signature `func() []LineItem` and assign it to a named type.

```golang
type ProviderFunc func() []LineItem
```

With the function type defined, we can now create a `GetLineItems` method that uses the `ProviderFunc` type as a receiver.

```golang
// the OrderProvider type from the previous blog post for reference
type OrderProvider interface {
	GetLineItems() []LineItem
}

func (f ProviderFunc) GetLineItems() []LineItem {
	return f()
}
```

The `GetLineItems` method of the `ProviderFunc` type returns the results of calling the function `f`. In other words we are able to satisfy the `GetLineItems` method of the `OrderProvider` interface by having the `ProviderFunc` function type invoke itself.

Before we can use our two factory functions to create `ProviderFunc` functions that satisfy the `OrderProvider` interface, we'll need to change their return type from `func() []LineItem` to `ProviderFunc`. Nothing needs to be changed in the body of the factories as the functions they return already satisfy the `ProviderFunc` type. They just have to be explicitly returned as such.

```golang
func InMemoryLineItemsProviderFuncFactory(lineItems []LineItem) ProviderFunc {
	// ...function body remains the same as before
}

func FileBasedLineItemsProviderFuncFactory(filepath string) ProviderFunc {
	// ...function body remains the same as before
}
```

Now the functions returned by both factories satisfy the parameter type of the new `CalculateOrderTotal` function (a `ProviderFunc` can still be used as a `func() []LinetItem` type) as well as the previous version of `CalculateOrderTotal` with its `OrderProvider` parameter type. Whether we use the old or new version of `CalculateOrderTotal` we are still able to switch data sources with relative ease, going from in memory to file based data retrieval without having to rewrite either version of `CalculateOrderTotal`.

```golang
func NewCalculateOrderTotal(providerFunc func() []LineItem) float64 {
	lineItems := providerFunc()

	var orderTotal float64
	for _, lineItem := range lineItems {
		orderTotal += lineItem.Price
	}

	return orderTotal
}

func PrevCalculateOrderTotal(provider OrderProvider) float64 {
	lineItems := provider.GetLineItems()

	var orderTotal float64
	for _, lineItem := range lineItems {
		orderTotal += lineItem.Price
	}

	return orderTotal
}

func main() {
	fileBasedProviderFunc := FileBasedLineItemsProviderFuncFactory("data/order_items.json")

    // using the file based provider function as a 'func() []LineItem' function type
	orderTotal := NewCalculateOrderTotal(fileBasedProviderFunc)
	fmt.Printf("Your total comes to %.2f", orderTotal)

    // using the file based provider function as an 'OrderProvider' interface type
	orderTotal = PrevCalculateOrderTotal(fileBasedProviderFunc)
	fmt.Printf("Your total comes to %.2f", orderTotal)

	lineItems := []LineItem{
		{Description: "Leather Recliner", Price: 2499},
		{Description: "End Table", Price: 249},
	}

	inMemoryProviderFunc := InMemoryLineItemsProviderFuncFactory(lineItems)

    // using the in memory provider function as a 'func() []LineItem' function type
	orderTotal = NewCalculateOrderTotal(inMemoryProviderFunc)
	fmt.Printf("Your total comes to %.2f", orderTotal)

    // using the in memory provider function as an 'OrderProvider' interface type
	orderTotal = PrevCalculateOrderTotal(inMemoryProviderFunc)
	fmt.Printf("Your total comes to %.2f", orderTotal)
}
```

Determining which method you use &mdash; a struct satisfying an interface type, a higher order function, or a function satisfying an interface type &mdash; will depend on details about the problems you're trying to solve, the application you're building, and personal preference. It is outside the scope of this blog post to discuss how those factors might impact your design decisions. 

Regardless of which method best suites the requirements of your application, creating separation between business logic and IO through these abstractions will make your code resilient to change and easier to test.

*All example code from both blog posts on abstracting application IO can be found [here](https://github.com/jakewitcher/abstracting-application-io).*