Skip to content
Chris Pappas edited this page May 10, 2022 · 17 revisions

hoff: Higher Order Functions (and Friends)

2014 called, they want their meme back :/ Chris L insisted

Welcome to the hoff wiki. This is just a starting page and will evolve along with our project.

This project is part of Shopify Hack Days 32! Our talented and eager group of gophers aim to release a public, open source project that provides some "batteries" for building Golang apps using Go 1.18+ and Generics.

Contributors

Starting point/project outline

This Hackdays project was Chris Pappas's idea, after seeing we (mostly Chris Langager) had built up a fairly useful library of utils using generics, and realizing we should share this for efficiency and prestige.

A lot of the below functions are already implemented in our "day job" project, and just need tests/benchmarks and documentation: https://github.com/Shopify/shopify-monitoring-api/tree/main/pkg/utils

Pappy also included his implementation of Set as a starting point for the repo, previously published on his personal GH: https://github.com/chrispappas/golang-generics-set/

These are a possible list of utils functions we want... This comes courtesy of Chris Langager:

  1. Write out a list of the utils functions we want, ex

Eduardo will take MapMap and add tests/documentation - PR: https://github.com/Shopify/hof/pull/6
[X] Map
[X] MapContext
[X] MapError
[X] MapContextError
[X] MapConcurrent
[X] MapConcurrentError
[X] ToValues
[X] ToSlices

Pappy will handle Filter and Reduce - PR: https://github.com/Shopify/hof/pull/4
[x] Filter
[x] FilterContext
[x] FilterError
[x] FilterContextError

https://github.com/Shopify/hoff/pull/14
[x] Reduce
[x] ReduceContext
[x] ReduceError
[x] ReduceContextError

Whesley will take care of the ForEach methods
[x] ForEach
[x] ForEachContext
[] ForEachError
[] ForEachContextError

Paco will work on this! - PR: https://github.com/Shopify/hoff/pull/5
[x] FlatMap
[x] FlatMapContext
[x] FlatMapError
[x] FlatMapContextError

[] MapMap
[] MapMapContext
[] MapMapError
[] MapMapContextError

[x] Pluck PR#8
[x] Chunk
  1. Implement one "set" of functions (ie. Map) to be an exemplar for naming conventions
  2. Assign functions for people to implement (+ tests) and get a whole bunch of work done in parallel
  3. Document functions with examples it would be great if someone with a sense of design/readability could come up with a standard way of documenting things in a readme
  4. CI/CD to run tests in repo using github actions should be pretty straight forward since all of these are unit tests

More background and discussion from Slack

Putting this here so it's all in one place

Chris Langager 2:51 PM Worth nothing one of the goals here... Like Chris P said, there's a couple of libs already out there that have a lot of this functionality (godash comes to mind). One of the biggest gaps that those other ones have is not embracing common go things, like passing context.Context and dealing with errors as return values. To use an example to explain....

import "context"

func Map[In, Out any](
	arr []In,
	fn func(In, int) Out,
) []Out {
	out := make([]Out, 0, len(arr))

	for i, elem := range arr {
		out = append(out, fn(elem, i))
	}

	return out
}

func MapContext[In, Out any](
	ctx context.Context,
	ins []In,
	fn func(context.Context, In, int) (Out, context.Context),
) ([]Out, context.Context) {
	outs := make([]Out, 0, len(ins))

	for i, elem := range ins {
		var mapped Out
		mapped, ctx = fn(ctx, elem, i)
		outs = append(outs, mapped)
	}

	return outs, ctx
}

func MapError[In, Out any](
	arr []In,
	fn func(In, int) (Out, error),
) ([]Out, error) {
	out := make([]Out, 0, len(arr))

	for i, elem := range arr {
		mapped, err := fn(elem, i)
		if err != nil {
			return nil, err
		}
		out = append(out, mapped)
	}

	return out, nil
}

func MapContextError[In, Out any](
	ctx context.Context,
	arr []In,
	fn func(context.Context, In, int) (Out, error),
) ([]Out, error) {
	out := make([]Out, 0, len(arr))

	for i, elem := range arr {
		mapped, err := fn(ctx, elem, i)
		if err != nil {
			return nil, err
		}
		out = append(out, mapped)
	}

	return out, nil
}

Sometimes you just want a simple map of one type to another, but we've had a bunch of use cases where we need to pass context along, or deal with errors if there's a problem mid map. These 4 functions cover all combos.

2:51 Wondering if this idea of supporting Context / Errors can help prompt for names

2:51 also other opinion, I think a really short package name would be nice