Skip to content

Conversation

blerner
Copy link
Member

@blerner blerner commented Oct 15, 2025

Alternate design to #1818, where instead of manually placing errbacks parameters everywhere throughout the code, and where even a single missed errbacks argument could be a brittle and hard-to-find failure later, this one changes js-numbers to only expose a module-creating function that closes over the errbacks parameter, and exports the created jsnums instance as a field on runtime -- so that modules that previously imported jsnums directly can now access it off their respective runtimes.

I've pulled in the tests from #1818, and they all pass, and I think I've pulled in any substantive changes to runtime.js as well.

@blerner blerner requested a review from jpolitz October 15, 2025 21:10
blerner pushed a commit to brownplt/code.pyret.org that referenced this pull request Oct 15, 2025
…dules obtain the correct jsnums library that now closes over an appropriate errbacks parameter
Ben Lerner added 2 commits October 15, 2025 17:30
@jpolitz
Copy link
Member

jpolitz commented Oct 15, 2025

Makes me a bit nervous that we have to make sure we account for every client of js-numbers here, because it is no longer exporting a bunch of functions it used to.

Is there a way to do this that is backwards-compatible?

Like, maybe all the existing functions are also exported, with an errbacks argument, and they call MakeNumberLibrary with the given errbacks and then the appropriate function?

Sketch:

const libByErrbacksCache = new Map(); // cache of instantiated NumberLibrary objects by errbacks object identity
function makeBackcompatWrapper(name) {
  NumbersExport[name] = function() {
    if(libByErrbacksCache[name]) { return errbacksCache[name].apply(arguments); }
    const errbacks = arguments[arguments.length - 1];
    const lib = MakeNumberLibrary(errbacks);
    libByErrbacksCache[errbacks] = lib;
    return lib[name].apply(arguments);
  }
}
makeBackcompatWrapper("fromFixnum");
makeBackcompatWrapper("fromString");
...

@jpolitz
Copy link
Member

jpolitz commented Oct 15, 2025

Properties this has:

  • If someone was forgetting errbacks arguments, oh well, they still are
  • If someone calls over and over again with an errbacks argument, they pay a small cost of the cache check but no more
  • We don't break any existing code

@blerner
Copy link
Member Author

blerner commented Oct 15, 2025

I don't think that will quite work, because BigInteger and Rational are generative types -- they're defined within the MakeNumberLibrary() closure, so if a "legacy caller" of the non-closed-over library uses multiple functions in a row (with structured numbers), they'll all fail each others' isPyretNumber checks. We'd have to do something fancier with object-identity of the errbacks objects themselves, I think, to get the cache to not be generative?

Do we have any library authors who write native JS libraries for Pyret that we're not aware of? Because if not, then as of this PR (and the counterpart for CPO), the only mentions of js-numbers are:

blerner@blerner-x1:~/pyret-lang$ grep -rn "pyret-base/js/js-numbers" src/ tests/ --exclude="*.jarr"
src/js/base/js-numbers.js:107:define("pyret-base/js/js-numbers", function() {
src/js/base/runtime.js:3:   "pyret-base/js/js-numbers",
src/scripts/standalone-configB.json:6:    "pyret-base/js/js-numbers": "build/phaseB/js/js-numbers.js",
src/scripts/node_modules-config.json:6:    "pyret-base/js/js-numbers": "$PYRET/js/js-numbers.js",
src/scripts/standalone-configC.json:6:    "pyret-base/js/js-numbers": "build/phaseC/js/js-numbers.js",
src/scripts/standalone-configA.json:6:    "pyret-base/js/js-numbers": "build/phaseA/js/js-numbers.js",
tests/jsnums-test/jsnums-test.js:14:R(["pyret-base/js/js-numbers"], function(JNlib) {

and

blerner@blerner-x1:~/code.pyret.org$ grep -rn "pyret-base/js/js-numbers" src/ --exclude="*.jarr"
blerner@blerner-x1:~/code.pyret.org$ 

@jpolitz
Copy link
Member

jpolitz commented Oct 16, 2025

Re: “it's not hard to find all the uses”.

What about pyret-embed? What about pyret-npm? What about the anchor branch? What about Brown's internal grader or NEU's (and other similar projects we don't know about). I don't know. I could look. I've also started to lose track of all the projects. Some of these things also have issues that are hard to predict with e.g. different versions of runtime files being in caches (esp. with pyret-npm). I've been burned quite a bit recently by being flippant with breaking “internal” API changes.

Thinking more about the design options here.

@jpolitz
Copy link
Member

jpolitz commented Oct 16, 2025

A few ideas that would make me more comfortable:

  • Make this new structure be called something else. Call it js-nums, or js-numbers-errbacks, or similar. Then import it into js-numbers, and export from js-numbers something like { ...MakeNumberLibrary({some default errbacks that throw}) }. This isn't a perfect drop-in because if some code is using the errbacks interface it will not respect that, but all the happy paths will keep being happy.
  • Make this new structure be called something else. Call it js-nums, or js-numbers-errbacks, or similar. Then leave js-numbers alone except for adding a console.log or console.error saying it is deprecated and will be deleted at $DATE. Then we delete it at $DATE.

@jpolitz
Copy link
Member

jpolitz commented Oct 16, 2025

To be clear this PR is a vast improvement in ergonomics.

I just really, really don't want to have some 5-alarm fire because we broke an interface that we didn't know something relies on (could be something we wrote and forgot about! could be a huge pain of a built file sitting in someone's .pyret directory that I'm not thinking about, etc).

@blerner
Copy link
Member Author

blerner commented Oct 16, 2025

Fair points.

  • I didn't consider the anchor branch because there are already so many diverging changes that we'd have to revisit this anyway.
  • pyret-npm is a customer of Pyret, not an integrated tool, so it shouldn't import the js-numbers library anyway. (And grepping through the source shows it doesn't.)
  • I didn't check pyret-embed because it's not in brownplt (I think?) -- I just looked through your repo now, though, and I think it's the same as pyret-npm in this regard.
  • Autograders are a possible concern, yes.

I'm not sure I completely understand your latest suggestion, yet. Will think about it more...

@ds26gte
Copy link
Contributor

ds26gte commented Oct 16, 2025

Re: “it's not hard to find all the uses”.

What about pyret-embed? What about pyret-npm? What about the anchor branch? What about Brown's internal grader or NEU's (and other similar projects we don't know about). I don't know. I could look. I've also started to lose track of all the projects. Some of these things also have issues that are hard to predict with e.g. different versions of runtime files being in caches (esp. with pyret-npm). I've been burned quite a bit recently by being flippant with breaking “internal” API changes.

Thinking more about the design options here.

In case this is a useful data point: Neither Brown's nor NEU's autograder repos use pyret-lang's js-numbers.js as a JS library.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants