At FOSDEM 2025, I have bought a Bangle.js 2 - a funny little smartwatch that runs on JavaScript. As a strong lover of Elm, I decided to try and build a smart watch that runs on Elm! However, I have run into some ReferenceError messages when trying to upload compiled Elm.
Take a look at this snippet on GitHub. Most errors come down to mistakes like these, where the order seems wrong on the JavaScript side. The variable _Utils_append is defined by _Utils_ap, which doesn’t exist yet because it is defined right after!
This is just one example, but the JavaScript code seems to contain multiple of these weird orderings that mess up the JavaScript engine on the tiny device. I was going to write pull requests for these issues, but I wanted to see if anyone here understands why the code is written like this.
Why is it written this way?
I have an open mind that this implementation was intentional, yet I struggle to find an answer. My current theory is that this implementation was because a functional programmer forgot that the order of function definitions might matter in imperative languages.
Looking at the git blame, this change was initiated in this commit, where the functions are separated into separate variables. The commit discusses some optimization but I fail to see which of the three optimizations is being made here. Hence I don’t know whether the “wrong” order is deliberate and/or necessary.
Why hasn’t anyone encountered this error before?
This error was fairly easy to figure out logically, but I’m dumbfounded by why no-one else has encountered this before. Do all browsers intuitively replace the _Utils_ap variable, which is originally of the undefined value, into the function’s definition once it has been set?? Is this a universal feature that only the Bangle doesn’t apply?
Is this worth fixing?
I would like to write a pull request so that this issue can be resolved. However, the elm/core library doesn’t have a track record of having a lot of pull requests so I might need to resolve the issues locally.
I believe function declarations (including the function body) are hoisted and thus can be called before their declaration. So this snippet of code should work fine.
Sounds like you may be running into Temporal Dead Zone issues with the ReferenceErrors you are receiving. var declared variables (like var _Utils_append = F2(_Utils_ap);) would not have the TDZ issue because they are initialized with undefined when hoisted.
Thanks for the response! I wasn’t aware of the concept of hoisting. However, I don’t think that the TDZ is the problem here. (Unless I misunderstand you.)
This is the output I’m getting from the Bangle.js 2 smartwatch when running my Elm code:
Compiled in DEV mode. Follow the advice at https://elm-lang.org/0.19.1/optimize for better performance and smaller assets.
app.js ReferenceError: ReferenceError: "_Utils_ap" is not defined at line 42 col 1952 in .widcache
...wRecord;}var _Utils_append=F2(_Utils_ap);function _Utils_ap(xs,ys){if(typeof x...
^
in function called from line 55 col 14149 in .widcache
...d(_Utils_Tuple0))(0)}});}(this));{let app=Elm.Main.init();app...
So the error is specifically saying that _Utils_ap is not defined, not that _Utils_append is not defined. Perhaps Bangle.js doesn’t support hoisting?
Looking at MDN’s page on hoisting, it doesn’t seem normatively defined in ECMAScript. Would that make it a bug when aiming for compatibility?
MDN is saying the the term “hoisting” is not normatively defined, but the behaviors we defacto define as “hoisting” are certainly part of the ECMAScript spec. Just a guess, but it appears to me that Bangle.js is not a full ECMAScript implementation and handles all “hoisting” behaviors the same, which is to ignore the hoist, thus placing assignments relying on hoisting in the TDZ, and thus you get ReferenceErrors. That is a bummer. ( And sorry for all the "thus"es )
Looking into this more, looks like Bangle.js uses Espruino, and per Espruino’s FAQ, “Espruino implements a (large) subset of the full JavaScript specification, but it isn’t 100% complete”
I would venture to say it is likely Espruino’s fault, Yes.
Judging by some comments about JS hoisting, it seems that the decision was deliberate in order to save memory. I’m still going to bring it up anyway.
Do you know if there’s any (optimization) benefits to writing the elm/core JS code in this order? If there’s no benefits, I might write a pull request.
I’m not an expert at optimizing javascript at this level, and I suppose it depends, in part, on the compiler/interpreter, but I do not know of any reason this code is written in this way for optimization purposes. Others in these forums would be more qualified to answer this question.
Just a note about TDZ (Temporal Dead Zone), since it was mentioned: TDZ only applies to let, const and class. Elm uses none of those, so TDZ is a red herring in this case.
Note that even if you manage to work around Espruino’s lack of support for function hoisting, you’ll probably run into labels not being supported: Espruino Feature List. Elm generates JS with labels in it sometimes – it depends on the exact Elm code you use.
It sounds difficult to run JS you didn’t write yourself in such a limited JS engine.
Thanks for the clarification @lydell; and that is my understanding as well. My assumption was from Espruino being a non-standard implementation. If I understood @Bram in this case, the var statement, instead of returning undefined, was returning a ReferenceError; var should have returned undefined. Perhaps under the hood, Espruino is not following the standard, which would explain the non-standard ReferenceError on a var statement.