pipei 0.1.3

Similar to the tap crate; a generic alternative to Tap’s tap and pipe methods.
Documentation
# pipei

`pipei` provides `pipe{i}` and `tap{i}` traits that enable point-free function chaining syntax. They bind the first argument of a free function to the receiver and return a closure for the remaining arguments.

* **Pipe**: Transforms the input. Returns the result of the function `f`.
* **Tap**: Inspects or mutates the input. Ignores the result of `f` and returns the original value.

The `_with` variants allow projecting the input (e.g., viewing `String` as `str` or `Vec<T>` as `[T]`) before passing it to the function.

## Enabling arities

Enable the arities you need via features:

```toml
[dependencies]
pipei = "0.1" # default: features = ["up_to_5"]
# pipei = { version = "0.1", features = ["up_to_10"] }
# pipei = { version = "0.1", features = ["pipe0", "pipe1", "tap0", "tap1"] }
```

## Basic chaining (by value)

`pipe` passes the value into the function and returns the result. `tap` moves the value in, runs the function, and returns the original value.

**Unified Tap API**: `tap` methods seamlessly accept functions taking either `&Self` (immutable) or `&mut Self` (mutable).

```rust
use pipei::{Pipe1, Pipe2, Tap1, Tap2};

fn add(x: i32, y: i32) -> i32 { x + y }
fn mul(x: i32, y: i32) -> i32 { x * y }
fn lin(x: i32, a: i32, b: i32) -> i32 { a * x + b }

let out = 2
    .pipe1(add)(3)      // 2 + 3 = 5
    .pipe1(mul)(10)     // 5 * 10 = 50
    .pipe2(lin)(7, 1);  // 50 * 7 + 1 = 351

assert_eq!(out, 351);

fn log_val(x: &i32, label: &str) { println!("{}: {}", label, x); }
fn add_assign(x: &mut i32, y: i32) { *x += y; }

let val = 2
    .tap1(log_val)("init")     // Immutable: passes &i32
    .tap1(add_assign)(3)       // Mutable: passes &mut i32
    .tap1(log_val)("result");

assert_eq!(val, 5);
```

## Arity 0 (Pipe0 / Tap0)

`Pipe0` is useful for passing the receiver to a function that takes only one argument, or for wrapping the receiver in a constructor (like `Some` or `Ok`) without extra parentheses.

```rust
use pipei::{Pipe0, Tap0};

fn get_len(s: String) -> usize { s.len() }
fn log_val(s: &String) { println!("val: {}", s); }
fn clear_str(s: &mut String) { s.clear(); }

assert_eq!("hello".to_string().pipe0(get_len)(), 5);

// Wraps value in Option::Some (removing wrapper syntax)
let maybe_num = 10.pipe0(Option::Some)();
assert_eq!(maybe_num, Some(10));

// Works with both immutable and mutable functions
let s = "hello".to_string()
    .tap0(log_val)()    // Inspect
    .tap0(clear_str)(); // Mutate

assert_eq!(s, "");
```

## Borrowed views (Projection)

Use `_with` variants to apply a projection (like `Borrow::borrow` or `AsRef::as_ref`) before calling the function.

```rust
use pipei::{Pipe2Ref, Tap2Ref};
use core::borrow::Borrow;

fn slice_len(s: &str, start: usize, end: usize) -> usize {
    s[start..end].len()
}
fn assert_slice(s: &str, start: usize, end: usize) {
    assert_eq!(&s[start..end], "hello");
}

let s = "hello world".to_string();

// |x| x.borrow() projects &String -> &str
assert_eq!(s.pipe2_with(|x| x.borrow(), slice_len)(0, 5), 5);

s.tap2_with(|x| x.borrow(), assert_slice)(0, 5);
```

## Mutable views

`tap{i}_with_mut` allows chaining side effects on a mutable reference. It accepts both mutable and immutable functions.

```rust
use pipei::{Pipe1Ref, Tap1Ref};

fn log_vec(v: &Vec<i32>) { println!("len: {}", v.len()); }
fn push_ret(v: &mut Vec<i32>, x: i32) -> &mut Vec<i32> { v.push(x); v }

let mut v1 = vec![];
v1.pipe1_with_mut(|x| x, push_ret)(1);
assert_eq!(v1, vec![1]);

let mut v2 = vec![];
v2.tap0_with_mut(|x| x, log_vec)()          // Immutable Fn(&Vec) works on mutable view
  .tap1_with_mut(|x| x, Vec::push)(1);      // Mutable Fn(&mut Vec) works

assert_eq!(v2, vec![1]);
```

## Comparison with the `tap` crate

The [tap](https://crates.io/crates/tap) crate is the standard solution for continues chaining. 
`pipei` extends this concept to **multi-argument functions** to address issues related to control flow and nesting depth.

### 1. Control Flow (The `?` Operator)

Using closures prevents the `?` operator from propagating errors out of the parent function.

**Standard Rust:**
```rust
fn calc(s1: &str, s2: &str) -> Result<i32, ParseIntError> {
    let x = s1.parse::<i32>()?;
    let y = s2.parse::<i32>()?;
    Ok(x.wrapping_add(y))
}
```

**Using `tap`:**
The `?` operator affects the *closure's* return type, not the function's, which may lead to type mismatches which may be hard to resolve (especially when used recursively).
```rust
fn calc(s1: &str, s2: &str) -> Result<i32, ParseIntError> {
    s1.pipe(|x| {
        let x = x.parse::<i32>()?; // Returns Result from closure
        let y = s2.parse::<i32>()?;
        Ok(x.wrapping_add(y))
    }) // Returns Result<Result<...>>
}
```

**Using `pipei`:**
Arguments are evaluated before the call, so the `?` operator works as usual.
```rust
fn calc(s1: &str, s2: &str) -> Result<i32, ParseIntError> {
    s1.pipe0(str::parse::<i32>)()?
      .pipe1(i32::wrapping_add)(
          s2.pipe0(str::parse::<i32>)()?
      )
      .pipe0(Ok)()
}
```

### 2. Recursive Nesting

When function arguments are results of other chains, standard chaining forces deep closure nesting. pipei maintains a flat structure. To illustrate this, consider this (admittedly contrived) example:

In Standard form, the logic is "inside-out".
```rust
let result = process_one(
    x_very_long_name,
    "config",
    process_two(
        y_very_long_name,
        arg10_very_long_name,
        arg20_very_long_name,
        init(
            z_very_long_name,
            arg30_very_long_name,
            arg40_very_long_name
        )
    )
);
```

By using `tap` we have recursive nested calls with recursive nested causers:
```rust
let result = x_very_long_name
    .pipe(|x_val| process_one(
        x_val,
        "config",
        y_very_long_name
            .pipe(|y_val| process_two(
                y_val,
                arg10_very_long_name,
                arg20_very_long_name,
                z_very_long_name
                    .pipe(|z_val| init(
                        z_val,
                        arg30_very_long_name,
                        arg40_very_long_name
                        )
                    )
            ))
    ));
```

Using `pipei`, the logic remains linear.
```rust
let result = x_very_long_name
    .pipe2(process_one)(
        "config",
        y_very_long_name
            .pipe3(process_two)(
                arg10_very_long_name,
                arg20_very_long_name,
                z_very_long_name
                    .pipe2(init)(
                        arg30_very_long_name,
                        arg40_very_long_name
                    )
            )
    );
```