Functional Programming in Golang: A Deep Dive into Map, Filter, and Reduce
Written on
Chapter 1: Introduction to Functional Programming
Functional programming is a computational paradigm that focuses on evaluating mathematical functions. Although Go is primarily known as an imperative programming language, it incorporates functional programming techniques, which enhance code readability, maintainability, and testability.
In this discussion, we will demonstrate the application of functional programming in Go through a practical data processing scenario: calculating the total hours of train delays for the Warsaw train station (WAW) based on a provided dataset. This dataset, formatted as JSON, includes various statistics for multiple stations. Our objectives are to:
- Filter the dataset for records pertaining to WAW.
- Convert the MinutesDelayed data into hours.
- Sum the total hours of delay.
To achieve this, we will utilize the functional programming core concepts: Filter, Map, and Reduce.
Here’s an example of the data we’re working with:
[
{
"City": {
"Code": "WAW",
"Name": "Warsaw"
},
"Statistics": {
"Trains": {
"Cancelled": 10,
"Delayed": 20,
"On Time": 70,
"Total": 100
},
"Minutes Delayed": {
"Carrier": 120,
"Late Aircraft": 240,
"Security": 30,
"Total": 390,
"Weather": 120
}
}
}
]
Each entry contains the city code and name, along with detailed statistics on train delays, with the Minutes Delayed section showing total delay times due to various reasons.
Let’s break down the solution step-by-step.
Section 1.1: Filtering the Data
First, we need to filter out the records for the Warsaw train station. We will create a Filter function that processes a slice of data and applies a condition to select relevant entries. In our case, we aim to check if the City.Code is WAW.
func Filter[A any](input []A, f filterFunc[A]) []A {
var output []A
for _, element := range input {
if f(element) {
output = append(output, element)}
}
return output
}
Section 1.2: Mapping the Data
Next, we will transform the minutes of delay into hours for better clarity. This is accomplished using the Map function, which accepts a slice and a transformation function.
func Map[A, B any](input []A, m mapFunc[A, B]) []B {
output := make([]B, len(input))
for i, element := range input {
output[i] = m(element)}
return output
}
Chapter 2: Reducing to a Final Result
Finally, we will sum the total hours of delay using the Reduce function, which condenses a collection of values into a single output.
func Reduce[A any](input []A, r reduceFunc[A], initial A) A {
acc := initial
for _, v := range input {
acc = r(acc, v)}
return acc
}
Now, let’s examine how we can use these three functional patterns to calculate the total hours of delays for the Warsaw station.
The video titled "Complete EdgeTX Radio Settings Guide: How-To Configure the Radiomaster Boxer [Zorro too]" offers a comprehensive guide that may provide additional insights into configuration processes relevant to our scenario.
Section 2.1: Complete Implementation
Here is the complete code that integrates all the components discussed:
package main
import (
"encoding/json"
"fmt"
"os"
)
type Statistics struct {
Trains struct {
Cancelled int json:"Cancelled"
Delayed int json:"Delayed"
OnTime int json:"On Time"
Total int json:"Total"
} json:"Trains"
MinutesDelayed struct {
Carrier int json:"Carrier"
LateAircraft int json:"Late Aircraft"
Security int json:"Security"
Total int json:"Total"
Weather int json:"Weather"
} json:"Minutes Delayed"
}
type City struct {
Code string json:"Code"
Name string json:"Name"
}
type Record struct {
City City json:"City"
Statistics Statistics json:"Statistics"
}
func main() {
// Read the JSON file
data, err := os.ReadFile("./scripts/functional-programming/together/data.json")
if err != nil {
fmt.Println("File reading error", err)
return
}
// Unmarshal JSON data
var records []Record
err = json.Unmarshal(data, &records)
if err != nil {
fmt.Println("Error unmarshalling JSON", err)
return
}
// Filter records by the airport code
filteredRecords := Filter(records, func(r Record) bool {
return r.City.Code == "WAW"})
// Transform MinutesDelayed into hours
hoursDelayed := Map(filteredRecords, func(r Record) float64 {
return float64(r.Statistics.MinutesDelayed.Total) / 60.0})
// Sum all the hours
totalHours := Reduce(hoursDelayed, func(a, b float64) float64 {
return a + b}, 0.0)
fmt.Printf("Total hours of delays for Warsaw (WAW): %.2fn", totalHours)
}
The effectiveness of functional programming lies in the fact that functions like Filter, Map, and Reduce are modular and independent of the specific conditions or transformations applied. This modularity allows for greater code reuse across different application sections, leading to enhanced readability and maintainability.
Section 2.2: Conclusion
While Go may not be strictly categorized as a functional programming language like Haskell or Elixir, incorporating functional programming principles can significantly improve code organization and efficiency. This exploration highlights just a fraction of the potential that functional programming holds within Go, inviting developers to delve deeper into its vast possibilities.
The video titled "Nikon Z8: Full Menu Setup Guide For Bank A Standard Settings" provides an in-depth look at configuration techniques that may enhance your understanding of similar programming paradigms.