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

Pipe & Flow

Function composition for readable data transformations. Pipe and Flow enable left-to-right composition, making code more readable than nested function calls.


Core API

FunctionSignatureDescription
Pipe2func Pipe2[A, B, C any](A, func(A) B, func(B) C) CApply value through 2 functions
Pipe3func Pipe3[A, B, C, D any](A, func(A) B, func(B) C, func(C) D) DApply through 3 functions
Pipe4-9Similar patternUp to 9 functions
Flow2func Flow2[A, B, C any](func(A) B, func(B) C) func(A) CCompose 2 functions
Flow3func Flow3[A, B, C, D any](func(A) B, func(B) C, func(C) D) func(A) DCompose 3 functions
Flow4-9Similar patternUp to 9 functions

Usage Examples

Pipe - Immediate Execution

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

// Instead of: g(f(x))
result := F.Pipe2(
  x,
  f,  // First transformation
  g,  // Second transformation
)

// Instead of: h(g(f(x)))
result := F.Pipe3(
  x,
  f,
  g,
  h,
)

// Up to Pipe9 available
result := F.Pipe5(x, f1, f2, f3, f4, f5)

Flow - Reusable Pipeline

flow.go
// Create composed function
transform := F.Flow2(
  f,  // A -> B
  g,  // B -> C
)   // Returns: A -> C

// Use later
result := transform(x)

// Compose multiple functions
pipeline := F.Flow4(
  parse,      // string -> int
  validate,   // int -> Result[int]
  transform,  // Result[int] -> Result[string]
  format,     // Result[string] -> string
)

output := pipeline(input)

Data Processing Pipeline

data_pipeline.go
import (
  A "github.com/IBM/fp-go/v2/array"
  O "github.com/IBM/fp-go/v2/option"
)

type User struct {
  Name  string
  Age   int
  Email string
}

users := []User{
  {Name: "Alice", Age: 30, Email: "alice@example.com"},
  {Name: "Bob", Age: 17, Email: ""},
  {Name: "Charlie", Age: 25, Email: "charlie@example.com"},
}

// Process users: filter adults, extract emails, remove empty
emails := F.Pipe3(
  users,
  A.Filter(func(u User) bool { return u.Age >= 18 }),
  A.Map(func(u User) string { return u.Email }),
  A.Filter(func(e string) bool { return e != "" }),
)
// []string{"alice@example.com", "charlie@example.com"}

String Transformation

string.go
import S "github.com/IBM/fp-go/v2/string"

// Clean and format string
cleanString := F.Flow3(
  strings.TrimSpace,
  strings.ToLower,
  func(s string) string {
      return strings.ReplaceAll(s, " ", "-")
  },
)

slug := cleanString("  Hello World  ")
// "hello-world"

API Request Processing

api.go
type Request struct {
  Body string
}

type Response struct {
  Status int
  Data   string
}

// Create processing pipeline
processRequest := F.Flow4(
  parseBody,      // Request -> Result[Data]
  validateData,   // Result[Data] -> Result[Data]
  transformData,  // Result[Data] -> Result[string]
  formatResponse, // Result[string] -> Response
)

// Use pipeline
response := processRequest(request)

Validation Chain

validation.go
import R "github.com/IBM/fp-go/v2/result"

type ValidationError struct {
  Field   string
  Message string
}

// Compose validators
validateUser := F.Flow3(
  validateEmail,              // User -> Result[User]
  R.Chain(validateAge),       // Result[User] -> Result[User]
  R.Chain(validateName),      // Result[User] -> Result[User]
)

result := validateUser(user)
// Result[User] - Success or first error

Complex Product Processing

products.go
type Product struct {
  ID    int
  Name  string
  Price float64
  Tags  []string
}

// Process products
result := F.Pipe5(
  products,
  // Filter by price range
  A.Filter(func(p Product) bool {
      return p.Price >= 10 && p.Price <= 100
  }),
  // Sort by price
  A.Sort(productPriceOrd),
  // Add discount
  A.Map(func(p Product) Product {
      p.Price = p.Price * 0.9
      return p
  }),
  // Extract names
  A.Map(func(p Product) string { return p.Name }),
  // Take first 10
  A.Slice(0, 10),
)

Error Handling Pipeline

error_handling.go
// Safe division with validation
safeDivide := F.Flow3(
  // Validate denominator
  func(args [2]int) R.Result[[2]int] {
      if args[1] == 0 {
          return R.Error[[2]int](errors.New("division by zero"))
      }
      return R.Success(args)
  },
  // Perform division
  R.Map(func(args [2]int) float64 {
      return float64(args[0]) / float64(args[1])
  }),
  // Round result
  R.Map(func(f float64) float64 {
      return math.Round(f*100) / 100
  }),
)

result := safeDivide([2]int{10, 3})
// Success(3.33)

Common Patterns

Pipe vs Flow

comparison.go
// Pipe - immediate execution with value
result := F.Pipe3(
  initialValue,
  step1,
  step2,
  step3,
)

// Flow - create reusable pipeline
pipeline := F.Flow3(
  step1,
  step2,
  step3,
)

// Use multiple times
result1 := pipeline(value1)
result2 := pipeline(value2)

Optional Transformation

optional.go
// Transform if present
result := F.Pipe3(
  maybeValue,
  O.Map(transform),
  O.GetOrElse(F.Constant(defaultValue)),
)

Array Processing Pattern

array_pattern.go
// Filter, map, reduce pattern
total := F.Pipe3(
  items,
  A.Filter(predicate),
  A.Map(extract),
  A.Reduce(sum, 0),
)

Conditional Pipeline

conditional.go
// Different paths based on condition
process := func(useAdvanced bool) func(Data) Result {
  if useAdvanced {
      return F.Flow3(validate, advancedTransform, format)
  }
  return F.Flow2(validate, simpleTransform)
}

result := process(true)(data)

When to Use:

  • Pipe: When you have a value and want to transform it immediately
  • Flow: When you want to create a reusable transformation pipeline

Readability: Pipe and Flow make code more readable by showing the transformation flow from left to right, matching how we naturally read code.

Performance: Each Pipe/Flow call has minimal overhead. For performance-critical code with simple transformations, direct function calls may be faster.