Go the functional way

Trying to do functional programming with GO, the below code worked fine with me, but liked to share with the community for review/comments/feedback

package main

import (
	"fmt"
	"log"
	"sort"
	"time"
)

const (
	layoutISO = "2006-01-02"
	layoutUS  = "January 2, 2006"
	custom    = "1/2/2006"
)

type Inventories []Inventory
type Inventory struct { //instead of: map[string]map[string]Pairs
	Warehouse string
	Item      string
	Batches   Lots
}
type Lots []Lot
type Lot struct {
	Date  time.Time
	Key   string
	Value float64
}

func main() {
	fmt.Println("Hello, 世界")
	date := "12/31/19" // "1999-12-31"
	t, _ := time.Parse(custom, date)
	var inventories = new(Inventories)
	var inventory = new(Inventory) // Or = Inventory{} both are working //warehouse[item[batch, qty]]
	inventory.Warehouse = "DMM"
	inventory.Item = "Helmet"
	inventory.Batches = append(inventory.Batches, Lot{t, "Jan", 10})
	inventory.InsertBatch(Lot{time.Now(), "Jan", 30})
	inventory.Batches.Insert(Lot{time.Now(), "Feb", 30})
	fmt.Printf("\nBefore grouping: %v %T\n", inventory, inventory)

	x := Inventory{
		Warehouse: "DMM",
		Item:      "Gloves",
		Batches: Lots{
			Lot{mustTime(time.Parse(custom, "1/7/2020")), "Jan", 50},
			Lot{mustTime(time.Parse(custom, "2/1/2020")), "Feb", 20},
			Lot{mustTime(time.Parse(custom, "1/5/2020")), "Jan", 40},
			Lot{mustTime(time.Parse(custom, "2/9/2020")), "Feb", 60},
		},
	}
	fmt.Printf("\nBefore grouping: %v %T\n", x, x)

	// Below can be used for grouping batches in each warehouse seperatly, this can be combined in the lines under
	//	inventory.Batches.Group()
	//	x.GroupBatches()
	// 	fmt.Printf("\nAfter grouping: %v %T\n", inventory, inventory)
	//  fmt.Printf("\nAfter grouping: %v %T\n", x, x)

	// Above can be replaced by below
	inventories.Insert(*inventory)
	inventories.Insert(x)
	inventories.GroupBatches()

	fmt.Printf("\nInventories after gouping batches: %v %T\n", inventories, inventories)

	inventories.SortBatches()

	fmt.Printf("\nInventories after sorting batches: %v %T\n", inventories, inventories)
}

func (i *Inventories) Insert(x Inventory) {
	*i = append(*i, x)
}
func (i *Inventories) GroupBatches() {
	inv := new(Inventories)
	for _, el := range *i {
		el.GroupBatches()
		inv.Insert(el)
	}
	(*i).ReplaceBy(inv)
}

func (i *Inventories) SortBatches() {
	inv := new(Inventories)
	for _, el := range *i {
		sort.Sort(Lots(el.Batches))
		inv.Insert(el)
	}
	(*i).ReplaceBy(inv)
}

func (i *Inventories) ReplaceBy(x *Inventories) {
	*i = *x
}

func (i *Inventory) InsertBatch(x Lot) {
	(*i).Batches = append((*i).Batches, x)
}

func (i *Inventory) GroupBatches() {
	(*i).Batches.Group()
}

func (p *Lots) Group() {
	lots := new(Lots)
	lots.FromMap(p.Map())
	p.ReplaceBy(lots)
}

func (p *Lots) FromMap(m map[string]float64) {
	for k, v := range m {
		(*p).Insert(Lot{time.Now(), k, v})
	}
}

// Below to enable sorting: sort.Sort(Lots(lots))
func (l Lots) Len() int           { return len(l) }
func (l Lots) Less(i, j int) bool { return (l[i].Date).Before(l[j].Date) } // { return l[i].Key < l[j].Key }
func (l Lots) Swap(i, j int)      { l[i], l[j] = l[j], l[i] }

func (p *Lots) Insert(x Lot) {
	*p = append(*p, x)
}

func (p *Lots) ReplaceBy(x *Lots) {
	*p = *x
}

func (p *Lots) Map() map[string]float64 {
	sum := make(map[string]float64)
	for _, el := range *p {
		sum[el.Key] = sum[el.Key] + el.Value
	}
	return sum
}

type Interface interface {
	// Len is the number of elements in the collection.
	Len() int
	// Less reports whether the element with
	// index i should sort before the element with index j.
	Less(i, j int) bool
	// Swap swaps the elements with indexes i and j.
	Swap(i, j int)
}

func mustTime(t time.Time, err error) time.Time {
	failOnError(err)
	return t
}

func failOnError(err error) {
	if err != nil {
		log.Fatal("Error:", err)
		panic(err)
	}
}

The output is:

[Running] go run "d:\goplay\grouping.go"
Hello, 世界

Before grouping: &{DMM Helmet [{0001-01-01 00:00:00 +0000 UTC Jan 10} {2020-06-05 00:26:16.9165066 +0300 +03 m=+0.006981101 Jan 30} {2020-06-05 00:26:16.9165066 +0300 +03 m=+0.006981101 Feb 30}]} *main.Inventory

Before grouping: {DMM Gloves [{2020-01-07 00:00:00 +0000 UTC Jan 50} {2020-02-01 00:00:00 +0000 UTC Feb 20} {2020-01-05 00:00:00 +0000 UTC Jan 40} {2020-02-09 00:00:00 +0000 UTC Feb 60}]} main.Inventory

Inventories after gouping batches: &[{DMM Helmet [{2020-06-05 00:26:16.9175045 +0300 +03 m=+0.007979001 Jan 40} {2020-06-05 00:26:16.9175045 +0300 +03 m=+0.007979001 Feb 30}]} {DMM Gloves [{2020-06-05 00:26:16.9175045 +0300 +03 m=+0.007979001 Jan 90} {2020-06-05 00:26:16.9175045 +0300 +03 m=+0.007979001 Feb 80}]}] *main.Inventories

Inventories after sorting batches: &[{DMM Helmet [{2020-06-05 00:26:16.9175045 +0300 +03 m=+0.007979001 Jan 40} {2020-06-05 00:26:16.9175045 +0300 +03 m=+0.007979001 Feb 30}]} {DMM Gloves [{2020-06-05 00:26:16.9175045 +0300 +03 m=+0.007979001 Jan 90} {2020-06-05 00:26:16.9175045 +0300 +03 m=+0.007979001 Feb 80}]}] *main.Inventories

[Done] exited with code=0 in 3.624 seconds```
1 Like

Could you explain how this is functional programming? AFAIK, an important aspect would be to be able to use a func as the argument of another func.

As per Wikipedia,

Functional programming is a programming paradigm—a style of building the structure and elements of computer programs—that treats computation as the evaluation of mathematical functions and avoids changing-state and mutable data.

Hence in functional programming, there are two very important rules

  • No Data mutations : It means a data object should not be changed after it is created.
  • No implicit state : Hidden/Implicit state should be avoided. In functional programming state is not eliminated, instead, its made visible and explicit

What you mentioned is just one of the concepts of functional programming, that is named Higher-order-functions that you can:

  1. Assign functions to variables,
  2. Pass a function as an argument to another function,
  3. Return a function from another

Reference

An example of it with GO is:

package main
import "fmt"

func main() {
	var list = []string{"Orange", "Apple", "Banana", "Grape"}
	// we are passing the array and a function as arguments to mapForEach method.
	var out = mapForEach(list, iter)
	fmt.Println(out) // [6, 5, 6, 5]

}

func iter(x string) int {
	return len(x)
}

// The higher-order-function takes an array and a function as arguments
func mapForEach(arr []string, fn func(it string) int) []int {
	var newArray = []int{}
	for _, r := range arr {
		// We are executing the method passed // in this case it is func iter
		newArray = append(newArray, fn(r))
	}
	return newArray
}

3 Likes

It’s a bit hard if you type-specific your function parameters (as in []string, etc.). There are a few ways to go around it:

  1. Define a strict data structure outline for each functions to pass against one another, forming a design pattern known as “Chain of Responsibilities”. More Info: https://sites.google.com/view/chewkeanho/guides/software-design-patterns/patterns/behavioral/chain-of-responsibilities
  2. Rather than type defining, you can “use” null interface interface{} as data types and re-identify data type inside the function. This takes extra CPU resources but it’s the most crude way of implementing FP.

You must understand that under true FP, function like iter and mapForEach must work for any data types. This is the reason why Go is not a suitable candidate for FP as there are extra resources spent on data type identifications.

You should try Haskell when you have spare time if you’re into FP.

FYI, you should checkout how UNIX pipeline works in terminal. That pipeline is a real concept of FP where the source files are not changed unless being piped to function that explicitly changes them.

2 Likes

The following examples I wrote back in time might be helpful. V1 is the the simplest version of all.
V1: https://play.golang.org/p/xBgJUlz2uYX
V2: https://play.golang.org/p/1_NXGwTtBHJ
V3: https://play.golang.org/p/gHRO16rEEw7
V4: https://play.golang.org/p/2gdsQyvL0yi

1 Like