-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
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.