ElectrifyPro / cas-rs

An opinionated computer algebra system written in Rust, used by CalcBot.
MIT License
35 stars 3 forks source link

Ambiguity Between Implicit Multiplication and Function Calls #3

Open BilakshanP opened 1 month ago

BilakshanP commented 1 month ago

Since func(...) and func (...) are treated equivalently, the expression x (...) is interpreted as a function call (x called with (...)) rather than implicit multiplication. Due to this ambiguity, I have temporarily removed this test case (and improved the description) until the behavior can be clarified or adjusted.

Reference: #2 Manual std::hash::Hash implementation for consistency with PartialEq

ElectrifyPro commented 1 month ago

I didn't even realize that was possible! Thank you for discovering this.

The best solution I can think of is to make func(...) always be a function call, and make func (...) (with a space) always be implicit multiplication. It'll be easy to implement and makes the behavior consistent and easily documentable, and it allows implicit multiplication to stay, although it forces a particular style on developers who prefer func (...) for their function calls.

There are some other interesting ideas:

  1. Make func(...) or func (...) always be function calls, and limit implicit multiplication to "unambiguous" expressions, like 3(x + 4), or (x + 4)3. This appears to be the current behavior.
  2. Make func(...) or func (...) always be implicit multiplication, and add some other syntax for function calls, like func.call(...). Consistent, but somewha tdifficult to implement, and might be confusing to programmers.
  3. Parse func(...) as an ambigious expression. At runtime, check to see if func is indeed pointing to a function and call it if it is; otherwise, treat it as implicit multiplication. Hard to implement and has the potential to be highly confusing!
  4. Remove implicit multiplication or function calls entirely. Hopefully we won't have to 😅
BilakshanP commented 1 month ago

3 is the best solution, but it'll be a bit of work. But, doable nonetheless else we would fall back to 1.

2 and 4 are a big no-no in my opinion.

ElectrifyPro commented 1 month ago

To me, it seems like the detriments of solution 3 outweigh the benefits. It would certainly be highly flexible, but it becomes more difficult to look at CalcScript code and know whether a function will be called or not. In addition, by moving the check from compile-time to runtime, we might lose the ability to catch particular errors early on, and performance would suffer slightly. (In the future, I want to make CalcScript compile into bytecode instead of using a tree-walking interpreter, which would be faster and easier to debug, see the dev branch!)

My initial idea aims to also be as flexible as solution 3, but with the benefit of being able to unambiguously determine the behavior of func() or func () at compile-time, and avoiding the other issues that solution 3 brings. Forcing a style on the developer may be a bit annoying, but it may be worth it for flexibility and consistency.

However, I am open to other ideas or suggestions. If you have other reasons to prefer solution 3, I'd be happy to hear them!