hof

package
v0.60.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 21, 2026 License: MIT Imports: 2 Imported by: 0

README

hof

Pure function combinators — compose, partially apply, or compare.

normalize := hof.Pipe(strings.TrimSpace, strings.ToLower)
slice.From(inputs).Transform(normalize)

For decorators over context-aware calls (retry, circuit breaker, throttle), see the call package.

What It Looks Like

// Compose two functions left-to-right
normalize := hof.Pipe(strings.TrimSpace, strings.ToLower)
// Partial application — fix one argument
add := func(a, b int) int { return a + b }
add5 := hof.Bind(add, 5)
slice.From(nums).Transform(add5)
// Apply separate functions to separate values
both := hof.Cross(double, toUpper)
d, u := both(5, "hello")  // 10, "HELLO"
// Equality predicate for Every/Any
allSkipped := slice.From(statuses).Every(hof.Eq(Skipped))
// Debounce — coalesce rapid calls, execute once after quiet period
d := hof.NewDebouncer(500*time.Millisecond, saveConfig)
defer d.Close()
d.Call(cfg)

Operations

Composition

  • Pipe[A, B, C](f func(A) B, g func(B) C) func(A) C — left-to-right composition
  • Bind[A, B, C](f func(A, B) C, a A) func(B) C — fix first arg
  • BindR[A, B, C](f func(A, B) C, b B) func(A) C — fix second arg
  • Cross[A, B, C, D](f func(A) C, g func(B) D) func(A, B) (C, D) — apply separate fns to separate args
  • Eq[T comparable](target T) func(T) bool — equality predicate factory

Debounce

  • NewDebouncer[T](wait, fn, opts...) *Debouncer[T] — trailing-edge coalescer
  • MaxWait(d) DebounceOption — cap maximum deferral

All functions panic on nil inputs.

See pkg.go.dev for complete API documentation and the main README for installation.

Documentation

Overview

Package hof provides higher-order functions over plain function signatures: composition, partial application, independent application, and call coalescing.

The organizing principle is the function shape. hof operates on plain signatures like func(A) B, func(A, B) C, and func(T). For decorators over the context-aware call shape func(context.Context, T) (R, error), see the [call] package.

Based on Stone's "Algorithms: A Functional Programming Approach" (pipe, sect, cross).

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Bind

func Bind[A, B, C any](f func(A, B) C, a A) func(B) C

Bind fixes the first argument of a binary function: Bind(f, x)(y) = f(x, y). Panics if f is nil.

Example
package main

import (
	"fmt"

	"github.com/binaryphile/fluentfp/hof"
)

func main() {
	// Fix the first argument of a binary function.
	add := func(a, b int) int { return a + b }
	addFive := hof.Bind(add, 5)

	fmt.Println(addFive(3))
}
Output:

8

func BindR

func BindR[A, B, C any](f func(A, B) C, b B) func(A) C

BindR fixes the second argument of a binary function: BindR(f, y)(x) = f(x, y). Panics if f is nil.

Example
package main

import (
	"fmt"

	"github.com/binaryphile/fluentfp/hof"
)

func main() {
	// Fix the second argument of a binary function.
	subtract := func(a, b int) int { return a - b }
	subtractThree := hof.BindR(subtract, 3)

	fmt.Println(subtractThree(10))
}
Output:

7

func Cross

func Cross[A, B, C, D any](f func(A) C, g func(B) D) func(A, B) (C, D)

Cross applies two functions independently to two separate arguments. Cross(f, g)(a, b) = (f(a), g(b)). Panics if f or g is nil.

Example
package main

import (
	"fmt"
	"strings"

	"github.com/binaryphile/fluentfp/hof"
)

func main() {
	// Apply separate functions to separate arguments.
	double := func(n int) int { return n * 2 }
	toUpper := func(s string) string { return strings.ToUpper(s) }

	both := hof.Cross(double, toUpper)
	d, u := both(5, "hello")

	fmt.Println(d, u)
}
Output:

10 HELLO

func Eq

func Eq[T comparable](target T) func(T) bool

Eq returns a predicate that checks equality to target. T is inferred from target: hof.Eq(Skipped) returns func(Status) bool.

func Pipe

func Pipe[A, B, C any](f func(A) B, g func(B) C) func(A) C

Pipe composes two functions left-to-right: Pipe(f, g)(x) = g(f(x)). Panics if f or g is nil.

Example
package main

import (
	"fmt"
	"strings"

	"github.com/binaryphile/fluentfp/hof"
)

func main() {
	// Compose TrimSpace then ToLower into a single transform.
	normalize := hof.Pipe(strings.TrimSpace, strings.ToLower)

	fmt.Println(normalize("  Hello World  "))
}
Output:

hello world
Example (Chaining)
package main

import (
	"fmt"
	"strconv"

	"github.com/binaryphile/fluentfp/hof"
)

func main() {
	// Multi-step composition uses intermediate variables.
	double := func(n int) int { return n * 2 }
	addOne := func(n int) int { return n + 1 }
	toString := func(n int) string { return strconv.Itoa(n) }

	doubleAddOne := hof.Pipe(double, addOne)
	full := hof.Pipe(doubleAddOne, toString)

	fmt.Println(full(5))
}
Output:

11

Types

type DebounceOption

type DebounceOption func(*debounceConfig)

DebounceOption configures a Debouncer.

func MaxWait

func MaxWait(d time.Duration) DebounceOption

MaxWait caps the maximum delay under continuous activity. When continuous calls keep resetting the trailing timer, MaxWait guarantees execution after this duration from the first call in a burst. Zero (default) means no cap — trailing edge only, which can defer indefinitely under continuous activity. Panics if d < 0.

type Debouncer

type Debouncer[T any] struct {
	// contains filtered or unexported fields
}

Debouncer coalesces rapid calls, executing fn with the latest value after a quiet period of at least wait. At most one fn execution runs at a time; calls during execution queue the latest value for a fresh timer cycle after completion.

A single owner goroutine manages all state — no mutex contention, no stale timer callbacks. Call, Cancel, and Flush communicate via channels; the owner processes events sequentially.

Value capture: Call stores the latest T by value. No deep copy is performed. If T contains pointers, slices, or maps, the caller must not mutate their contents after Call.

Panic behavior: fn runs in a spawned goroutine. If fn panics, the owner goroutine's state is preserved via deferred completion signaling, and the panic propagates normally (typically crashing the process).

Reentrancy: Call and Cancel are safe to invoke from within fn on the same Debouncer. Flush and Close from within fn will deadlock — fn completion must signal before either can proceed.

Close must be called when the Debouncer is no longer needed to stop the owner goroutine. Use-after-Close panics. Close is idempotent. Operations concurrent with Close may block until Close completes, then panic.

func NewDebouncer

func NewDebouncer[T any](wait time.Duration, fn func(T), opts ...DebounceOption) *Debouncer[T]

NewDebouncer creates a trailing-edge debouncer that executes fn with the latest value after wait elapses with no new calls. Panics if wait <= 0 or fn is nil.

func (*Debouncer[T]) Call

func (d *Debouncer[T]) Call(v T)

Call schedules fn with v. If a previous call is pending, its value is replaced with v and the trailing timer resets. If fn is currently executing, v is queued for a fresh timer cycle after completion.

func (*Debouncer[T]) Cancel

func (d *Debouncer[T]) Cancel() bool

Cancel stops any pending execution. Returns true if pending work was canceled, false if there was nothing pending. If a Flush is blocked waiting for pending work, Cancel unblocks it and the Flush returns false.

func (*Debouncer[T]) Close

func (d *Debouncer[T]) Close()

Close stops the owner goroutine. Any pending work is discarded. If fn is currently executing, Close waits for it to complete. If a Flush triggered the currently running execution, Flush returns true (the execution completes). If a Flush is waiting for pending work that Close discards, Flush returns false. Close is idempotent — subsequent calls return immediately. After Close, Call, Cancel, and Flush will panic. Operations concurrent with Close may block until Close completes.

Close must not be called from within fn on the same Debouncer — this will deadlock because fn completion must signal before Close can proceed.

func (*Debouncer[T]) Flush

func (d *Debouncer[T]) Flush() bool

Flush executes pending work immediately. Returns true if fn was executed as a result of this call, false if there was nothing pending.

When fn is already running with pending work queued, Flush blocks until the current fn completes and the pending work executes. New Calls that arrive during a flushed execution do not extend the Flush — they are scheduled normally via timer after Flush returns.

Only one Flush waiter is supported at a time. If a Flush is already waiting, subsequent Flush calls return false immediately.

Flush must not be called from within fn on the same Debouncer — this will deadlock because fn completion must signal before Flush can proceed.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL