mathjs icon indicating copy to clipboard operation
mathjs copied to clipboard

Function Call Operator ’()‘ cannot be used continuously

Open HK-SHAO opened this issue 3 years ago • 10 comments

Function Call Operator ’()‘ cannot be used continuously just like this

> f(x)=g(y)=x+y
// f(x)
> f(1)
// g(y)
> f(1)(2)
// TypeError: Unexpected type of argument in function multiplyScalar (expected: number or Complex or BigNumber or Fraction or Unit or string or boolean, actual: function, index: 0)

image

HK-SHAO avatar Apr 14 '21 07:04 HK-SHAO

Indeed, this is unfortunately not possible: it conflicts with implicit multiplication.

josdejong avatar Apr 14 '21 08:04 josdejong

Indeed, this is unfortunately not possible: it conflicts with implicit multiplication.

You're right. It's a pity that I can't do magic like that. I can only do it like this 🤣

> f(x)=g(y)=x+y
// f(x)
> f(1)
// g(y)
> a=f(1)
//g(y)
> a(2)
// 3

HK-SHAO avatar Apr 14 '21 08:04 HK-SHAO

Indeed. If you have these curried functions it may be easier to create a helper function around f and g which allows you to pass both x and y at once, since the expression parser is not able to deal with this unfortunately.

josdejong avatar Apr 14 '21 08:04 josdejong

Alternatively, we might add a virtual property .call to math.js functions to resolve the ambiguity:

f(x) == f.call(x)
f(x) * y == f(x)(y) != f(x).call(y) == f.call(x).call(y)

It's a bit ugly, but it solves the problem.

m93a avatar Jun 10 '21 00:06 m93a

That is an interesting idea, that would definitely work.

I'm not sure if the need for it is high enough right now to justify implementing it. Current solution is to create an intermediate variable which is verbose but works too.

josdejong avatar Jun 12 '21 08:06 josdejong

Some observations:

  1. Except when EXPR is a symbol node or AccessorNode, the syntax EXPR(A, B) is currently a syntax error. Since it can't be implicit multiplication, there doesn't seem to be any reason why it shouldn't parse to a function application for any node type of EXPR that could result in a function object. (E.g FunctionNode, ConditionalNode, etc., but not ArrayNode or RangeNode). That should take care of the poster's concern for all multi-argument calls.
  2. For one -argument calls, there are actually two syntaxes: EXPR(ARG) and (EXPR)(ARG). Given that, it would seem sensible to me to make one have the current behavior of favoring implicit multiplication and only being interpreted as function application when EXPR is a Symbol Node or AccessorNode, while making the other favor function application and be interpreted as such whenever the EXPR could syntactically conceivably produce a function and be interpreted as implicit multiplication only when it syntactically couldn't produce a function. Which was which would be a matter of taste but given the current overall preference for implicit multiplication maybe the simpler one EXPR(ARG) should favor implicit. In that case, 3(4) and (3)(4) would both be implicit multiplication, f(4) and (f)(4) would both be function calls, and f(1)(2) would be f(1)*2 while (f(1))(2) would apply the function returned by f(1) to the argument 2.
  3. currently there are two other existing workarounds: the cryptic [f(1)][1](2) and the more verbose but clearer {call: f(1)}.call(2). Both of these work because mathjs prefers function call interpretation when the EXPR is an AccessorNode, and both [EXPR][1] and {foo: EXPR}.foo are AccessorNodes guaranteed to return the value of EXPR.

Let me know if either (1) or (2) is agreeable and I will be happy to work up a PR for either or both.

gwhitney avatar Mar 29 '22 07:03 gwhitney

  1. That is a good point. I think it wouldn't do harm to allow that, except that it feels inconsistent if EXPR(A) would be handled differently than EXPR(A, B).
  2. I'm in doubt about that. I have the feeling that 3(4) should give the same result as x=3; x(4) or f=3; f(4). If we do need additional syntax like (f(1))(2) to make the result of f(1) be executed as a function, I prefer a more explicit solution like f(1).call(2). It feels quite tricky to give this kind of meaning and change in behavior to an additional pair of (...).
  3. When I try {call: f(1)}.call(2) it gives an error "Error: No access to property "call", can you execute that succesfully? Call is disabled because it can open up security issues, we have to be a bit careful there. Maybe choose an other naming, I'm not sure right now. Anyway, it would be good to have a neat "workaround" instead of needing to rely on "tricks"

josdejong avatar Apr 01 '22 09:04 josdejong

(1) well but they are already handled differently: 3(4,5) is s syntax error but 3(4) is 12. In other words, currently EXPR(A) has a wider range of semantics than EXPR(A,B), so why not extend EXPR(A,B) as well, consistent with doing what the client expects as often as possible? If EXPR evaluates to a function of two arguments, I don't think anyone will be surprised by EXPR(A,B) calling that function.

(2) a) I assume you meant x = 3; x(4) and note that right now that does not give the same result as 3(4), it throws 'x is not a function' because the parser prefers function application for SYMBOL(A). (Which is good because otherwise it would be very hard to call a one-argument function!)

(2) b) There is precedent for assigning EXPR1 REST a different meaning than (EXPR1) REST even specifically in relation to implicit multiplication: 3 / 4x is (3/4)x but (3)/4 x is 3/(4x). So why not use a similar distinction to ease computed function calls? Providing an explicit syntax for computed function calls seems no "trick" to me.

(3) My mistake: I had tried similar things like {do: f(1)}.do(2) (which works) and overlooked that some specific keys like 'call' are forbidden here.

gwhitney avatar Apr 01 '22 13:04 gwhitney

(1) 😂 good point. Yes, I'm OK with changing the behavior of EXPR(A, B) to execute this as a function (2.a) yes, sorry for the confusion, I've fixed the example in the comment. But you're right, the parser isn't working at all like that right now, and it already evaluates x(4) as a function call, I had it differently in my mind. (2.b) hm, yeah. That's true. But there is a bit of a difference: in the case of implicit multiplication like 3/4x, this is about a common use case of writing a number as a fraction. An expression like (3)/4x is a theoretic case, and in practice you'll more likely see something like (3y + 2)/4x, in which case it makes sense that it can be evaluated differently. In my mind, we currently only use parenthesis only to override precedence of operations like in 2 * (3 + 4). That's what people know of parenthesis: it's about overriding precedence. In case of implicit multiplication there are some special rules about how a specific expression is executed, but still, you can safely add parenthesis to make sure the expression parser understands evaluates it the way you want. It maybe something between my ears, but it feels to me that adding parenthesis to specify whether to execute something as an implicit multiplication or as a function invocation is different from specifying precedence of operators. That's why I have a preference for an explicit fn.call(...) syntax instead. (3) 👍

josdejong avatar Apr 08 '22 09:04 josdejong

Ok, I will work up a PR for EXPR(A,B) being a function call. And that suggests another alternative for forcing a function call: f(1)(2,) could call the result of f(1) on 2. It would probably not be hard as a consequence of EXPR(A,B) favoring function calls. It's like optional final comma in arrays and objects, just here it's in argument lists.

gwhitney avatar Apr 08 '22 13:04 gwhitney