mathjs
mathjs copied to clipboard
Error: Method Map.prototype.has called on incompatible receiver #<Map>
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
- Install NestJS... > npm i -g @nestjs/cli
-
nest new sandbox
-
cd sandbox
- Install MathJS library as a dependency > npm i mathjs
- Copy the contents the txt file into app.service.ts (see next comment)
- Start NestJS Server > npm run start:dev
- 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?
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 :)
@jhugman This is probably your area of expertise, let me know if I got something wrong :)
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.
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.
- The
scopethatmathjspasses thetransformfunction is aMap. - The
scopeis 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 aparentScope, 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);
};
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.