fluent-chaining


Namefluent-chaining JSON
Version 0.1.0 PyPI version JSON
download
home_pageNone
SummaryA Python package for pure functional programming with fluent chaining that reads like prose
upload_time2025-08-12 23:21:00
maintainerNone
docs_urlNone
authorNone
requires_python>=3.9
licenseMIT
keywords functional chaining fluent pure-functions compose
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Fluent Chaining

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)

> Pure functional programming with fluent chaining that reads like prose.

Fluent Chaining is a Python package that enables functional programming with method chaining that reads like natural language, making your code more expressive, readable, and maintainable.

## โœจ Why Fluent Chaining?

Traditional functional programming in Python can be verbose and hard to read:

```python
# Traditional approach - hard to read
from functools import reduce

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
result = reduce(
    lambda acc, x: acc + x,
    map(lambda x: x ** 2,
        filter(lambda x: x % 2 == 0, numbers)),
    0
)
```

With Fluent Chaining, the same operation reads like prose:

```python
# Fluent Chaining - reads like natural language
from fluent_chaining import take

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
result = (take(numbers)
    .where(lambda x: x % 2 == 0)
    .transform(lambda x: x ** 2)
    .sum()
    .value())
```

## ๐Ÿš€ Installation (Project-Saturday Members Only)

### Quick Setup (Recommended)
```bash
# 1. Set up your credentials (one-time)
cp docs/setup/team-secrets.template team-secrets.env
# Edit team-secrets.env with your GitHub PAT token

# 2. Run automated setup
python scripts/setup-team-environment.py

# 3. Install the package
pip install fluent-chaining
```

### Alternative Installation Methods

**GitHub Packages (manual setup):**
```bash
# Configure pip for GitHub Packages
pip config set global.extra-index-url https://pypi.pkg.github.com/Project-Saturday/simple/
pip config set global.trusted-host pypi.pkg.github.com

# Install with your .pypirc configured
pip install fluent-chaining
```

**Direct Git Installation:**
```bash
# Install latest from git
pip install git+https://github.com/Project-Saturday/fluent-chaining.git

# Install specific version
pip install git+https://github.com/Project-Saturday/fluent-chaining.git@v0.1.0
```

> **Note:** This is a private Project-Saturday library. You'll need a GitHub Personal Access Token with `packages:read` permission. See [`docs/setup/setup-github-packages.md`](docs/setup/setup-github-packages.md) for detailed setup instructions.

## ๐Ÿ“– Basic Usage

### Getting Started

```python
from fluent_chaining import take

# Start with data using take()
data = [1, 2, 3, 4, 5]
result = take(data).transform(lambda x: x * 2).value()
# Result: [2, 4, 6, 8, 10]
```

### Core Operations

#### Filtering with Natural Language

```python
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# Using prose-like methods
even_numbers = (take(numbers)
    .where(lambda x: x % 2 == 0)
    .value())

# Even more natural
large_numbers = (take(numbers)
    .that_are_greater_than(5)
    .value())

# Chaining conditions
result = (take(numbers)
    .where(lambda x: x % 2 == 0)
    .that_are_greater_than(4)
    .value())
# Result: [6, 8, 10]
```

#### Transformations

```python
numbers = [1, 2, 3, 4, 5]

# Mathematical operations with readable syntax
result = (take(numbers)
    .transform(lambda x: x * 2)
    .plus(1)
    .value())
# Result: [3, 5, 7, 9, 11]

# Multiple transformations
result = (take(numbers)
    .multiplied_by(3)
    .minus(1)
    .divided_by(2)
    .value())
# Result: [1.0, 2.5, 4.0, 5.5, 7.0]
```

#### Aggregations

```python
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# Simple aggregations
total = take(numbers).sum().value()
count = take(numbers).count().value()
maximum = take(numbers).max().value()
minimum = take(numbers).min().value()

# Complex chained operations
average_of_squares = (take(numbers)
    .where(lambda x: x % 2 == 0)
    .transform(lambda x: x ** 2)
    .sum()
    .divided_by(take(numbers).where(lambda x: x % 2 == 0).count().value())
    .value())
```

## ๐ŸŽฏ Real-World Examples

### Data Processing Pipeline

```python
from fluent_chaining import take

# Process a list of user records
users = [
    {"name": "Alice", "age": 25, "city": "New York", "salary": 50000},
    {"name": "Bob", "age": 30, "city": "San Francisco", "salary": 75000},
    {"name": "Charlie", "age": 35, "city": "New York", "salary": 60000},
    {"name": "Diana", "age": 28, "city": "Boston", "salary": 55000},
]

# Find the average salary of users over 25 in New York
average_salary = (take(users)
    .where(lambda user: user["age"] > 25)
    .where(lambda user: user["city"] == "New York")
    .transform(lambda user: user["salary"])
    .sum()
    .divided_by(
        take(users)
        .where(lambda user: user["age"] > 25)
        .where(lambda user: user["city"] == "New York")
        .count()
        .value()
    )
    .value())
```

### Text Processing

```python
text = ["hello", "world", "this", "is", "a", "test"]

# Process text with readable operations
result = (take(text)
    .where(lambda word: len(word) > 3)
    .transform(str.upper)
    .transform(lambda word: f"[{word}]")
    .value())
# Result: ['[HELLO]', '[WORLD]', '[THIS]', '[TEST]']
```

### Mathematical Computations

```python
# Calculate factorial using fluent chaining
def factorial(n):
    return (take(range(1, n + 1))
        .reduce(lambda acc, x: acc * x, 1)
        .value())

# Find prime numbers with descriptive operations
def is_prime(n):
    if n < 2:
        return False
    return (take(range(2, int(n ** 0.5) + 1))
        .where(lambda x: n % x == 0)
        .count()
        .value()) == 0

primes = (take(range(2, 20))
    .where(is_prime)
    .value())
```

## ๐Ÿ”ง Advanced Features

### Working with Collections

```python
nested_data = [[1, 2], [3, 4], [5, 6]]

# Flatten and process
result = (take(nested_data)
    .flatten()
    .where(lambda x: x % 2 == 0)
    .value())
# Result: [2, 4, 6]

# Remove duplicates
data_with_dupes = [1, 2, 2, 3, 3, 3, 4]
unique_values = (take(data_with_dupes)
    .distinct()
    .value())
# Result: [1, 2, 3, 4]
```

### Sorting and Ordering

```python
people = [
    {"name": "Alice", "age": 25},
    {"name": "Bob", "age": 30},
    {"name": "Charlie", "age": 20},
]

# Sort by age
sorted_people = (take(people)
    .sort(key=lambda person: person["age"])
    .value())

# Reverse order
reversed_ages = (take(people)
    .transform(lambda person: person["age"])
    .sort(reverse=True)
    .value())
```

### Grouping Operations

```python
students = [
    {"name": "Alice", "grade": "A"},
    {"name": "Bob", "grade": "B"},
    {"name": "Charlie", "grade": "A"},
    {"name": "Diana", "grade": "B"},
]

# Group students by grade
groups = (take(students)
    .group_by(lambda student: student["grade"])
    .value())
```

## ๐Ÿ› ๏ธ Method Reference

### Entry Points
- `take(data)` - Start a fluent chain with data
- `chain(data)` - Alias for `take()`

### Filtering Methods
- `.where(predicate)` - Filter elements that satisfy the predicate
- `.filter(predicate)` - Alias for `where()`
- `.that_are(predicate)` - Natural language filtering
- `.that_are_greater_than(value)` - Filter elements > value
- `.that_are_less_than(value)` - Filter elements < value
- `.that_equal(value)` - Filter elements == value

### Transformation Methods
- `.transform(func)` - Transform each element
- `.map(func)` - Alias for `transform()`
- `.multiplied_by(factor)` - Multiply by factor
- `.divided_by(divisor)` - Divide by divisor
- `.plus(addend)` - Add value
- `.minus(subtrahend)` - Subtract value

### Aggregation Methods
- `.sum()` - Sum all elements
- `.count()` - Count elements
- `.length()` - Alias for `count()`
- `.max()` - Maximum element
- `.min()` - Minimum element
- `.first()` - First element
- `.last()` - Last element

### Collection Methods
- `.take(n)` - Take first n elements
- `.skip(n)` - Skip first n elements
- `.distinct()` - Remove duplicates
- `.unique()` - Alias for `distinct()`
- `.reverse()` - Reverse order
- `.sort(key=None, reverse=False)` - Sort elements
- `.flatten()` - Flatten nested collections
- `.group_by(key_func)` - Group by key function

### Reduction Methods
- `.reduce(func, initial=None)` - Reduce to single value
- `.fold(func, initial)` - Fold with initial value

### Output Methods
- `.value()` - Get the final result
- `.to_list()` - Convert to list

## ๐Ÿงช Advanced Functional Programming

The package includes comprehensive utilities for advanced functional programming:

### Function Composition and Utilities

```python
from fluent_chaining import compose, pipe, curry, partial, memoize

# Function composition
add_one = lambda x: x + 1
multiply_by_two = lambda x: x * 2
composed = compose(multiply_by_two, add_one)

result = take([1, 2, 3]).transform(composed).value()
# Result: [4, 6, 8]

# Function piping (left to right)
piped = pipe(add_one, multiply_by_two)
result = take([1, 2, 3]).transform(piped).value()
# Result: [4, 6, 8]

# Currying
@curry
def add_three_numbers(x, y, z):
    return x + y + z

add_five = add_three_numbers(2)(3)
result = take([1, 2, 3]).transform(add_five).value()
# Result: [6, 7, 8]

# Memoization for expensive computations
@memoize
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

fib_sequence = take(range(10)).transform(fibonacci).value()
# Result: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
```

### Advanced Chain Methods

```python
# Debugging and side effects
result = (take([1, 2, 3, 4, 5])
    .where(lambda x: x % 2 == 0)
    .debug("Even numbers")  # Prints: Debug: [2, 4]
    .transform(lambda x: x ** 2)
    .side_effect(lambda data: print(f"Squared: {data}"))
    .sum()
    .value())

# Conditional processing
def double_if_small(chain):
    return chain.transform(lambda x: x * 2)

result = (take([1, 2, 3])
    .apply_if(True, double_if_small)  # Apply transformation conditionally
    .apply_when(lambda data: sum(data) > 10, lambda c: c.plus(5))  # Apply when condition met
    .value())

# Branching logic
result = (take([1, 2, 3, 4, 5])
    .branch(
        len([1, 2, 3, 4, 5]) > 3,
        lambda c: c.take(3),  # If true: take first 3
        lambda c: c.reverse()  # If false: reverse
    )
    .sum()
    .value())

# Error handling and defaults
result = (take([])
    .or_else([1, 2, 3])  # Use default if empty
    .assert_that(lambda data: len(data) > 0, "Must not be empty")
    .sum()
    .value())
```

### Quantification and Higher-Order Functions

```python
from fluent_chaining import always, exists, identity, constant

# Universal and existential quantification
is_positive = lambda x: x > 0
all_positive = always(is_positive)
any_positive = exists(is_positive)

result1 = all_positive([1, 2, 3])  # True
result2 = any_positive([-1, 0, 1])  # True

# Utility functions
numbers = [1, 2, 3, 4, 5]
result = (take(numbers)
    .transform(identity)  # No-op transformation
    .where(constant(True))  # Always true predicate
    .value())
```

## ๐Ÿš€ Monadic Types (.NET-Style Functional Programming)

Inspired by .NET's functional programming patterns, we provide `Result<T>` and `Option<T>` types for robust error and null handling.

### Result<T> - Functional Error Handling

No more exception handling - make errors explicit and composable:

```python
from fluent_chaining import Result, Ok, Err, safe, try_parse_int

# Safe operations that return Result<T>
def safe_divide(x, y):
    if y == 0:
        return Err("Division by zero")
    return Ok(x / y)

# Chain operations with .then() - like .NET's Result.Then()
result = (safe_divide(10, 2)
    .then(lambda x: safe_divide(x, 2))
    .then(lambda x: Ok(x + 1))
    .match(
        ok_func=lambda value: f"Success: {value}",
        err_func=lambda error: f"Error: {error}"
    ))
# Result: "Success: 3.5"

# Convert exception-throwing functions with @safe decorator
@safe
def risky_operation(value):
    if value < 0:
        raise ValueError("Negative value not allowed")
    return value * 2

results = [risky_operation(x) for x in [1, -2, 3]]
successful_values = [r.unwrap() for r in results if r.is_ok()]
# successful_values: [2, 6]

# Parse and process data safely
input_data = ["10", "20", "invalid", "30"]
parsed_results = [try_parse_int(s) for s in input_data]

total = (take([r.unwrap() for r in parsed_results if r.is_ok()])
    .sum()
    .value())
# total: 60 (10 + 20 + 30, "invalid" safely ignored)
```

### Option<T> - Functional Null Handling

Handle null/missing values functionally with pattern matching:

```python
from fluent_chaining import Some, Nothing, from_nullable, from_dict

# Create Options
user_name = Some("Alice")
missing_data = Nothing()

# Pattern matching - like .NET's Option.Match()
def process_user(name_option):
    return name_option.match(
        some_func=lambda name: f"Processing user: {name}",
        none_func=lambda: "No user to process"
    )

print(process_user(user_name))    # "Processing user: Alice"
print(process_user(missing_data)) # "No user to process"

# Safe data access
user_data = {"name": "Bob", "profile": {"age": 30}}

age_message = (from_dict(user_data, "profile")
    .then(lambda profile: from_dict(profile, "age"))
    .match(
        some_func=lambda age: f"User is {age} years old",
        none_func=lambda: "Age not available"
    ))
# Result: "User is 30 years old"

# Chain multiple optional operations
def get_user_email_domain(user):
    return (from_dict(user, "email")
        .map(lambda email: email.split("@"))
        .filter(lambda parts: len(parts) == 2)
        .map(lambda parts: parts[1])
        .unwrap_or("unknown"))

users = [
    {"email": "alice@example.com"},
    {"email": "invalid-email"},
    {"name": "Bob"}  # No email
]

domains = [get_user_email_domain(user) for user in users]
# domains: ["example.com", "unknown", "unknown"]
```

### Integration with Fluent Chains

Convert between chains and monadic types seamlessly:

```python
# Chain to Result/Option
numbers = [1, 2, 3, 4, 5]
result = (take(numbers)
    .where(lambda x: x > 10)
    .to_result("No large numbers found"))

result.match(
    ok_func=lambda values: f"Found: {values}",
    err_func=lambda error: f"Error: {error}"
)
# Result: "Error: No large numbers found"

# Option to Chain
optional_data = Some([1, 2, 3])
total = (from_option(optional_data, [])
    .sum()
    .value())
# total: 6

# Complex pipeline with error handling
def safe_process_scores(scores_text):
    return (try_parse_int(scores_text)
        .filter(lambda score: 0 <= score <= 100, "Score out of range")
        .map(lambda score: score * 1.1)  # Apply 10% bonus
        .map(lambda score: min(score, 100)))  # Cap at 100

test_scores = ["85", "95", "invalid", "105", "-5"]
processed = [safe_process_scores(s) for s in test_scores]

# Extract successful scores
final_scores = [r.unwrap() for r in processed if r.is_ok()]
average = take(final_scores).sum().value() / len(final_scores) if final_scores else 0
# average: ~96.5 (from 85*1.1=93.5 and 95*1.1=100 capped)
```

## ๐Ÿค Contributing

We welcome contributions! Please see our [Contributing Guidelines](CONTRIBUTING.md) for details.

## ๐Ÿ“„ License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

## ๐ŸŒŸ Project Saturday

This package is part of the [Project Saturday](https://github.com/Project-Saturday) organization, dedicated to creating tools that make programming more expressive and enjoyable.

---

*Made with โค๏ธ by the Project Saturday team*

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "fluent-chaining",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.9",
    "maintainer_email": null,
    "keywords": "functional, chaining, fluent, pure-functions, compose",
    "author": null,
    "author_email": "Project-Saturday <contact@project-saturday.org>",
    "download_url": "https://files.pythonhosted.org/packages/d6/d1/6c10093bc6599b6ed32a3153823ffd48acd04dfe6be5e862e1abb41da572/fluent_chaining-0.1.0.tar.gz",
    "platform": null,
    "description": "# Fluent Chaining\n\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)\n\n> Pure functional programming with fluent chaining that reads like prose.\n\nFluent Chaining is a Python package that enables functional programming with method chaining that reads like natural language, making your code more expressive, readable, and maintainable.\n\n## \u2728 Why Fluent Chaining?\n\nTraditional functional programming in Python can be verbose and hard to read:\n\n```python\n# Traditional approach - hard to read\nfrom functools import reduce\n\nnumbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\nresult = reduce(\n    lambda acc, x: acc + x,\n    map(lambda x: x ** 2,\n        filter(lambda x: x % 2 == 0, numbers)),\n    0\n)\n```\n\nWith Fluent Chaining, the same operation reads like prose:\n\n```python\n# Fluent Chaining - reads like natural language\nfrom fluent_chaining import take\n\nnumbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\nresult = (take(numbers)\n    .where(lambda x: x % 2 == 0)\n    .transform(lambda x: x ** 2)\n    .sum()\n    .value())\n```\n\n## \ud83d\ude80 Installation (Project-Saturday Members Only)\n\n### Quick Setup (Recommended)\n```bash\n# 1. Set up your credentials (one-time)\ncp docs/setup/team-secrets.template team-secrets.env\n# Edit team-secrets.env with your GitHub PAT token\n\n# 2. Run automated setup\npython scripts/setup-team-environment.py\n\n# 3. Install the package\npip install fluent-chaining\n```\n\n### Alternative Installation Methods\n\n**GitHub Packages (manual setup):**\n```bash\n# Configure pip for GitHub Packages\npip config set global.extra-index-url https://pypi.pkg.github.com/Project-Saturday/simple/\npip config set global.trusted-host pypi.pkg.github.com\n\n# Install with your .pypirc configured\npip install fluent-chaining\n```\n\n**Direct Git Installation:**\n```bash\n# Install latest from git\npip install git+https://github.com/Project-Saturday/fluent-chaining.git\n\n# Install specific version\npip install git+https://github.com/Project-Saturday/fluent-chaining.git@v0.1.0\n```\n\n> **Note:** This is a private Project-Saturday library. You'll need a GitHub Personal Access Token with `packages:read` permission. See [`docs/setup/setup-github-packages.md`](docs/setup/setup-github-packages.md) for detailed setup instructions.\n\n## \ud83d\udcd6 Basic Usage\n\n### Getting Started\n\n```python\nfrom fluent_chaining import take\n\n# Start with data using take()\ndata = [1, 2, 3, 4, 5]\nresult = take(data).transform(lambda x: x * 2).value()\n# Result: [2, 4, 6, 8, 10]\n```\n\n### Core Operations\n\n#### Filtering with Natural Language\n\n```python\nnumbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n\n# Using prose-like methods\neven_numbers = (take(numbers)\n    .where(lambda x: x % 2 == 0)\n    .value())\n\n# Even more natural\nlarge_numbers = (take(numbers)\n    .that_are_greater_than(5)\n    .value())\n\n# Chaining conditions\nresult = (take(numbers)\n    .where(lambda x: x % 2 == 0)\n    .that_are_greater_than(4)\n    .value())\n# Result: [6, 8, 10]\n```\n\n#### Transformations\n\n```python\nnumbers = [1, 2, 3, 4, 5]\n\n# Mathematical operations with readable syntax\nresult = (take(numbers)\n    .transform(lambda x: x * 2)\n    .plus(1)\n    .value())\n# Result: [3, 5, 7, 9, 11]\n\n# Multiple transformations\nresult = (take(numbers)\n    .multiplied_by(3)\n    .minus(1)\n    .divided_by(2)\n    .value())\n# Result: [1.0, 2.5, 4.0, 5.5, 7.0]\n```\n\n#### Aggregations\n\n```python\nnumbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]\n\n# Simple aggregations\ntotal = take(numbers).sum().value()\ncount = take(numbers).count().value()\nmaximum = take(numbers).max().value()\nminimum = take(numbers).min().value()\n\n# Complex chained operations\naverage_of_squares = (take(numbers)\n    .where(lambda x: x % 2 == 0)\n    .transform(lambda x: x ** 2)\n    .sum()\n    .divided_by(take(numbers).where(lambda x: x % 2 == 0).count().value())\n    .value())\n```\n\n## \ud83c\udfaf Real-World Examples\n\n### Data Processing Pipeline\n\n```python\nfrom fluent_chaining import take\n\n# Process a list of user records\nusers = [\n    {\"name\": \"Alice\", \"age\": 25, \"city\": \"New York\", \"salary\": 50000},\n    {\"name\": \"Bob\", \"age\": 30, \"city\": \"San Francisco\", \"salary\": 75000},\n    {\"name\": \"Charlie\", \"age\": 35, \"city\": \"New York\", \"salary\": 60000},\n    {\"name\": \"Diana\", \"age\": 28, \"city\": \"Boston\", \"salary\": 55000},\n]\n\n# Find the average salary of users over 25 in New York\naverage_salary = (take(users)\n    .where(lambda user: user[\"age\"] > 25)\n    .where(lambda user: user[\"city\"] == \"New York\")\n    .transform(lambda user: user[\"salary\"])\n    .sum()\n    .divided_by(\n        take(users)\n        .where(lambda user: user[\"age\"] > 25)\n        .where(lambda user: user[\"city\"] == \"New York\")\n        .count()\n        .value()\n    )\n    .value())\n```\n\n### Text Processing\n\n```python\ntext = [\"hello\", \"world\", \"this\", \"is\", \"a\", \"test\"]\n\n# Process text with readable operations\nresult = (take(text)\n    .where(lambda word: len(word) > 3)\n    .transform(str.upper)\n    .transform(lambda word: f\"[{word}]\")\n    .value())\n# Result: ['[HELLO]', '[WORLD]', '[THIS]', '[TEST]']\n```\n\n### Mathematical Computations\n\n```python\n# Calculate factorial using fluent chaining\ndef factorial(n):\n    return (take(range(1, n + 1))\n        .reduce(lambda acc, x: acc * x, 1)\n        .value())\n\n# Find prime numbers with descriptive operations\ndef is_prime(n):\n    if n < 2:\n        return False\n    return (take(range(2, int(n ** 0.5) + 1))\n        .where(lambda x: n % x == 0)\n        .count()\n        .value()) == 0\n\nprimes = (take(range(2, 20))\n    .where(is_prime)\n    .value())\n```\n\n## \ud83d\udd27 Advanced Features\n\n### Working with Collections\n\n```python\nnested_data = [[1, 2], [3, 4], [5, 6]]\n\n# Flatten and process\nresult = (take(nested_data)\n    .flatten()\n    .where(lambda x: x % 2 == 0)\n    .value())\n# Result: [2, 4, 6]\n\n# Remove duplicates\ndata_with_dupes = [1, 2, 2, 3, 3, 3, 4]\nunique_values = (take(data_with_dupes)\n    .distinct()\n    .value())\n# Result: [1, 2, 3, 4]\n```\n\n### Sorting and Ordering\n\n```python\npeople = [\n    {\"name\": \"Alice\", \"age\": 25},\n    {\"name\": \"Bob\", \"age\": 30},\n    {\"name\": \"Charlie\", \"age\": 20},\n]\n\n# Sort by age\nsorted_people = (take(people)\n    .sort(key=lambda person: person[\"age\"])\n    .value())\n\n# Reverse order\nreversed_ages = (take(people)\n    .transform(lambda person: person[\"age\"])\n    .sort(reverse=True)\n    .value())\n```\n\n### Grouping Operations\n\n```python\nstudents = [\n    {\"name\": \"Alice\", \"grade\": \"A\"},\n    {\"name\": \"Bob\", \"grade\": \"B\"},\n    {\"name\": \"Charlie\", \"grade\": \"A\"},\n    {\"name\": \"Diana\", \"grade\": \"B\"},\n]\n\n# Group students by grade\ngroups = (take(students)\n    .group_by(lambda student: student[\"grade\"])\n    .value())\n```\n\n## \ud83d\udee0\ufe0f Method Reference\n\n### Entry Points\n- `take(data)` - Start a fluent chain with data\n- `chain(data)` - Alias for `take()`\n\n### Filtering Methods\n- `.where(predicate)` - Filter elements that satisfy the predicate\n- `.filter(predicate)` - Alias for `where()`\n- `.that_are(predicate)` - Natural language filtering\n- `.that_are_greater_than(value)` - Filter elements > value\n- `.that_are_less_than(value)` - Filter elements < value\n- `.that_equal(value)` - Filter elements == value\n\n### Transformation Methods\n- `.transform(func)` - Transform each element\n- `.map(func)` - Alias for `transform()`\n- `.multiplied_by(factor)` - Multiply by factor\n- `.divided_by(divisor)` - Divide by divisor\n- `.plus(addend)` - Add value\n- `.minus(subtrahend)` - Subtract value\n\n### Aggregation Methods\n- `.sum()` - Sum all elements\n- `.count()` - Count elements\n- `.length()` - Alias for `count()`\n- `.max()` - Maximum element\n- `.min()` - Minimum element\n- `.first()` - First element\n- `.last()` - Last element\n\n### Collection Methods\n- `.take(n)` - Take first n elements\n- `.skip(n)` - Skip first n elements\n- `.distinct()` - Remove duplicates\n- `.unique()` - Alias for `distinct()`\n- `.reverse()` - Reverse order\n- `.sort(key=None, reverse=False)` - Sort elements\n- `.flatten()` - Flatten nested collections\n- `.group_by(key_func)` - Group by key function\n\n### Reduction Methods\n- `.reduce(func, initial=None)` - Reduce to single value\n- `.fold(func, initial)` - Fold with initial value\n\n### Output Methods\n- `.value()` - Get the final result\n- `.to_list()` - Convert to list\n\n## \ud83e\uddea Advanced Functional Programming\n\nThe package includes comprehensive utilities for advanced functional programming:\n\n### Function Composition and Utilities\n\n```python\nfrom fluent_chaining import compose, pipe, curry, partial, memoize\n\n# Function composition\nadd_one = lambda x: x + 1\nmultiply_by_two = lambda x: x * 2\ncomposed = compose(multiply_by_two, add_one)\n\nresult = take([1, 2, 3]).transform(composed).value()\n# Result: [4, 6, 8]\n\n# Function piping (left to right)\npiped = pipe(add_one, multiply_by_two)\nresult = take([1, 2, 3]).transform(piped).value()\n# Result: [4, 6, 8]\n\n# Currying\n@curry\ndef add_three_numbers(x, y, z):\n    return x + y + z\n\nadd_five = add_three_numbers(2)(3)\nresult = take([1, 2, 3]).transform(add_five).value()\n# Result: [6, 7, 8]\n\n# Memoization for expensive computations\n@memoize\ndef fibonacci(n):\n    if n < 2:\n        return n\n    return fibonacci(n-1) + fibonacci(n-2)\n\nfib_sequence = take(range(10)).transform(fibonacci).value()\n# Result: [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]\n```\n\n### Advanced Chain Methods\n\n```python\n# Debugging and side effects\nresult = (take([1, 2, 3, 4, 5])\n    .where(lambda x: x % 2 == 0)\n    .debug(\"Even numbers\")  # Prints: Debug: [2, 4]\n    .transform(lambda x: x ** 2)\n    .side_effect(lambda data: print(f\"Squared: {data}\"))\n    .sum()\n    .value())\n\n# Conditional processing\ndef double_if_small(chain):\n    return chain.transform(lambda x: x * 2)\n\nresult = (take([1, 2, 3])\n    .apply_if(True, double_if_small)  # Apply transformation conditionally\n    .apply_when(lambda data: sum(data) > 10, lambda c: c.plus(5))  # Apply when condition met\n    .value())\n\n# Branching logic\nresult = (take([1, 2, 3, 4, 5])\n    .branch(\n        len([1, 2, 3, 4, 5]) > 3,\n        lambda c: c.take(3),  # If true: take first 3\n        lambda c: c.reverse()  # If false: reverse\n    )\n    .sum()\n    .value())\n\n# Error handling and defaults\nresult = (take([])\n    .or_else([1, 2, 3])  # Use default if empty\n    .assert_that(lambda data: len(data) > 0, \"Must not be empty\")\n    .sum()\n    .value())\n```\n\n### Quantification and Higher-Order Functions\n\n```python\nfrom fluent_chaining import always, exists, identity, constant\n\n# Universal and existential quantification\nis_positive = lambda x: x > 0\nall_positive = always(is_positive)\nany_positive = exists(is_positive)\n\nresult1 = all_positive([1, 2, 3])  # True\nresult2 = any_positive([-1, 0, 1])  # True\n\n# Utility functions\nnumbers = [1, 2, 3, 4, 5]\nresult = (take(numbers)\n    .transform(identity)  # No-op transformation\n    .where(constant(True))  # Always true predicate\n    .value())\n```\n\n## \ud83d\ude80 Monadic Types (.NET-Style Functional Programming)\n\nInspired by .NET's functional programming patterns, we provide `Result<T>` and `Option<T>` types for robust error and null handling.\n\n### Result<T> - Functional Error Handling\n\nNo more exception handling - make errors explicit and composable:\n\n```python\nfrom fluent_chaining import Result, Ok, Err, safe, try_parse_int\n\n# Safe operations that return Result<T>\ndef safe_divide(x, y):\n    if y == 0:\n        return Err(\"Division by zero\")\n    return Ok(x / y)\n\n# Chain operations with .then() - like .NET's Result.Then()\nresult = (safe_divide(10, 2)\n    .then(lambda x: safe_divide(x, 2))\n    .then(lambda x: Ok(x + 1))\n    .match(\n        ok_func=lambda value: f\"Success: {value}\",\n        err_func=lambda error: f\"Error: {error}\"\n    ))\n# Result: \"Success: 3.5\"\n\n# Convert exception-throwing functions with @safe decorator\n@safe\ndef risky_operation(value):\n    if value < 0:\n        raise ValueError(\"Negative value not allowed\")\n    return value * 2\n\nresults = [risky_operation(x) for x in [1, -2, 3]]\nsuccessful_values = [r.unwrap() for r in results if r.is_ok()]\n# successful_values: [2, 6]\n\n# Parse and process data safely\ninput_data = [\"10\", \"20\", \"invalid\", \"30\"]\nparsed_results = [try_parse_int(s) for s in input_data]\n\ntotal = (take([r.unwrap() for r in parsed_results if r.is_ok()])\n    .sum()\n    .value())\n# total: 60 (10 + 20 + 30, \"invalid\" safely ignored)\n```\n\n### Option<T> - Functional Null Handling\n\nHandle null/missing values functionally with pattern matching:\n\n```python\nfrom fluent_chaining import Some, Nothing, from_nullable, from_dict\n\n# Create Options\nuser_name = Some(\"Alice\")\nmissing_data = Nothing()\n\n# Pattern matching - like .NET's Option.Match()\ndef process_user(name_option):\n    return name_option.match(\n        some_func=lambda name: f\"Processing user: {name}\",\n        none_func=lambda: \"No user to process\"\n    )\n\nprint(process_user(user_name))    # \"Processing user: Alice\"\nprint(process_user(missing_data)) # \"No user to process\"\n\n# Safe data access\nuser_data = {\"name\": \"Bob\", \"profile\": {\"age\": 30}}\n\nage_message = (from_dict(user_data, \"profile\")\n    .then(lambda profile: from_dict(profile, \"age\"))\n    .match(\n        some_func=lambda age: f\"User is {age} years old\",\n        none_func=lambda: \"Age not available\"\n    ))\n# Result: \"User is 30 years old\"\n\n# Chain multiple optional operations\ndef get_user_email_domain(user):\n    return (from_dict(user, \"email\")\n        .map(lambda email: email.split(\"@\"))\n        .filter(lambda parts: len(parts) == 2)\n        .map(lambda parts: parts[1])\n        .unwrap_or(\"unknown\"))\n\nusers = [\n    {\"email\": \"alice@example.com\"},\n    {\"email\": \"invalid-email\"},\n    {\"name\": \"Bob\"}  # No email\n]\n\ndomains = [get_user_email_domain(user) for user in users]\n# domains: [\"example.com\", \"unknown\", \"unknown\"]\n```\n\n### Integration with Fluent Chains\n\nConvert between chains and monadic types seamlessly:\n\n```python\n# Chain to Result/Option\nnumbers = [1, 2, 3, 4, 5]\nresult = (take(numbers)\n    .where(lambda x: x > 10)\n    .to_result(\"No large numbers found\"))\n\nresult.match(\n    ok_func=lambda values: f\"Found: {values}\",\n    err_func=lambda error: f\"Error: {error}\"\n)\n# Result: \"Error: No large numbers found\"\n\n# Option to Chain\noptional_data = Some([1, 2, 3])\ntotal = (from_option(optional_data, [])\n    .sum()\n    .value())\n# total: 6\n\n# Complex pipeline with error handling\ndef safe_process_scores(scores_text):\n    return (try_parse_int(scores_text)\n        .filter(lambda score: 0 <= score <= 100, \"Score out of range\")\n        .map(lambda score: score * 1.1)  # Apply 10% bonus\n        .map(lambda score: min(score, 100)))  # Cap at 100\n\ntest_scores = [\"85\", \"95\", \"invalid\", \"105\", \"-5\"]\nprocessed = [safe_process_scores(s) for s in test_scores]\n\n# Extract successful scores\nfinal_scores = [r.unwrap() for r in processed if r.is_ok()]\naverage = take(final_scores).sum().value() / len(final_scores) if final_scores else 0\n# average: ~96.5 (from 85*1.1=93.5 and 95*1.1=100 capped)\n```\n\n## \ud83e\udd1d Contributing\n\nWe welcome contributions! Please see our [Contributing Guidelines](CONTRIBUTING.md) for details.\n\n## \ud83d\udcc4 License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## \ud83c\udf1f Project Saturday\n\nThis package is part of the [Project Saturday](https://github.com/Project-Saturday) organization, dedicated to creating tools that make programming more expressive and enjoyable.\n\n---\n\n*Made with \u2764\ufe0f by the Project Saturday team*\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "A Python package for pure functional programming with fluent chaining that reads like prose",
    "version": "0.1.0",
    "project_urls": {
        "Bug Reports": "https://github.com/Project-Saturday/fluent-chaining/issues",
        "Homepage": "https://github.com/Project-Saturday/fluent-chaining",
        "Source": "https://github.com/Project-Saturday/fluent-chaining"
    },
    "split_keywords": [
        "functional",
        " chaining",
        " fluent",
        " pure-functions",
        " compose"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "3b2ad335bd889b5df97c25c55600fc599a6c08d63223a4a28c7e871c57623dcf",
                "md5": "89e00b0fe505bac48578cb5e2bda616f",
                "sha256": "31721932456fd308705fe036c05ba710121a5149267408f935ca023355b2fc13"
            },
            "downloads": -1,
            "filename": "fluent_chaining-0.1.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "89e00b0fe505bac48578cb5e2bda616f",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.9",
            "size": 18091,
            "upload_time": "2025-08-12T23:20:58",
            "upload_time_iso_8601": "2025-08-12T23:20:58.818927Z",
            "url": "https://files.pythonhosted.org/packages/3b/2a/d335bd889b5df97c25c55600fc599a6c08d63223a4a28c7e871c57623dcf/fluent_chaining-0.1.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "d6d16c10093bc6599b6ed32a3153823ffd48acd04dfe6be5e862e1abb41da572",
                "md5": "a7055d8abf8821bf46c96bd7a2babe93",
                "sha256": "66bbf824c44e91ec59ca33e1571f7c4664bca27f3630959f6ca08dc0cd9491f9"
            },
            "downloads": -1,
            "filename": "fluent_chaining-0.1.0.tar.gz",
            "has_sig": false,
            "md5_digest": "a7055d8abf8821bf46c96bd7a2babe93",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.9",
            "size": 33444,
            "upload_time": "2025-08-12T23:21:00",
            "upload_time_iso_8601": "2025-08-12T23:21:00.739369Z",
            "url": "https://files.pythonhosted.org/packages/d6/d1/6c10093bc6599b6ed32a3153823ffd48acd04dfe6be5e862e1abb41da572/fluent_chaining-0.1.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-08-12 23:21:00",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "Project-Saturday",
    "github_project": "fluent-chaining",
    "github_not_found": true,
    "lcname": "fluent-chaining"
}
        
Elapsed time: 2.01775s