Skip to main content
Version: v2.2.82 (latest)
v2 · Utilities

Compose Right-to-Left

Compose creates new functions by combining existing ones, executing from right to left in mathematical composition style.


Overview

Compose combines functions in right-to-left order, following mathematical function composition notation: (f ∘ g)(x) = f(g(x)).

Key Characteristics:

  • Right-to-left execution: Inner function executes first
  • Mathematical notation: Matches f ∘ g composition
  • Type-safe: Ensures output of one function matches input of next

For left-to-right composition (more readable in code), use Flow instead.


Basic Composition

compose_basic.go
import F "github.com/IBM/fp-go/function"

// Define simple functions
double := func(n int) int {
  return n * 2
}

addTen := func(n int) int {
  return n + 10
}

// Compose: addTen(double(x))
// Executes right-to-left: double first, then addTen
transform := F.Compose2(addTen, double)

result := transform(5)
// 5 -> double -> 10 -> addTen -> 20

// Equivalent to:
result := addTen(double(5))  // 20

Compose vs Flow

Understanding the difference between Compose and Flow:

Compose (Right-to-Left)

// Mathematical: f ∘ g
c := F.Compose2(f, g)
// Executes: f(g(x))

double := func(n int) int { return n * 2 }
addTen := func(n int) int { return n + 10 }

// Read right-to-left
transform := F.Compose2(addTen, double)
transform(5) // 20
// 5 -> double(5)=10 -> addTen(10)=20

Flow (Left-to-Right)

// Pipeline style
f := F.Flow2(g, f)
// Executes: f(g(x))

double := func(n int) int { return n * 2 }
addTen := func(n int) int { return n + 10 }

// Read left-to-right
transform := F.Flow2(double, addTen)
transform(5) // 20
// 5 -> double(5)=10 -> addTen(10)=20

Both produce the same result, but the order of arguments is reversed. Choose based on readability preference.


Multiple Functions

Compose multiple functions together:

compose_multiple.go
import F "github.com/IBM/fp-go/function"

// Define transformation functions
double := func(n int) int { return n * 2 }
addTen := func(n int) int { return n + 10 }
square := func(n int) int { return n * n }

// Compose three functions
// Executes: square(addTen(double(x)))
transform := F.Compose3(square, addTen, double)

result := transform(5)
// 5 -> double -> 10 -> addTen -> 20 -> square -> 400

// Step by step:
// 1. double(5) = 10
// 2. addTen(10) = 20
// 3. square(20) = 400

String Transformations

Compose string operations:

compose_strings.go
import (
  F "github.com/IBM/fp-go/function"
  "strings"
)

// Define string transformations
trim := func(s string) string {
  return strings.TrimSpace(s)
}

upper := func(s string) string {
  return strings.ToUpper(s)
}

addPrefix := func(s string) string {
  return ">>> " + s
}

// Compose: addPrefix(upper(trim(x)))
normalize := F.Compose3(addPrefix, upper, trim)

result := normalize("  hello world  ")
// "  hello world  " -> trim -> "hello world" 
//                   -> upper -> "HELLO WORLD"
//                   -> addPrefix -> ">>> HELLO WORLD"

Type Transformations

Compose functions that change types:

compose_types.go
import F "github.com/IBM/fp-go/function"

// int -> string
toString := func(n int) string {
  return fmt.Sprintf("%d", n)
}

// string -> int (length)
length := func(s string) int {
  return len(s)
}

// int -> bool
isEven := func(n int) bool {
  return n%2 == 0
}

// Compose: isEven(length(toString(x)))
// int -> string -> int -> bool
check := F.Compose3(isEven, length, toString)

check(42)    // true  (toString="42", len=2, isEven=true)
check(123)   // false (toString="123", len=3, isEven=false)
check(1000)  // true  (toString="1000", len=4, isEven=true)

Validation Pipeline

Build validation chains:

compose_validation.go
type User struct {
  Name  string
  Email string
  Age   int
}

// Validation functions
validateName := func(u User) User {
  if u.Name == "" {
      panic("Name required")
  }
  return u
}

validateEmail := func(u User) User {
  if !strings.Contains(u.Email, "@") {
      panic("Invalid email")
  }
  return u
}

validateAge := func(u User) User {
  if u.Age < 18 {
      panic("Must be 18+")
  }
  return u
}

// Compose validators (right-to-left)
// Executes: validateAge -> validateEmail -> validateName
validate := F.Compose3(validateName, validateEmail, validateAge)

user := User{Name: "Alice", Email: "alice@example.com", Age: 25}
validated := validate(user)  // All validations pass

When to Use Compose

Use Compose when:

  • You prefer mathematical notation
  • Working with mathematical concepts
  • Porting code from languages with operator
  • Building abstract function combinators

Use Flow when:

  • You prefer readable left-to-right pipelines
  • Working with data transformations
  • Building business logic
  • Most practical applications

API Reference

FunctionTypeDescription
Compose2[A, B, C](B -> C, A -> B) -> (A -> C)Compose two functions
Compose3[A, B, C, D](C -> D, B -> C, A -> B) -> (A -> D)Compose three functions
Compose4[A, B, C, D, E](D -> E, C -> D, B -> C, A -> B) -> (A -> E)Compose four functions

Execution Order:

Compose2(f, g)(x) = f(g(x))
Compose3(f, g, h)(x) = f(g(h(x)))

Related Concepts

Function Composition Styles:

  • Compose: Right-to-left (mathematical)
  • Flow: Left-to-right (pipeline)
  • Pipe: Data-first left-to-right

See Also: