josdejong / josdejong/mathjs

Interesting consequence of current parsing rules

Open
#3,608 5 comments 0 reactions 0 assignees View on GitHub
Dominant language
JavaScript
Stars
15.1k
Forks
1.3k
PR merge metrics
No merged PRs in 30d

Description

**Q:** Without typing it into the mathjs.org REPL, what type is passed as the argument to `not` in

`math.evaluate('not(sparse([[1, 1]])).valueOf()')`

and what is the result of

`math.evaluate('typeOf(sparse([[1, 1]])).valueOf()')`

???

**A:** Turns out that in the first case, the type is Array, whereas in the second, it's SparseMatrix. In other words, the first expression is parsed as `not( (sparse([[1, 1]])).valueOf() )` whereas the second is parsed as `(typeOf(sparse([[1, 1]]))) .valueOf()`, even though they look syntactically identical. (This difference led to a bug in a mathjs expression that took me a couple of hours to figure out, unfortunately.) The difference it seems stems entirely from the arbitrary designation that `not` is a unary operator, as opposed to `typeOf` that is designated as a unary function (even though, for example, in JavaScript `typeof` is an operator).

I've suggested before that this distinction between unary operators and functions is artificial and has no benefit and that the expression language would be improved by eliminating it. I am not posting particularly to reopen that debate. I think the important thing for now is that when an operator is used with function-style syntax `not(expr)` it should parse/behave like a function, just as `and(exprA, exprB)` does. Otherwise, it's just too difficult to read and write expressions, having to remember special rules for just a small handful of specific symbols. I would suggest that the current non-parallelism between `not` and `typeOf` amounts to something at least very close to bug, and would suggest it be addressed in one or more of the following ways:

I) Just make `not` a unary function. The only side effect is that with no other changes to the parser, the expression `not true` would become illegal, and one would have to write `not(true)`.

II) Allow any unary function (e.g., `sin`) to apply to the following expression like `not` does now (i.e., in essence make all unary functions into unary operators). I know @josdejong has had concerns about this in the past; I am merely including it here to record the logical possibilities I can think of.

III) Alter the parser so that when a function or operator is used with unary function-call notation, `OP(EXPR)`, then it parses just like a unary function call, with the same precedence and grouping, etc.

IV) Try for a one-off fix for the specific troublesome expressions noted above, e,g., try making the `not` operator higher-precedence than the `.` accessor operator, so that `not S.method()` always parses as `(not S).method()` rather than `not (S.method())`, even without any parentheses, This precedence change would be at odds with JavaScript, where `!object.method()` means `!(object.method())` rather than `(!object).method()`.

V) Do nothing except document the difference between a unary function and a unary operator even more clearly, with a specific warning about the pitfall of this parsing difference between the two.

There may of course be other options I haven't thought of., and clearly I am not a fan of (V). But please just let me know what direction you'd like to go in connection with the potential difficulty highlighted here and I will file a PR as soon as I can.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.