match-commutative 0.1.0

Match on patterns commutatively, reducing the use of duplicated patterns. ↔️
Documentation
<h1 style="text-align: center;">match-commutative ↔️</h1>

<h3 style="text-align: center;">
	Match on patterns commutatively, reducing the use of duplicated patterns.
</h3>

<br>

<p style="text-align: center;">
<!-- Tests status -->
  <a href="https://gitlab.com/janriemer/match-commutative/-/commits/main">
    <img alt="Pipeline Status" src="https://img.shields.io/gitlab/pipeline-status/janriemer/match-commutative?branch=main&color=B5E8E0&logoColor=D9E0EE&label=pipeline&labelColor=302D41&style=for-the-badge" />
  </a>
<!-- Crates version -->
  <a href="https://crates.io/crates/match-commutative">
    <img src="https://img.shields.io/crates/v/match-commutative.svg?style=for-the-badge&logo=rust&color=C9CBFF&logoColor=D9E0EE&labelColor=302D41"
    alt="Crates.io version" />
  </a>
  <!-- Downloads -->
  <a href="https://crates.io/crates/match-commutative">
    <img src="https://img.shields.io/crates/d/match-commutative.svg?style=for-the-badge&color=F2CDCD&logoColor=D9E0EE&labelColor=302D41"
      alt="Downloads" />
  </a>
  <!-- docs.rs docs -->
  <a href="https://docs.rs/match-commutative">
    <img src="https://img.shields.io/badge/docs-latest-blue.svg?style=for-the-badge&color=DDB6F2&logoColor=D9E0EE&labelColor=302D41"
      alt="docs.rs docs" />
  </a>
</p>

--------------

## Documentation
[https://docs.rs/match-commutative](https://docs.rs/match-commutative)


## Motivation
When you need to `match` on three values that form a commutative math relation, you often need to
duplicate a lot of patterns.
Let's look at an example of what this might look like:

```rust
// imagine that these values come from somewhere and we need to match on them
let operant1 = Operant::Str(Some("42".into()));
let operant2 = Operant::Num(Some(1));
let operator = Operator::Plus;

match (operant1, operator, operant2) {
    (
        Operant::Str(Some(operant_str)),
        Operator::Plus,
        Operant::Num(Some(operant_num)),
    )
    | (
        Operant::Num(Some(operant_num)),
        Operator::Plus,
        Operant::Str(Some(operant_str)),
    ) if operant_str.len() < 3 => {
        let result = operant_num + operant_str.parse::<isize>().unwrap();
        println!("Result is: {}", result);
    }
    (
        Operant::Str(Some(operant_str)),
        Operator::Mult,
        Operant::Num(Some(operant_num)),
    )
    | (
        Operant::Num(Some(operant_num)),
        Operator::Mult,
        Operant::Str(Some(operant_str)),
    ) if operant_str.len() < 3 => {
        let result = operant_num * operant_str.parse::<isize>().unwrap();
        println!("Result is: {}", result);
    }
    (_, _, _) => {
        panic!("Not relevant for this example")
    }
}

// Types that we use in this example
enum Operant {
    Str(Option<String>),
    Num(Option<isize>),
}

enum Operator {
    Plus,
    Mult,
    Minus,
}
```
For both `Operator::{Plus, Mult}`, we have to write _two patterns each_ that are exactly identical
(and connect them with `|` (or-pattern)) and execute the same logic.
The only difference in the pattern is the ordering of the `Operant`. Not nice!

### Using `match-commutative` instead

With `match-commutative` this can be simplified to:
```rust
use match_commutative::match_commutative;
// imagine that these values come from somewhere and we need to match on them
let operant1 = Operant::Str(Some("42".into()));
let operant2 = Operant::Num(Some(1));
let operator = Operator::Plus;

match_commutative!(
    operant1,
    operator,
    operant2,
    Operant::Str(Some(operant_str)),
    Operator::Plus,
    Operant::Num(Some(operant_num)) if operant_str.len() < 3 => {
        let result = operant_num + operant_str.parse::<isize>().unwrap();
        println!("Result is: {}", result);
    },
    Operant::Str(Some(operant_str)),
    Operator::Mult,
    Operant::Num(Some(operant_num)) if operant_str.len() < 3 => {
        let result = operant_num * operant_str.parse::<isize>().unwrap();
        println!("Result is: {}", result);
    }
    non_commut {
        _, _, _ => {
            // in `non_commut` block, patterns and their execution block behave exactly like std Rust
            panic!("Not relevant for this example")
        }
    }
);

// Types that we use in this example
enum Operant {
    Str(Option<String>),
    Num(Option<isize>),
}

enum Operator {
    Plus,
    Mult,
    Minus,
}
```

Note that in the above example the values of `operant1` and `operant2` could have been swapped, __while still
leading to the same program output.__
So we have successfully avoided the expression of _ordering_ in our patterns
(where ordering is not needed between two `Operant`s).✨

### Using `non_commut` block for operants that are not commutative

If you need to `match` on operants that are not commutative, you can put the pattern
in the optional `non_commut` block. Within `non_commut` patterns behave exactly like std Rust:
```rust
use match_commutative::match_commutative;
let operant1 = Operant::Str(Some("42".into()));
let operant2 = Operant::Num(Some(1));
let operator = Operator::Minus; // a non-commutative operator!

let result = match_commutative!(
        operant2,
        operator,
        operant1,
        Operant::Str(_),
        Operator::Plus,
        Operant::Num(_) => {
            // do something here
            todo!()
        }
        non_commut {
            // for minus operations, we get different results depending on the
            // ordering of the operants
            Operant::Num(Some(op_num)),
            Operator::Minus,
            Operant::Str(Some(op_str)) if op_str.len() < 3 => {
                op_num - op_str.parse::<isize>().unwrap()
            },
            Operant::Str(Some(op_str)),
            Operator::Minus,
            Operant::Num(Some(op_num)) if op_str.len() < 3 => {
                op_str.parse::<isize>().unwrap() - op_num
            },
            _,_,_ => {
                // catch all match arm
                todo!()
            }
        }
    );

assert_eq!(-41, result);

// Types that we use in this example
enum Operant {
    Str(Option<String>),
    Num(Option<isize>),
}

enum Operator {
    Plus,
    Mult,
    Minus,
}
```

## Getting Started
In your Cargo.toml file add the following lines under `[dependencies]`:
```toml
match-commutative = "0.1.0"
```

## Safety
This crate is implemented in __100% Safe Rust__, which is ensured by using `#![forbid(unsafe_code)]`.

## MSRV
The Minimum Supported Rust Version for this crate is __1.54__. An increase of MSRV will be indicated by a minor change (according to SemVer).

<br>

-------

<br>

#### License

<sup>
Licensed under either of <a href="LICENSE-APACHE">Apache License, Version
2.0</a> or <a href="LICENSE-MIT">MIT license</a> at your option.
</sup>

<br>

<sub>
Unless you explicitly state otherwise, any contribution intentionally submitted
for inclusion in this crate by you, as defined in the Apache-2.0 license, shall
be dual licensed as above, without any additional terms or conditions.
</sub>