Skip to content

Commit

Permalink
Merge pull request #6 from geo-ant/feature/simplify_macros
Browse files Browse the repository at this point in the history
Feature/simplify macros
  • Loading branch information
geo-ant authored Apr 19, 2021
2 parents 5783657 + 76dbf9c commit 6b0dba1
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 55 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "fluent-comparisons"
version = "0.1.1"
version = "0.2.0"
authors = ["geo-ant <geos.blog@posteo.de>"]
edition = "2018"
homepage = "https://github.com/geo-ant/fluent-comparisons"
Expand All @@ -18,7 +18,7 @@ keywords = ["fluent","DRY","multi","comparison","expressions"]
# be used, but on crates.io the version dependency will be used, which is just extremely helpful for development.
# See this Reddit post: https://www.reddit.com/r/rust/comments/a39er8/how_do_you_publish_your_cargo_workspace_packages/
# And the Rust Doc: https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html#specifying-path-dependencies
fluent-comparisons-macros = {version = "0.1.0", path = "fluent-comparisons-macros"}
fluent-comparisons-macros = {version = "0.2.0", path = "fluent-comparisons-macros"}

[workspace]
members = ["fluent-comparisons-macros"]
Expand Down
22 changes: 12 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,50 @@
![tests](https://github.com/geo-ant/fluent-comparisons/workflows/tests/badge.svg?branch=main)
![lints](https://github.com/geo-ant/fluent-comparisons/workflows/lints/badge.svg?branch=main)

A crate offering a fluent syntax for multi-comparisons.
**Fluent syntax for multi-comparisons.**

## Motivation

This crate is everyone for you if you have ever gotten annoyed at writing conditions like this
This crate is for you if you have ever been annoyed at writing repetitive conditions like this

```rust
if x < a && y < a && z < a {
// ... do something
}
```
and wished you could replace that code by something more expressive and less repetitive? If so, then this crate is for you, because it allows us to write the condition above as
and wished you could replace that code by something more expressive and less repetitive. Now you can write the condition above as
```rust
use fluent_comparisons::all_of;

if all_of!({x,y,z} < a) {
// ... do something
}
```

## Brief Description and Key Advantages

The crate provides the macros `any_of`, `all_of` and `none_of` to facilitate writing conditions analogous to the example above. Additional to offering an intuitive syntax and more readable code, the macros compile down to the same assembly as the handwritten code for any nonzero optimization level ([check it on godbolt.org](https://godbolt.org/z/zTfTq6va5)).
The crate provides the macros `any_of`, `all_of` and `none_of` to facilitate writing expressive multicomparisons. In addition to providing an intuitive syntax, the macros compile to the same assembly as the handwritten code ([check it on godbolt.org](https://godbolt.org/z/M3494a6Mc)).

A further benefit is [lazy evaluation](https://doc.rust-lang.org/reference/expressions/operator-expr.html#lazy-boolean-operators) from left to right as seen in the next snippet:

```rust
// if cheap_calc(arg1) <=5, then the costly calculation
// if cheap_calc(arg1) <=5, then the expensive calculation
// is never performed
let cond = any_of!({cheap_calc(arg1), expensive_calc(arg2)}<=5);
any_of!({cheap_calc(arg1), expensive_calc(arg2)}<=5)

// whereas if we did this, the expensive calculation would be
// performed regardless of the result of cheap_calculation(arg1)
[cheap_calculation(arg1), expensive_calculation(arg2)].iter().any(|val|val<=5)
[cheap_calc(arg1), expensive_calc(arg2)].iter().any(|val|val<=&5)
```

## Usage

The macros are called as `any_of!({/*list of expressions*/} operator rhs)`, where operator can be any of the binary comparison operators, i.e. `==`, `!=`, `<=`, `<`, `>`, and `>=`. The list of expressions on the left hand side is comma separated without a trailing comma. The right hand side is an expression as well.
Use the macros by writing `any_of!({/*list of expressions*/} operator rhs)`, where operator can be any of the binary comparison operators, i.e. `==`, `!=`, `<=`, `<`, `>`, and `>=`. The list of expressions on the left hand side is comma separated without a trailing comma. The right hand side is an expression as well.

The list of expressions can have a variadic number of elements but must have at least one. It must always be enclosed in curly braces. The expressions on the left hand side need not be of the same type, but the comparison with the right hand side must be valid. In particular, the expressions need not be numeric.
The list of expressions can have a variadic number of elements but must have at least one. It must always be enclosed in curly braces. The expressions on the left hand side need not be of the same type, but the comparison with the right hand side must be valid individually. Furthermore, the expressions can evaluate to anything that can be compared to `rhs`, not just numbers.

The same goes for the `all_of` and `none_of` macros. Check the docs for more information.

## Links

This library is inspired by Björn Fahller's [DRY comparisons](https://github.com/rollbear/dry-comparisons) library, which I read about [in this blog post](https://www.fluentcpp.com/2020/01/03/dry-comparisons-a-c-library-to-shorten-redundant-if-statements/) on Jonathan Boccara's blog.
This library is inspired by Björn Fahller's [DRY comparisons](https://github.com/rollbear/dry-comparisons) Modern C++ library, which I read about [in this blog post](https://www.fluentcpp.com/2020/01/03/dry-comparisons-a-c-library-to-shorten-redundant-if-statements/) on Jonathan Boccara's blog.
2 changes: 1 addition & 1 deletion check_ci.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
# echo on
cargo fmt
cargo clippy --all-targets --all-features -- -D warnings
cargo test
cargo test --workspace
3 changes: 2 additions & 1 deletion fluent-comparisons-macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
[package]
name = "fluent-comparisons-macros"
version = "0.1.0"
version = "0.2.0"
authors = ["geo-ant <geos.blog@posteo.de>"]
edition = "2018"
homepage = "https://github.com/geo-ant/fluent-comparisons"
repository = "https://github.com/geo-ant/fluent-comparisons"
description = "Macros for the fluent-comparisons crate."
categories = ["algorithms", "development tools"]
license = "MIT"
readme = "README.md"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

Expand Down
2 changes: 2 additions & 0 deletions fluent-comparisons-macros/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# fluent-comparisons-macros
Macros for the [fluent-comparisons](https://crates.io/crates/fluent-comparisons) crate.
42 changes: 4 additions & 38 deletions fluent-comparisons-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,23 +52,12 @@ macro_rules! __check_operator {
/// ```
#[macro_export]
macro_rules! any_of {
//TODO CAUTION: THIS COULD BE CALLED WITH ONE ARGUMENT. MAKE SURE THAT THIS PRODUCES A VALID RESULT
// expression like any_of!( {1,v.len(),4} < 3)
( {$($lh_sides:expr),+} $operator:tt $rhs:expr)=> {
{
$crate::__check_operator!($operator);
any_of!(@internal lhs={$($lh_sides),+}, op=[$operator], rhs = $rhs, expanded = {false})
$( ($lh_sides $operator $rhs) )||+
}
};
// internal rules, recursion final case
(@internal lhs = {$head:expr},op = [$op:tt], rhs = $rhs:expr, expanded = {$expanded:expr}) => {
$expanded || ($head $op $rhs)
};

// internal rules, recursion case
(@internal lhs = {$head:expr, $($tail:expr),*}, op = [$op:tt], rhs = $rhs:expr, expanded = {$expanded:expr}) =>{
any_of!(@internal lhs={$($tail),*}, op=[$op], rhs = $rhs, expanded = {$expanded || ($head $op $rhs)})
};
}

/// Compare all values in a set to a common right hand side and decide whether the comparison returns `true` for *all of the values* in the set.
Expand Down Expand Up @@ -101,24 +90,12 @@ macro_rules! any_of {
/// ```
#[macro_export]
macro_rules! all_of {
//TODO CAUTION: THIS COULD BE CALLED WITH ONE ARGUMENT. MAKE SURE THAT THIS PRODUCES A VALID RESULT
// expression like any_of!( {1,v.len(),4} < 3)
( {$($lh_sides:expr),+} $operator:tt $rhs:expr)=> {
{
$crate::__check_operator!($operator);
all_of!(@internal lhs={$($lh_sides),+}, op=[$operator], rhs = $rhs, expanded = {true})
$( ($lh_sides $operator $rhs) )&&+
}
};

// internal rules, recursion final case
(@internal lhs = {$head:expr},op = [$op:tt], rhs = $rhs:expr, expanded = {$expanded:expr}) => {
$expanded && ($head $op $rhs)
};

// internal rules, recursion case
(@internal lhs = {$head:expr, $($tail:expr),*}, op = [$op:tt], rhs = $rhs:expr, expanded = {$expanded:expr}) =>{
all_of!(@internal lhs={$($tail),*}, op=[$op], rhs = $rhs, expanded = {$expanded && ($head $op $rhs)})
};
}

/// Compare all values in a set to a common right hand side and decide whether the comparison returns `true` for *none of the values* in the set.
Expand Down Expand Up @@ -151,27 +128,16 @@ macro_rules! all_of {
/// ```
#[macro_export]
macro_rules! none_of {
//TODO CAUTION: THIS COULD BE CALLED WITH ONE ARGUMENT. MAKE SURE THAT THIS PRODUCES A VALID RESULT
// expression like any_of!( {1,v.len(),4} < 3)
( {$($lh_sides:expr),+} $operator:tt $rhs:expr)=> {
{
$crate::__check_operator!($operator);
none_of!(@internal lhs={$($lh_sides),+}, op=[$operator], rhs = $rhs, expanded = {true})
$( !($lh_sides $operator $rhs) )&&+
}
};

// internal rules, recursion final case
(@internal lhs = {$head:expr},op = [$op:tt], rhs = $rhs:expr, expanded = {$expanded:expr}) => {
$expanded && !($head $op $rhs)
};

// internal rules, recursion case
(@internal lhs = {$head:expr, $($tail:expr),*}, op = [$op:tt], rhs = $rhs:expr, expanded = {$expanded:expr}) =>{
none_of!(@internal lhs={$($tail),*}, op=[$op], rhs = $rhs, expanded = {$expanded && !($head $op $rhs)})
};
}

// TODO FINISH THIS UP, TEST IT AND DOCUMENT IT
// TODO: make this as simple as the ones above w/o recursion
// #[macro_export]
// macro_rules! exactly_one_of {
// //TODO CAUTION: THIS COULD BE CALLED WITH ONE ARGUMENT. MAKE SURE THAT THIS PRODUCES A VALID RESULT
Expand Down
49 changes: 46 additions & 3 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,49 @@
//! This library provides a number of macros to make multicomparison expressions less annoying to
//! write while still keeping the benefits of hand-written code, such as lazy evaluation and
//! boolean short circuiting. Go to any of the macros to find out about their usage.
//! This crate is for you if you have ever been annoyed at writing repetitive conditions like this
//! ```rust
//! # fn test(a:i32,x:i32,y:i32,z:i32) {
//! if x < a && y < a && z < a {
//! // ... do something
//! }
//! # }
//! ```
//! and wished you could replace that code by something more expressive and less repetitive. Now you can rewrite the code as
//! ```rust
//!# fn test(a:i32,x:i32,y:i32,z:i32) {
//! use fluent_comparisons::all_of;
//!
//! if all_of!({x,y,z} < a) {
//! // ... do something
//! }
//! # }
//! ```
//!
//! ## Brief Description and Key Advantages
//!
//! The crate provides the macros `any_of`, `all_of` and `none_of` to facilitate writing expressive multicomparisons.
//! In addition to providing an intuitive syntax, the macros compile to the same assembly as
//! the handwritten code ([check it on godbolt.org](https://godbolt.org/z/M3494a6Mc)).
//!
//! A further benefit is [lazy evaluation](https://doc.rust-lang.org/reference/expressions/operator-expr.html#lazy-boolean-operators) from
//! left to right as seen in the next snippet:
//!
//! ```rust
//! use fluent_comparisons::any_of;
//!
//! # fn cheap_calc(v : usize)-> usize {v}
//! # fn expensive_calc(v : usize)-> usize {v}
//! # fn test(arg1: usize, arg2:usize) {
//! // if cheap_calc(arg1) <=5, then the expensive calculation
//! // is never performed
//! let b = any_of!({cheap_calc(arg1), expensive_calc(arg2)}<=5);
//! // whereas if we did this, the expensive calculation would be
//! // performed regardless of the result of cheap_calculation(arg1)
//! let b = [cheap_calc(arg1), expensive_calc(arg2)].iter().any(|val|val<=&5);
//! # }
//! ```
//!
//! ## Usage
//!
//! Refer to the items in the documentation below to learn about the usage of the macros.
pub use fluent_comparisons_macros::any_of;

Expand Down

0 comments on commit 6b0dba1

Please sign in to comment.