Math formula mode

Hello, I really like a lot of concepts in the Rust language over C and C++.

One of my personal pain points with many programming languages (C, C++, C#, Python, ...) is mathematical code. The "code" always seems so far away from an actual math formula. This often makes it hard to read and check for implementation mistakes.

Some examples:

  • 4.0 or worse 4.0.into() instead of just 4 to be "type safe"
  • f32::sin(x) or x.sin() instead of just sin(x)
  • x * x * x instead of just x^3
  • Weird line breaks in formulas because of auto-format

I was wondering if Rust could make progress here. Maybe in form of a "formula scope" where one can write "natural" math formulas?

Example:

fn foo(x: f32, y: f32) -> f32 {
  let z, h: f32;
  formula {  // << new keyword?
    h = x^2 + 5 y^3  // gets translated to: x*x + 5.0*y*y*y
    z = h x^2 + 2 x sin(h)  // translated to: h*x*x + 2.0*x*f32::sin(h)
  }
  return z;
}

You could probably write a proc macro that does this.

I doubt it would ever become a core part of rust syntax, though.

12 Likes

Seems one persons step forward is another persons step backwards.

We have plenty of languages that allows writing such maths expressions in more concise high school maths kind of way. To do so they tend to be languages that are rather lax about types and allow all kind of errors to creep in that many people would prefer to avoid.

A step forward for Rust is to be far more rigorous about types. And as a result require some extra syntax sprinkled around.

Edit: Corrected a typo : "an other" instead of "abetter". No offence intended.

1 Like

This wouldn't really be practical. Mathematical notations aren't very well-defined, and tend to be very fluid (and they change depending on whatever your trying to do). To properly support the full range of math formulas (including higher order math, vector computation, linear algebra, etc.), we'd practically need an entirely new parser and lexer on top of the existing lexer and parser. And given how fluid math notation is, it would be an incredibly complex parser at that.

9 Likes

I wrote a proc_macro that does this as part of bullet. The problem is that in order to parse some expression, you need to know their types. Think of f x: If f is a function, this means f(x), otherwise f * x.

It would have been nice for rust to offer a power operator. Your use of ^ makes me cringe because I've worked with C++ code that overloaded that operator to mean power and it made for the most subtle of bugs due to its precedence in C++ not matching the precedence in math. I presume you would change the precedence to match math notation, but sounds so would make your formula more even more jarring to developers who would have an extra hurdle in translating between formula more and rust code.

Personally, I think x.powi(3) is good enough syntax, provided it is implemented to use the optimal number of multiplications.

Having sin(x) doesn't require any special mode, you can just define that function to be whichever one you care for.

It's now implemented using the llvm powi intrinsic which exists for exactly that reason. Though beware that it allows different calculation approaches for common subexpression elimination, so if you need perfect ½ULP reproducible answers you might want to avoid it (for anything but .powi(2)).

1 Like

It is great that you think of yourself as a better person, but may I remind you of the code of conduct?

I do not think this has to be true.

1 Like

May I ask what a "proc macro" is?

In addition to the ambiguity of some maths notation, it can be downright unwieldy to try to translate many common notations (sums, integrals, exponents, derivatives, matrices) into linear text. These notations were developed for 2-d writing. Aside from a few additional arithmetical/functional notations, what benefit would even be available?

Maths notation simply doesn't do enough to describe the behavior of the machine you're asking it to run on.

For example, you gave this:

x^2 + 5 y^3  // gets translated to: x*x + 5.0*y*y*y

But is that the translation you should use? Why x*x and not x.powi(2)? Why not (y*y*y).mul_add(5.0, x*x)?

2 Likes

Oh my God, I swear that is not what I meant to type. It's supposed to read:

Seems one persons step forward is another persons step backwards.

I'm not used to how a Mac so easily "corrects" my typos into something far more wrong than the original error.

I was merely observing that what some people see as an improvement others may see as the opposite.

Sorry all.

9 Likes

proc macro = procedural macro:

https://doc.rust-lang.org/reference/procedural-macros.html

Macros in general allow you for example to extend the Rust language so that you can embed a DSL (Domain Specific Language) (or mini-language) inside your Rust code.
For example: JSON inside Rust:

HTML inside Rust:

I had a similar idea a couple of years ago but didn't have the time to implement it.
So macros are a perfect fit for that.

An alternative would be to use build.rs and parse the Rust code by hand for math terms an turn them into Rust code. The LALRPOP parser generator uses this approach:

1 Like

Ok.. I was honestly surprised about this change of the proverb.

What I am trying to suggest/ponder would indeed not be for everyone. I just see so many great things in Rust that I was wondering if it might help in that direction.

Regarding "sprinkling" in syntax to help with type safety: This is legal in Rust:

let x = 2.0;  // compiler can infer the type
let y = x;  // no type necessary at all

where C would of course insist on:

double x = 2.0;
double y = x;

There is var in C++, but the Rust compiler goes beyond that and can infer template parameters on traits, etc. So there is the desire and ability to reduce clutter while still staying type safe.

Thank you! That will take a while to digest

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.