bertiqwerty / exmex

Math parser and evaluator in Rust, capable of partial differentiation, allows the use of custom operators.
Apache License 2.0
39 stars 6 forks source link

Binary operators with function call syntax #41

Closed terrorfisch closed 9 months ago

terrorfisch commented 1 year ago

Is it possible to add function call syntax to binary operators? Possibly extended to n-ary operations.

My motivating example is that I want to support max and min functions.

A possible workaround for me is to add a tuple variant to my Value type which is built through a , operator and use unary operators for this. Unfortunately this blows up all other code because then there are a lots of non-sensical states representable in the type system as not all values are allowed as operator inputs.

As I see there are the following alternatives:

  1. Do nothing and push this into the Value type if required
  2. Add an optional function name to each binary operator
  3. Add a n-ary operator with function call syntax
bertiqwerty commented 1 year ago

When I created the value feature I thought about adding a tuple type. But I dismissed it because the code looked too bloated. I think one would need to extend the parser and give a special meaning to the comma.

terrorfisch commented 1 year ago

I can have a look at it. Do you think adding such an operation is feasible:

struct NAry<T> {
    // bit field that signifies with how many arguments this function can be called (used for parsing)
    allowed_argument_count: u64,
    // Implementation of the n-ary operation
    apply: fn(&[T]) -> T,
}

impl<T> NAry<T> {
    fn is_allowed_argument_count(&self, n: usize) -> bool {
        self.allowed_argument_count & (1 << n) != 0
    }
}

EDIT: After revisiting the evaluation code: This might be hard to integrate witout performance degradation.

EDIT2: Something along these lines should have litte performance impact if the branch predictor catches up:

fn eval_flatex_u64<T: Clone + Debug>(
        numbers: &mut [T],
        ops: &[FlatOp<T>],
        prio_indices: &[(usize, usize)],
    ) {
        let mut ignore = 0_u64;

        for (&idx, &additional) in prio_indices {
            let mut rotated = ignore.rotate_right(idx as u32 + 1);
            let mut shift_right = rotated.trailing_ones() as usize + 1;
            let shift_left = rotated.leading_ones() as usize;

            let num_1 = numbers[idx - shift_left].clone();
            let num_2 = numbers[idx + shift_right].clone();
            let mut additional_nums = Vec::new();
            for add in 0..additional {
                let next_shift = todo!();
                additional_nums.push(numbers[idx + next_shift]);
                ignore |= todo!();
            }

            numbers[idx - shift_left] = {
                let bop_res = (ops[idx].nary_op.apply)(num_1, num_2, &additional_nums[..]);
                ops[idx].unary_op.apply(bop_res)
            };
            ignore |= 1 << (idx + shift_right);
        }
    }
terrorfisch commented 1 year ago

This might even able to speed up evaluations of binary operations if we are able to flatten "a*b*c" into a single n-ary operation.

bertiqwerty commented 1 year ago

Hmmm... I need to have a closer look, but Vec::new looks expensive to me.

terrorfisch commented 1 year ago

Oh, the Vec is just a placeholder. Vec::new by itself is a const fn that is very cheap especially if moved out of the loop. The Vec::push is potentially more expensive but it is not called for regular binary ops and one could use a SmallVec.

bertiqwerty commented 1 year ago

Yes sure. But still slower than no Vec. Benchmarks could tell if this is relevant.

bertiqwerty commented 1 year ago

Oh. The vector would only increases in case someone would really have more than 2 operands. Ok Agreed. Shouldn't be a performance problem compared to what we have right now.

bertiqwerty commented 9 months ago

Binary function call syntax has been implemented with #52. N-ary function call syntax might come some time in the future. Feel free to create a separate issue for that in case of demand.