Trait xpr::Fold[][src]

pub trait Fold<T: ?Sized> {
    type Output;
    fn fold(&mut self, _: &T) -> Self::Output;
}
Expand description

A trait for expression manipulation. Fold together with crate::Xpr are at the heart of this crate. Fold<T>::fold will replace any instance of type T in an expression tree crate::Xpr<U> with a value of type Fold::Output.

The usage of this trait is best explained with examples.

Examples

The following examples can also be found in the examples directory.

Evaluating expressions

We can replace all Term<i32> instances in an expression by their wrapped type. This will effectively evaluate the expression.

use xpr::{ops::Term, Fold, Xpr};

struct Evaluator;

/// evaluates an expression of i32 terminals
impl Fold<Term<i32>> for Evaluator {

    type Output = i32;

    /// replaces Terminal values with their wrapped type
    fn fold(&mut self, Term(x): &Term<i32>) -> i32 {
        *x
    }
}

// create a new expression representing a chained addition
let x = Xpr::new(20) / Xpr::new(2) + Xpr::new(3) * Xpr::new(5) + Xpr::new(23) - Xpr::new(6);

// use the Evaluator to evaluate the expression
let res = Evaluator.fold(&x);
assert_eq!(res, 42);

// note that `Xpr::eval` is syntactic sugar around a similar generic
// Evaluator struct
assert_eq!(res, x.eval());

Substituting expressions

We can replace terminals by other expressions

use xpr::{
    ops::{OutputOfSub, Term},
    Fold, Xpr,
};

struct Substitution;

impl Fold<Term<i32>> for Substitution {
    
    type Output = OutputOfSub<Term<i32>, Term<i32>>;

    // replaces i32 terminals with a Sub expression
    // that returns the same value
    fn fold(&mut self, &Term(x): &Term<i32>) -> Self::Output {
        Xpr::new(x + 42) - Xpr::new(42)
    }
}

let x = Xpr::new(10) + Xpr::new(15) + Xpr::new(17);

assert_eq!(x.eval(), 42);

// let's use the Substitution folder
let x = Substitution.fold(&x);

assert_eq!(x.eval(), 42);

Improving performance: Avoiding temporaries

This is the test-piece for expression templates and the classical use case scenario. Image we are using a linear algebra library that supplies a Vec type and implements std::ops::Add for it. Then, we could write a chained addition of potentially large vectors x1, x2 and x3.

let x = x1 + x2 + x3;

Internally, the additon of x2 and x3 would yield a temporary vector containing the result of the addition. This temporary vector is than added to x1 yielding yet another temporary vector, which is moved into x. The allocation of these two temporary vectors can be avoided using expression templates.

Let’s pretend we want to write our own linear algebra library.

use xpr::{ops::Term, Expression, Fold, Xpr};

// This is our statically sized vector type
#[derive(Debug)]
struct Vec<const N: usize>(Box<[f64; N]>);

impl<const N: usize> Vec<{ N }> {
    #[inline]
    fn new(array: [f64; N]) -> Self {
        Self(Box::new(array))
    }
}

// a convenience trait for cnverting Vec instances to xpr terminals.
// this lets us call as_xpr and into_xpr on Vec instances.
impl<const N: usize> Expression for Vec<N> {}

struct IthElement<const N: usize>(usize);

// IthElement will match all Terminals wrapping a Vec and return it's i-th element.
impl<const N: usize> Fold<Term<Vec<{ N }>>> for IthElement<{ N }> {

    type Output = f64;

    // extracts the i-th element of a vector terminal
    #[inline]
    fn fold(&mut self, Term(v): &Term<Vec<{ N }>>) -> f64 {
        v.0[self.0]
    }
}

// convert any Xpr<T> to Vec
impl<T, const N: usize> From<Xpr<T>> for Vec<{ N }>
where
    IthElement<N>: Fold<Xpr<T>, Output = f64>,
{
    // conversion from a vector expression to a Vec instance
    #[inline]
    fn from(expr: Xpr<T>) -> Self {
        // scary unsafe uninitialized array
        let mut ret = Vec::new(unsafe { std::mem::MaybeUninit::uninit().assume_init() });

        // apply the operations in the vector expression element-wise
        for (i, e) in ret.0.iter_mut().enumerate() {
            *e = IthElement(i).fold(&expr);
        }
        ret
    }
}

// Now let's take it for a spin!

// Create a couple of vector expressions
let x1 = Vec::new([0.6; 5000]).into_xpr();
let x2 = Vec::new([1.0; 5000]).into_xpr();
let x3 = Vec::new([40.0; 5000]).into_xpr();
let x4 = Vec::new([100.0; 5000]).into_xpr();
let x5 = Vec::new([3000.0; 5000]).into_xpr();

// A chained addition without any Vec temporaries!
let v = Vec::from(x1 + x2 + x3 + x4 + x5);
assert_eq!(v.0[0], 3141.6);

Granted, the example is a bit of an oversimplification because the addition consumes its summands and it would have been possible to write a fairly performant vector addition with the same functionality based on move semantics. But a sum that consumes its summands is something you usually don’t want. It takes only a little bit of tweaking to get the example to work on terminals that wrap references to vectors that will not be consumed. The tweaked example can be found in the examples subdirectory of the repository.

Associated Types

The output of Fold::fold, Self will replace all instances of T in an expression by values of this type.

Required methods

perform the replacement

Implementors