Numbas.jme.unwrapValue()

18 views
Skip to first unread message

William Haynes

unread,
Jul 10, 2025, 11:32:51 AMJul 10
to Numbas Users
Hi Christian,

I am trying to write a javascript extension and I'm passing it a list of objects.

I'm using  Numbas.jme.unwrapValue()to get the actual values, but this function apparently doesn't handle rational values and also decimal values like scalar(qty(1, 'm') properly. 

Thanks,
Will






Christian Lawson-Perfect

unread,
Jul 16, 2025, 7:36:31 AMJul 16
to numbas...@googlegroups.com
Hi Will,
As far as I can tell, they're working correctly: they return the underlying representation of the value, which isn't a standard JavaScript Number object.
If you want to ensure you get Number objects, you could cast them before using unwrapValue: Numbas.jme.unwrapValue(Numbas.jme.castToType(token, 'number')) instead of just Numbas.jme.unwrapValue(token).

--
You received this message because you are subscribed to the Google Groups "Numbas Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to numbas-users...@googlegroups.com.
To view this discussion, visit https://groups.google.com/d/msgid/numbas-users/ab6a600e-3c51-404e-8a8d-5b0c1680afb3n%40googlegroups.com.

William Haynes

unread,
Jul 16, 2025, 9:49:35 AMJul 16
to 'guillaume theo' via Numbas Users
Hi Christian,

Thanks, that was the part I was missing, I think.  I have managed to work around my problem, but I'm pretty sure my solution is a hack.

Could you say a little more about how to handle this situation when writing an extension?

Modifying the example on the extension documentation page to take a list, rather than two numbers breaks when one of the list members is rational:

difference([10,  12]) = 2
difference([10, 1/2]) = NaN
difference([10, 0.5]) = 11.5

scope.addFunction(
new funcObj(
"difference",
[TList],
TNum,
function (L) {
return Math.abs(L[0] - L[1]);
},
{ unwrapValues: true, random: false }
)
);

Thanks,

Will


William Haynes Engineering Department / Professor Massachusetts Maritime Academy 100 Academy Drive Buzzards Bay, MA 02532 wha...@maritime.edu

Christian Lawson-Perfect

unread,
Jul 16, 2025, 10:14:40 AMJul 16
to numbas...@googlegroups.com
You can use the type signature syntax to specify that the list should be a list of numbers, and all the elements will be converted automatically for you:

    extension.scope.addFunction(new funcObj('difference',['list of number'],TNum,function(ns){ return Math.abs(ns[1]-ns[0]); }, {unwrapValues:true, random: false}));

I need to check if I've documented the type signature syntax. There's a description of the grammar in the code documentation - https://github.com/numbas/Numbas/blob/053566e7a6b3ff1ef09380850f87765ee4504cce/runtime/scripts/jme.js#L5456 - but I don't think there's anything in the main docs.

William Haynes

unread,
Jul 16, 2025, 10:22:09 AMJul 16
to 'guillaume theo' via Numbas Users
WOW, thank you!   I don't think that I would have found that.

Will

William Haynes

unread,
Jul 17, 2025, 12:05:58 PMJul 17
to 'guillaume theo' via Numbas Users
Hi Christian,

I'm sorry to keep troubling you, but I am really struggling to get an extension using jsxgraph to work properly. Some of my issues are likely due to my misunderstanding, but others might be real.

I've made an example to demonstrate my problems here: https://numbas.mathcentre.ac.uk/question/176963/test-dots-extension/  and the extension code is below.

Issues:

  • The diagram displays properly in the variable list, but appears with no height when the question runs.  I have fixed this by adding appropriate css to the preamble, but is this intended?
  • My example works using unwrapValue() on line 13, but when I try to use {unwrapValues: true, random: false} in line 28 instead, the function returns an empty dictionary dict(), not the expected html result.element.
  • I still want to change the visibility of objects when advice is shown, but trying to grab the board using  question.scope.getVariable('diagram').board in the preamble returns undefined.
  • Also minor, but clicking on "see the source code" on this page: https://numbas.mathcentre.ac.uk/extensions/2/documentation  returns Forbidden.  

Any help?

Thanks, 
Will


Numbas.addExtension("dots", ["jme"], function (extension) {
var jme = Numbas.jme;
var funcObj = Numbas.jme.funcObj;
var THTML = Numbas.jme.types.THTML;

function drawDots(plist) {
var result = Numbas.extensions.jsxgraph.makeBoardPromise(200, 200, {
boundingBox: [-5, 5, 5, -5],
});

result.promise.then(function (board) {
for (var p of plist) {
p = jme.unwrapValue(p);
board.create("point", p.pt, {color: p.color }); // end create point
} //end loop
});
return result.element;
} // end of drawDots function

extension.scope.addFunction(
new funcObj(
"drawDots",
["list of dict of (list of number) or string"],
THTML,
function (plist) {
return drawDots(plist);
},
{}
) // end of function definition
); // end of add function
}); // end of add extension

Christian Lawson-Perfect

unread,
Jul 18, 2025, 4:12:41 AMJul 18
to numbas...@googlegroups.com
You've done well getting as far as you have!

You might not have noticed that the JSXGraph extension's `jsxgraph` function doesn't return a value with type 'html', it returns a 'jsxgraphboard' object. The jsxgraphboard constructor (available to you as Numbas.jme.types.jsxgraphboard) takes number values for the width and height, and returns an object with a `board` property. The `makeBoardPromise` function is used by `jsxgraphboard`, and expects the width and height to be specified with their units, i.e. '200px' instead of '200'.
I think the most straightforward thing is to use the `jsxgraphboard` constructor, so you don't have to replicate any of the stuff it does.

For unwrapping the `plist` dictionary, I appreciate that the behaviour around wrapping and unwrapping arguments is hard to understand. It's a bit of a mess, due to the first design ~14 years ago not really being versatile enough.

In `funcObj`, you can give `null` as the argument where the function should go, and then give a property `evaluate: function(args, scope)` in the options object. That gives you the list of arguments as JME tokens, and the evaluation scope. You could then just call `jme.unwrapValue` on the `plist` argument.

Finally, you should add 'extensions/jsxgraph/jsxgraph.js' as a dependency for your extension, so it only loads once the JSXGraph extension has loaded.

As a bit of advice for doing this development, the browser dev tools console is really helpful. By putting some console.log lines in, you can see what values look like at different points in your code, and even set break points to step through execution line by line.

I hope you don't mind that I've edited your extension with these changes. 

--
You received this message because you are subscribed to the Google Groups "Numbas Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email to numbas-users...@googlegroups.com.
Reply all
Reply to author
Forward
0 new messages