1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
//! This crate allows you to use expression templates with your custom type.
//! It is inspired by the C++ expression template library [boost::yap](https://www.boost.org/doc/libs/1_74_0/doc/html/yap.html).
//!
//! To use `xpr`, all you need to do is wrap all input variables to an expression in calls to `Xpr::new` and apply any supported
//! operation to them. The result will be an object representing the operations you performed. Any possible chain of operations
//! will be represented by their own specific type.
//!
//! ## Usage
//!
//! Evaluation of the expression is be performed lazily by calling `Xpr::eval`:
//!
//! ```
//! use xpr::Xpr;
//!
//! // create an exression. x will be a type representing the
//! // addition of 7 and 5, not the result of the calculation
//! let x = Xpr::new(7) + Xpr::new(5);
//!
//! // lazily evaluate the expression
//! assert_eq!(x.eval(), 12);
//! ```
//!
//! In the above example, the type of `x` is `Xpr<Add<(Xpr<Term<{integer}>>, Xpr<Term<{integer}>>)>>`[^note].
//! It is a type representing the addition of two integers. To actually evaulate the expression, we call `x.eval()`.
//!
//! [^note]: All crate and nested module names have been omitted in the type for better readability.
//!
//! In addition to lazy evaluation, you can manipulate expressions using the [`Fold`] trait. A struct implementing `Fold`
//! can replace terminals *(leaf expressions)* and perform operations along the way. It can be stateful or a
//! zero-sized type, depending on your needs.
//!
//! ```
//! use xpr::{Xpr, Fold, ops::Term};
//!
//! struct Fortytwoify;
//!
//! impl Fold<Term<f64>> for Fortytwoify {
//!
//!     // We will replace these terminals by terminal expressions wrapping f64 values
//!     type Output = Xpr<Term<f64>>;
//!
//!     // replaces terminals with terminals wrapping the value 42.
//!     fn fold(&mut self, _: &Term<f64>) -> Self::Output {
//!         Xpr::new(42.)
//!     }
//! }
//!
//! // create an expression
//! let x = -Xpr::new(2.)/Xpr::new(5.);
//!
//! // fortitwoify the expression
//! let y = Fortytwoify.fold(&x);
//!
//! // lazily evaluate the expression
//! assert_eq!(y.eval(), -1.);
//! ```
//!
//! Refer to the documentation of [`Fold`] for more useful examples.
//!
//! ## Limitations
//!
//! Current limitiations of the library are
//!  - that only expression holding terminals of the same type can be transformed using [`Fold`],
//!  - that terminals are the only expressions that can be transformed using [`Fold`].
//!
//! As a consequence, **we can not mix e.g. scalars, vectors and matrices in expressions**, which is
//! a major limitation.
//!
//! These restrictions could easily be lifted, once Rust supports
//! [specialization](https://github.com/rust-lang/rust/issues/31844), which is probably not any
//! time soon.
//!
//! ## Performance
//!
//! xpr allows us to write arithmetic operations like we would expect and hide away ugly implementation
//! details like optimizations, which the user should neither have to worry about, nor interfere with.
//!
//! What is the overhead of this? Spoiler alert: **xpr provides zero-cost abstraction!**
//!
//! Similarly to the
//! [analysis in the boost::yap documentation](https://www.boost.org/doc/libs/1_71_0/doc/html/boost_yap/manual.html#boost_yap.manual.object_code),
//! we can compare the assembly code of an xpr implementation to a native implementation.
//! The following code introduces two functions, `eval_native` and `eval_as_xpr`, were the former performs some arithmetic calculation directly and the latter creates an xpr
//! expression and evaluates it.
//!
//! ```
//! use xpr::Xpr;
//!
//! #[derive(Debug, Copy, Clone)]
//! struct Num(f64);
//!
//! impl std::ops::Add for Num {
//!     type Output = Num;
//!     #[inline]
//!     fn add(self, other: Num) -> Num {
//!         Num(self.0 + other.0)
//!     }
//! }
//!
//! impl std::ops::Mul for Num {
//!     type Output = Num;
//!     #[inline]
//!     fn mul(self, other: Num) -> Num {
//!         Num(self.0 * other.0)
//!     }
//! }
//!
//! fn eval_native(a: Num, x: Num, y: Num) -> Num {
//!     (a * x + y) * (a * x + y) + (a * x + y) +
//!     (a * x + y) * (a * x + y) + (a * x + y) +
//!     (a * x + y) * (a * x + y) + (a * x + y) +
//!     (a * x + y) * (a * x + y) + (a * x + y)
//! }
//!
//! fn eval_as_xpr(a: Num, x: Num, y: Num) -> Num {
//!     let a = Xpr::new(a);
//!     let x = Xpr::new(x);
//!     let y = Xpr::new(y);
//!     (
//!         (a * x + y) * (a * x + y) + (a * x + y) +
//!         (a * x + y) * (a * x + y) + (a * x + y) +
//!         (a * x + y) * (a * x + y) + (a * x + y) +
//!         (a * x + y) * (a * x + y) + (a * x + y)
//!     ).eval()
//! }
//!
//! fn main() {
//!     let a = Num(1.);
//!     let x = Num(2.);
//!     let y = Num(3.);
//!     // println!("{:?}", eval_as_xpr(a, x, y));
//!     println!("{:?}", eval_native(a, x, y));
//! }
//! ```
//! We can look at the assembly code after optimization by building in release mode and using the `--emit=asm` option for `rustc`.
//! If we comment the last line in the main function and uncomment the second to last line, we will get the exact same assembly. This
//! does not change if you add more operations to the expression *(You might have to add `#![recursion_limit = "256"]` to the top
//! of the file)*.
//!
//! That being said, this analysis only checks a special case using simple data types and only evaluation. The performance still needs to
//! be tested for more complex data types, expressions and fold operations.
//!

#[macro_use]
mod ops_macros;
mod foldable;

pub mod ops;

use std::marker::PhantomData;

use crate::foldable::{Foldable, OutputFoldable};
use crate::ops::Term;

/// An expression. `Xpr` together with the [`Fold`] trait are at the heart of this crate.
///
/// The nested type is a specific struct representing the operation, e.g. [`ops::Add`].
///
/// `Xpr` instances should be instantiated via the [`Xpr::new`] method, which creates
/// `Xpr<ops::Term>` leaf expressions. The other variants are constructed from them by
/// applying operations to the leaf expressions.
///
/// ```
/// use xpr::*;
/// let x = Xpr::new(5);
/// let y = Xpr::new(1);
/// let z = x * y;
/// //type of z is xpr::Xpr<xpr::ops::Mul<xpr::Xpr<xpr::ops::Term<{integer}>>, xpr::Xpr<xpr::ops::Term<{integer}>>>>
/// ```
#[derive(Debug, Copy, Clone)]
pub struct Xpr<T>(T);

//In a perfect world, this would be  an enum. But this only makes sense as soon as
// enum variants are promoted as first class types, so that we have per variant impls and irefutabl
// patterns in fn args, see also https://github.com/rust-lang/rfcs/pull/2593

impl<T> Xpr<Term<T>> {
    /// creates a new  leaf expression.
    #[inline]
    pub const fn new(t: T) -> Self {
        Self(Term(t))
    }
}

impl<T, F> Foldable<F> for Xpr<T>
where
    T: Foldable<F>,
    F: Fold<Self> + Fold<T>,
{
    type Output = <F as Fold<Self>>::Output;

    // ping-pongs to Fold::fold
    #[inline]
    fn fold(&self, f: &mut F) -> Self::Output {
        f.fold(self)
    }
}

impl<T, U> Fold<Xpr<T>> for U
where
    U: Fold<T>,
    T: Foldable<Self>,
{
    type Output = OutputFoldable<Self, T>;

    #[inline]
    fn fold(&mut self, Xpr(t): &Xpr<T>) -> <T as Foldable<Self>>::Output {
        // ping-pong to the Foldable::fold impl for Term<T> and Add<L,R>
        t.fold(self)
    }
}

/// internal type for evaluating expression. It folds each terminal in an expression tree to its wrapped
/// type and performs the operations on its upwards traversal through the tree, thus evaluating the expression.
#[doc(hidden)]
pub struct Evaluator<T>(pub PhantomData<T>);

impl<T> Fold<Term<T>> for Evaluator<T>
where
    T: Clone,
{
    type Output = T;
    /// replaces Terminal values with their wrapped type
    #[inline]
    fn fold(&mut self, Term(x): &Term<T>) -> T {
        x.clone()
    }
}

impl<U> Xpr<U> {
    /// evaluates the expression by unwrapping all terminals and applying the operations
    /// in the expression. It is synactic sugar for folding the expression with [`Evaluator`].
    #[inline]
    pub fn eval<T>(&self) -> foldable::OutputFoldable<Evaluator<T>, Self>
    where
        T: Clone,
        U: Foldable<Evaluator<T>>,
        Evaluator<T>: Fold<U> + Fold<Self>,
    {
        Evaluator(PhantomData::<T>).fold(self)
    }
}

/// 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.
///
pub trait Fold<T: ?Sized> {
    // implement the [fold pattern](https://rust-unofficial.github.io/patterns/patterns/creational/fold.html)

    /// The output of [`Fold::fold`], `Self` will replace all instances of `T` in an expression by values of this type.
    type Output;

    /// perform the replacement
    fn fold(&mut self, _: &T) -> Self::Output;
}

/// A trait for converting an instance into xpr terminals
pub trait Expression {
    /// Wrap a reference of self in an Xpr terminal
    #[inline]
    fn as_xpr(&self) -> Xpr<Term<&Self>>
    where
        Self: Sized,
    {
        Xpr::new(self)
    }

    /// Consume self and return as Xpr terminal
    #[inline]
    fn into_xpr(self) -> Xpr<Term<Self>>
    where
        Self: Sized,
    {
        Xpr::new(self)
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_eval() {
        let x = Xpr::new(1) + Xpr::new(5);
        assert_eq!(x.eval(), 6);
        assert_eq!(x.eval(), x.fold(&mut Evaluator(PhantomData::<i32>)));
        assert_eq!(x.eval(), Evaluator(PhantomData::<i32>).fold(&x));
    }

    struct Num(i32);
    impl std::ops::Add<i32> for Num {
        type Output = i32;
        fn add(self, other: i32) -> Self::Output {
            self.0 + other
        }
    }

    // #[test]
    // fn test_eval_different_terminal_types()
    // {
    //     // let x = Xpr::new(Num(2)) + Xpr::new(1);
    //     // assert_eq!(x.eval(), 3);
    //     assert!(false);
    // }
}