mathjs icon indicating copy to clipboard operation
mathjs copied to clipboard

Error: Method Map.prototype.has called on incompatible receiver #<Map>

Open bernarma opened this issue 4 years ago • 5 comments

Issue: When using MathJS in Nest.JS the following exception occurs.

[Nest] 14610 - 06/27/2021, 4:29:03 PM [ExceptionsHandler] Method Map.prototype.has called on incompatible receiver #<Map> +1419ms TypeError: Method Map.prototype.has called on incompatible receiver #<Map> at Map.has (<anonymous>) at _validateScope (/Users/berniemac/Develop/test/sandbox/node_modules/mathjs/lib/cjs/expression/node/Node.js:413:17) at Object.evaluate (/Users/berniemac/Develop/test/sandbox/node_modules/mathjs/lib/cjs/expression/node/Node.js:69:7) at f (/Users/berniemac/Develop/test/sandbox/dist/app.service.js:44:31) at integrate (/Users/berniemac/Develop/test/sandbox/dist/app.service.js:28:26) at integrate.transform (/Users/berniemac/Develop/test/sandbox/dist/app.service.js:46:20) at evalFunctionNode (/Users/berniemac/Develop/test/sandbox/node_modules/mathjs/lib/cjs/expression/node/FunctionNode.js:123:18) at Object.evaluate (/Users/berniemac/Develop/test/sandbox/node_modules/mathjs/lib/cjs/expression/node/Node.js:71:14) at Function.string (/Users/berniemac/Develop/test/sandbox/node_modules/mathjs/lib/cjs/expression/function/evaluate.js:55:36) at Object.evaluate (/Users/berniemac/D [app.service.ts.txt](https://github.com/josdejong/mathjs/files/6721136/app.service.ts.txt) evelop/test/sandbox/node_modules/typed-function/typed-function.js:1085:85)

Steps to Replicate

Using NodeJS v12.22.1

  1. Install NestJS... > npm i -g @nestjs/cli
  2. nest new sandbox

  3. cd sandbox

  4. Install MathJS library as a dependency > npm i mathjs
  5. Copy the contents the txt file into app.service.ts (see next comment)
  6. Start NestJS Server > npm run start:dev
  7. Run the command: > curl http://localhost:3000

Observe the error that is returned {"statusCode":500,"message":"Internal server error"}%

Review the console output from the NestJS Server.

I have located it to the Node.js file but am lost as to how to resolve the issue as it is simply iterating through the values in a Map. Is this something you have come across before? Could it be to do with TypeScript?

bernarma avatar Jun 27 '21 06:06 bernarma

Hey Mark, thanks for getting in touch! We've recently changed math.js scopes from plain JavaScript objects to Maps. In your code you're creating a copy of a scope using:

const fnScope = Object.create(scope); // line 43

However scope is a Map, therefore you're not creating a scope which inherits from the original scope – you're creating a broken map. You can try doing this in your browser/REPL to see that you get the same error messasge:

m = new Map()
n = Object.create(m)
n.has('a') // Uncaught TypeError: Method Map.prototype.has called on incompatible receiver #<Map>

I suggest that you replace your code with something like this:

const fnScope = new Map(scope);

This wouldn't be super efficient, since you'd be cloning the Map every time. If you're worried about performance, you can create your own implementation which provides the has, get and set methods and math.js will happily accept it.

Let me know if it worked!

Also, it is possible that there is some outdated information in the official documentation. If you know about a part of the docs which still says scope is an object, please do tell :)

cshaa avatar Jun 27 '21 12:06 cshaa

@jhugman This is probably your area of expertise, let me know if I got something wrong :)

cshaa avatar Jun 27 '21 14:06 cshaa

The change works so I am able to progress past this issue.

Many thanks for your assistance. I was pulling my hair out over the weekend trying to work out what the issue was.

bernarma avatar Jun 27 '21 22:06 bernarma

Hi, @m93a @bernarma

Thanks for the heads up, and the very succinct steps to reproduce.

@m93a is right: for a number of reasons (safety and extensibility) we've changed the type of the scope object from a raw JS object to a Map.

  1. The scope that mathjs passes the transform function is a Map.
  2. The scope is a sub-scope with access to all the main scope, with the passed arguments copied in as the argument names when the function was defined. The main scope, is passed in as a parentScope, but you probably don't want that in this case.

With this knowledge, we should be able to change line

const fnScope = scope; // line 43

fnScope.set(variable, x); // line 49

You might be tempted to use

const fnScope = Object.create(scope.entries()); // line 43

fnScope[variable] = x; // line 49

but for a number of reasons, I wouldn't recommend this— mostly because it's considerably less efficient, but also because it gets tricky when you start making your own custom scopes.

@m93a: This might well need a documentation change to the custom argument parsing example, and perhaps exposing a createSubscope method.

integrate.transform = function (args, math, scope, parentScope) {
  // determine the variable name
  if (!args[1].isSymbolNode) {
    throw new Error('Second argument must be a symbol');
  }
  const variable = args[1].name;

  // evaluate start, end, and step
  const start = args[2].compile().evaluate(scope);
  const end = args[3].compile().evaluate(scope);
  const step = args[4] && args[4].compile().evaluate(scope); // step is optional

  // Use the subscope for this argument scope, linked to the provided scope.
  // This is the scope with the arguments copied from the function, and we use it to apply the variable
  const fnScope = scope;

  // construct a function which evaluates the first parameter f after applying
  // a value for parameter x.
  const fnCode = args[0].compile();
  const f = function (x) {
    fnScope.set(variable, x);
    return fnCode.evaluate(fnScope);
  };

  // execute the integration
  return integrate(f, start, end, step);
};

jhugman avatar Jun 28 '21 23:06 jhugman

Since what remains seem to be general ideas for providing better ways to manipulate scopes (which I agree could be useful), moving this to a Discussion.

gwhitney avatar Oct 03 '23 03:10 gwhitney