Pipe Operator¶
The pipe operator |> enables left-to-right data transformation chains, improving readability over nested function calls.
Syntax¶
The pipe operator passes the left-hand value as the first argument to the right-hand function.
Basic Usage¶
# Instead of nested calls
result = sorted(filter(lambda x: x > 0, map(lambda x: x * 2, items)))
# With pipes - reads left to right
result = items |> map(lambda x: x * 2) |> filter(lambda x: x > 0) |> sorted()
# Single transformation
text = "hello" |> str.upper() # "HELLO"
# With additional arguments
numbers = [1, 2, 3] |> map(lambda x: x * 2) |> list()
Pipeline Chains¶
# Data processing pipeline
data = load_data()
result = data |> parse_json() |> validate() |> transform() |> save()
# Equivalent to:
# result = save(transform(validate(parse_json(load_data()))))
# With method chains
user = get_user(id) |> update_email(new_email) |> save_to_db()
Argument Position¶
The piped value becomes the first argument to the function:
# Piping to a function with multiple parameters
def format_with_prefix(text: str, prefix: str) -> str:
return f"{prefix}: {text}"
result = "hello" |> format_with_prefix("MESSAGE")
# Equivalent to: format_with_prefix("hello", "MESSAGE")
# Result: "MESSAGE: hello"
# Combining with partial application (see partial_application.md)
add_prefix = format_with_prefix(_, ">>>")
result = "test" |> add_prefix # ">>>: test"
Precedence¶
The pipe operator has low precedence, binding looser than most operators but tighter than assignment:
# Arithmetic before pipe
result = 5 + 3 |> str() # Equivalent to: (5 + 3) |> str() = "8"
# Comparison before pipe
flag = value > 0 |> not() # Equivalent to: (value > 0) |> not()
# Use parentheses for clarity
result = (x + y) |> str() |> print()
Method Chaining¶
The pipe operator works naturally with methods:
# Chaining methods and functions
result = " hello " |> str.strip() |> str.upper() |> print()
# Mixed method calls and free functions
data |> parse() |> data.validate() |> transform() |> data.save()
Type Safety¶
The pipe operator is fully type-checked at compile time:
# Type mismatch caught at compile time
result = 42 |> str.upper() # ERROR: str.upper() expects str, got int
# Correct types
result = 42 |> str() |> str.upper() # OK: "42"
Common Patterns¶
Data Transformation:
# Transform collection
results = items |> map(transform) |> filter(is_valid) |> list()
# Process text
output = text |> str.strip() |> str.lower() |> parse_words()
Option/Result Chaining:
# Chaining operations on Optional/Result types
value = get_user(id) |> user.get_profile() |> profile.get_email()
Validation Chains:
# Sequential validation
user_data |> validate_email() |> validate_age() |> validate_address() |> save()
Comparison with Other Operators¶
| Syntax | Meaning |
|---|---|
a \|> f() |
Pipe a as first arg to f() |
f(a) |
Regular function call |
a.method() |
Method call |
a ?? b |
Null coalescing |
Implementation: 🔄 Lowered - Compiler transformation to function call:
With Method Calls:
Limitations¶
- Cannot pipe to constructors directly (use factory function instead)
- Cannot pipe to operators (use lambda or function wrapper)
- Piped value always becomes first argument (use partial application to reorder)