A few surprises when authoring in JavaScript

I recently tried authoring in JavaScript instead of Blocks. Overall, it was a good experience—especially after learning about not putting the code in main.ts to avoid complicating the blocks surface.

I ran into a couple of minor surprises/issues that I thought I’d share:

  • sometimes the editor completions complete too much and leave the cursor in an awkward position. A specific example: scene.backgroundImage().fillR + [tab] doesn’t just fill in the name, it completes with .fillRect(0, 0, 0, 0, 0)<cursor>. This is mildly inconvenient because I already knew I needed to fill those parameters in with values, and now I have to arrow back to get to them. In VS code, it looks like when I [tab] to select a completion, it typically just fills in the name and waits for me to type the opening parenthesis.
  • I really miss code formatting when working in the JS editor. I’ve enjoyed using some other online JS editors (for example, CodeSandbox—which also uses the Monaco editor) which enable Prettier by default. I’d love to see some kind of automatic code formatting enabled/supported in makecode’s JS editor too.

Language surprises:

I hadn’t encountered https://makecode.com/language until after writing a bit. It did answer some of my questions. But I did still run into a couple of things that I didn’t see mentioned there:

No Array.from

Array.from with an array-like that declares a length + a mapping function is a really nice way to initialize a new array with values:

const cells = Array.from({ length: rows * columns }, () => 0);

See also: https://2ality.com/2018/12/creating-arrays.html#mapping-with-array.from()

Surprising parameter order for Math.clamp

This is mostly user error, but I assumed Math.clamp's parameter order would be (value, min, max). The libraries I checked use this order (notable exception: CSS’s clamp function), and this matches the stage 1 proposal’s spec. … if this proposal advances, that may be interesting for the way the function is currently provided by makecode.

See also: https://github.com/rwaldron/proposal-math-extensions

No ?? (nullish coalescing operator)

Nullish coalescing is supported by TypeScript as of 3.7 (source). But when I used it, the editor complained as if it didn’t understand the operator. This could be an editor issue, or a static TypeScript limitation (or both).

Our TypeScript version is 1.8.6. We don’t have any plans on upgrading, unfortunately, because we rely pretty heavily on the internals of the compiler.

1 Like

Actually, I think it’s 2.6. Regardless it’s pretty old!

1 Like

We could add Array.from. Could you file a bug on it?

1 Like

Clamp: for app compat reasons, we can’t change the order of that one.

1 Like

Could you file a bug on [Array.from]?

@peli, absolutely. Does this one go in pxt? (Rather than pxt-arcade.)

@jacob_c file it in either place, we’ll make sure it gets to the right destination!

1 Like

Hey thanks for the feedback! I’m working on some auto-complete improvements right now, and I think we can better handle the case you mentioned.

As for null coalescing, we might be able to add support for that eventually. We don’t track the official TypeScript releases like Richard said (because we have our own compiler to assembly), and we use an old version of typescript to do parsing and type checking. But I would love this as well

1 Like

Put it in pxt-arcade

You can do a.map(x => ...) instead of Array.from(a, x => ...)

How would you first initialize that Array to be an expected length?

When I try new Array(10) (or Array(10)) makecode displays an error indicating that Array is a namespace, not a class.

(Even in Javascript new Array(10).map(() => 0) still results in an unusual array. The slots are allocated but they’re still empty.)

Keep filing bugs :slight_smile: you can also use a buffer if you know about the data layout.

Does https://makecode.com/_VszWzPe1LRP0 work as an implementation for you? I hadn’t used Array.from before so I just scanned through the mdn docs on it and wrote up a version that seems right; have to sanity check that it works on hardware / follows the browser js implementation, and then I can add it in (as Array.from of course) if it’s right :slight_smile:

@jwunderl! This is delightful.

Would a github repo be easier for me to comment on? At first glance, this looks really good, but I do have a few questions I’d like to raise. I think I’m answering my own questions as I look deeper; I’ll just edit this post.

Since this is an addition to the builtin type, it’d be added directly into pxtlib in pxt core / not as a separate repo - here’s probably easiest if anything looks wrong. I’ll be sure to scan over the standard to make sure things are equivalent where possible though

1 Like

[You replied already, so I’ll just reply here with my questions rather than editing.]

Here’s are some non-questions I think I figured out satisfactorily on my own:

  1. the signature of the map function didn’t look like it marked the parameters as optional, which, at first seemed wrong; but I was thinking of it backwards. The caller of Array.from can optionally receive the parameters, but the implementation doesn’t optionally provide them.

  2. the MDN page doesn’t go into great detail on the signature of the map function, other than saying:

[it] has the same result as Array.from(obj).map(mapFn, thisArg), except that it does not create an intermediate array.

  1. (continued…) Array's map's mapping function has an optional 3rd parameter which is the array that you’re mapping over (I guess to avoid having to reference it through closure :man_shrugging:). But sure enough, Array.from's map function is only called with two arguments—which makes sense, since you’re not starting from an existing array.

And here are some, actual remaining questions:

  1. Array.from does take a 3rd parameter, which is an object to use as a “this” when calling the mapper function (I guess in case you don’t want to bind the function)… I kind of assume you intentionally chose not to implement this—which seems totally fine. But I wanted to note it in case the omission was unintentional.

  2. Looking at the polyfill implementation at the bottom of the MDN reference, it doesn’t look like Array.from({ 0: 'test', 10: 'test 2' }) is actually supported. I tried running it in the console, and here’s the result:

Screen Shot 2020-05-01 at 7.01.16 PM

  1. (continued…) so, it seems like an object literal with number-like keys doesn’t count as Array-like enough. I think you can get rid of the Object.keys / forEach steps from your implementation altogether. Unless you intentionally want to support this non-standard use case.

Thanks for taking the time to implement this! The inclusion of the test/examples at the bottom of the implementation was helpful, too.

3: That was half intentional; noticed it after I wrote it up, and decided it probably wasn’t worth adding in as we don’t really support arbitrary this’s, but I’ll take a look at how it’s typically used / can add in if it seems like something that should be made to work nevermind, took a second and remembered this just isn’t supported at all at the moment. Easy enough to work around with arrow functions as needed :slight_smile:

4: Oh… that’s disappointing, I must have misread the spec. I really liked the way I thought it worked (just turning anything that looks like an array if you squint into an array), probably need to take that out then and just do up to length if it’s there – it would be disappointing if a user was used to using a builtin function and then realized it didn’t actually work in normal js. Thanks for pointing that out!

1 Like