Built-in-like Range in JavaScript
Francesco Di Donato

Francesco Di Donato @didof

About: Grant me the serenity to accept the scripts I cannot change, courage to change the scripts I can, and wisdom to know the best way to refactor them.

Location:
Bari
Joined:
Feb 12, 2020

Built-in-like Range in JavaScript

Publish Date: Oct 6 '21
68 17

Premise

Make it possible to generate any range of integers with built-in-like syntax.

Motivation?
Honestly, none. Zero. Except for fun & study.


Basic functionality

You start by overriding the prototype of Number with itself, but proxed.

Object.setPrototypeOf(
  Number.prototype,
  new Proxy(Number.prototype, {
    // ...
  })
)
Enter fullscreen mode Exit fullscreen mode

In this way, any normal operations related to the prototype are not lost.

In the proxy you listen for access to any property via a getter. The third argument (receiver) is the "object", in this case the number itself - you call it start. It's already the right type, number.

The second argument corresponds to the name of the property, its typeof is indeed string.

Object.setPrototypeOf(
  Number.prototype,
  new Proxy(Number.prototype, {
    get(_, _end, start) {
      // _end -> '182' (typeof string)
      // start -> 42 (typeof number)
    },
  })
)

(42)[182]
Enter fullscreen mode Exit fullscreen mode

It is sufficient to use parseInt and, if it still isNaN just throw an error/warning. Or just ignore it silently and fallback by returning start.

let end = parseInt(_end)
if (isNaN(end)) {
  // warning or error
  // eventually, fallback
  return start
}
Enter fullscreen mode Exit fullscreen mode

Assured that the typeof end is also number, you can proceed to generate the range.

return Array(end - start + 1)
  .fill()
  .map((_, i) => start + i)
Enter fullscreen mode Exit fullscreen mode

Basic functionality is complete. Now the following code is perfectly valid.

(0)[5] // [0, 1, 2, 3, 4, 5]
Enter fullscreen mode Exit fullscreen mode

To make it not-end-inclusive, use
Array(end - start) instead of Array(end - start + 1).


Reverse range

To be able to do something like the following...

[5](0) // [5, 4, 3, 2, 1, 0]
Enter fullscreen mode Exit fullscreen mode

Check if start > end and if so swap both. Don't forget to sort the result in descending order.

The code is self-explanatory.

Object.setPrototypeOf(
  Number.prototype,
  new Proxy(Number.prototype, {
    get(_, _end, start) {
      // where (start)[_end]

      let end = parseInt(_end)
      if (isNaN(end)) {
        // warning or error
        // eventually, fallback
        return start
      }

      // sort behaviour - default ASC
      let s = +1

      if (start > end) {
        // swap
        let tmp = start
        start = end
        end = tmp

        // sort behaviour - DESC
        s = -1
      }

      // generate range
      return Array(end - start + 1)
        .fill()
        .map((_, i) => start + i)
        .sort(() => s)
    },
  })
)
Enter fullscreen mode Exit fullscreen mode

Result

42             // 42
(0)[5]         // [0, 1, 2, 3, 4, 5]
(0)['foo']     // #fallback -> 0
(3)[7]         // [3, 4, 5, 6, 7]
(8)[3]         // [8, 7, 6, 5, 4, 3]
Enter fullscreen mode Exit fullscreen mode

Couldn't I have done the same thing with a range function?
Yes, probably you should do it with a function.

Let this be a mental exercise and a way of making friends with the concept of prototype and proxy.

If you want to chat about nerdy things or just say hi, you can find me here:

Comments 17 total

  • Jon Randy 🎖️
    Jon Randy 🎖️Oct 7, 2021

    I'm planning to build something similar, but with 'safe' monkey patching using Symbols. I think I'll end up with a syntax something like:

    4[to](7) // [4, 5, 6, 7]
    [ ...1[to](3), ...10[to](15) ] // [1, 2, 3, 10, 11, 12, 13, 14, 15]
    
    (4)[to](7) // alternative way to write it
    
    Enter fullscreen mode Exit fullscreen mode

    Or maybe (not sure this is possible safely):

    4[to(7)] // [4, 5, 6, 7]
    [ ...1[to(3)], ...10[to(15)] ] // [1, 2, 3, 10, 11, 12, 13, 14, 15]
    
    Enter fullscreen mode Exit fullscreen mode

    Which syntax do you prefer? (I'm leaning towards the former)

    • Jon Randy 🎖️
      Jon Randy 🎖️Oct 7, 2021

      Could even expand it to:

      1[to](19, {step: 2})) // [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
      
      Enter fullscreen mode Exit fullscreen mode

      etc.

      • Francesco Di Donato
        Francesco Di DonatoOct 7, 2021

        It's an amazing idea, Jon. I find the second option more readable. Or, at least, it feels "righter". But I'm not sure it could work since a function cannot be used as a key in a plain object.
        Anyway, I'd like to work on it, together. Would you like to?

        • Jon Randy 🎖️
          Jon Randy 🎖️Oct 7, 2021

          I already did a POC in about 5 lines of code - works perfectly

          • Jon Randy 🎖️
            Jon Randy 🎖️Oct 7, 2021

            Still trying to think of a way to make the second syntax work without unsafe monkey patching. I'm not sure it's possible. My POC uses the a[to](b) syntax. Basically, to is a method on the number prototype... nothing particularly fancy going on

  • Jon Randy 🎖️
    Jon Randy 🎖️Oct 7, 2021

    I noticed in your header image, you have (3)(7) - that's not gonna work

  • Francesco Di Donato
    Francesco Di DonatoOct 7, 2021

    Me too! Thank you!

  • Adam Crockett 🌀
    Adam Crockett 🌀Oct 9, 2021

    This is too much magic for me, don't comment to argue, I don't like it I won't change my mind but you can like it.

    • Francesco Di Donato
      Francesco Di DonatoOct 9, 2021

      Hi Adam, I understand you point of view. As I stated in the post, this is something superfluous, a dev can definitely live without it :)

      • Adam Crockett 🌀
        Adam Crockett 🌀Oct 9, 2021

        It's not that, I woke up on the wrong side of the bed and I saw prototype of native primitive type being modified and it's just not my bag. Sorry if this came across in any other way but grumpy 😜

Add comment