Hacker News new | past | comments | ask | show | jobs | submit login
Recreating Python's Slice Syntax in JavaScript Using ES6 Proxies (intoli.com)
77 points by foob on June 28, 2018 | hide | past | favorite | 35 comments



I understand the idea behind the article - explain proxies and have a fun project to do so, so it doesn't get boring as hell - but, in general, can we please stop using runtime features of a language to implement syntactic sugar?

Actual syntactic suger can make a program more readable, easier to debug and even more efficient. Those kind of hacks are neither.

It's not more readable because your expressions are limited to the original grammar, so you have to resort to weird compromises. It's harder to debug because you add layers of abstraction (like the proxies here) that have no purpose at all except changing how your code looks. And it's less efficient because those layers of abstraction will still stay with the program when it's being executed even though you only needed them while you were writing the code.

So, please, syntactic sugar is awesome and more thought should be given to letting users add their own sugars - but can we do it via metaprogramming and preprocessors and not by hacks like this?

(Disclaimer: This pattern annoys me to no end in Java, so it's gotten a bit of a pet peeve. No fault to the author of this post who has written an excellent article)


Sometime it's nice. It's what makes numpy, pathlib and sqlalchemy great.

Just throwing the baby with the water is never a solution. As usual there is a balance to be found.


Absolutely. I didn't want to demand that we all stop using sugared libraries in an instant.

I'm more hoping that user-defined syntactic sugars and DSLs could be a focus point in future language design - so you actually get compile time tools and debugging support for them and don't have to resort to runtime or modeling hacks to implement them.


I used ES6 proxies recently to write a program which monitors changes on javascript data structures, and generates operation objects reflecting the changes to send to my data structure visualizer project[0].

You can see the source for the javascript client that does the monitoring with proxies here: https://github.com/westoncb/JS-Watcher/blob/master/watcher.j... —it just works for arrays at the moment, but the basic idea is there. (I've written another much more complete client that does monitoring in Java; this JS one is more experimental.)

[0] https://www.youtube.com/watch?v=KwZmAgAuIkY


Just use mobx. It's a robust, proven, documented way to monitor changes on data sets.


Looks interesting but I don't think it will be sufficient (I hope I'm wrong about that though!). The operation data I need has to include 'addresses' where nodes in data structures were modified.


Store the address in each object on set.


Recently I implemented a wrapper function that given an object returns a Proxy which has a get method that recursively calls itself with the accessed object path and finally returns the object and logs the full path. (The code is not complicated and only about as long as this post, but it's hard to type code on a phone)

The reason for this is to give me logging for this part of the application to aid in extracting it out to a separate service. That is, to see what from this part of the application is actually being used/called.

Edit: I meant to say that I wanted to share this because previously I hadn't yet found an interesting use for a Proxy.

Also, I thought of another use case: in the get method you could return a percentage of the time a different object, i.e. an A/B test.


I wrote a package that creates a mock object that can be used for pretty much anything. It's a joy to use when testing.

It's called @adrianhelvik/mock

https://www.npmjs.com/package/@adrianhelvik/mock


> My proposed solution here is to try to capture the spirit of Python’s slice syntax rather than the exact syntax itself. We can accomplish this using an alternative syntax where we use double square brackets and commas in place of colons: array[::-1] => array[[,,-1]], array[:n] => array[[,n]], array[-5::2] => array[[-5,,2]], and so on. This might not be quite as concise as Python’s syntax, but it only requires two extra keystrokes per slice and I think it’s as close as we’re gonna get.

I wonder if it makes more sense to write a babel plugin to support the python syntax. Since everyone using es6 features are transpiling anyway.


A downside to introducing your own custom syntax with a Babel plugin is that it's likely to break most tools that run on your source code, like editors, ESLint, and Prettier. (And, of course, it might conflict with future ES features.)

Also note that Babel's parser isn't pluginizable (normal Babel plugins just run during the transform step), so the typical way to add syntax is to fork the parser and run everything against the forked parser:

https://babeljs.io/docs/en/next/babel-parser.html#will-the-b...


There's currently a TC39 stage 1 proposal to add something very like the Python syntax to JavaScript: https://github.com/tc39/proposal-slice-notation.

There's an active Babel feature request to implement exactly that proposal: https://github.com/babel/proposals/issues/41. As such, it'd definitely make a lot of sense to implement this!


Great PoC, but please, don't do this. At my first gig I had to work on a huge Lua code base, where the original author did tricks like this, effectively creating a new language. Learning by example how things should not be done is effective, but the suffering wasn't worth it.


Fun, I learned something today! I imagine the performance of proxies is pretty terrible, so this is just a toy - but it could come in handy for implementing equivalents of some of the nice syntax sugar that numpy and tensorflow have in JavaScript.


Proxies aren’t actually that slow, the problem is the committee refusal to acknowledge indexed properties as being something that actually exists.

The result is that all property names passed to the interceptor get converting to a string.

That means if for example you were intending to proxy an array, your performance is destroyed.

First a numeric index gets converted to a string, then when to pass that on to your target array it has to be parsed back into a number.

In a trivial case (literally just forwarding the access) it might be possible to fake the stringing of the property name, but if you actually end up doing real work it becomes increasingly hard to avoid the property name escaping and so having to be reified.


I wonder why not to use the call syntax for JS slices? I.e. use a(1, 3) to mean a[1:3]? The drawback is that 0 at the beginning of the slice cannot be skipped, but things like a() in place of a[:] or a(i, j, k) in place of a[i:j:k] will work.


That would be a neat interface too, but it would necessitate a somewhat different approach than the one taken in the article. You can only use a proxy to trap operations that are already supported by the object that's being wrapped. The SliceArray class inherits from the builtin Array, and it therefore doesn't support the call operation. You would need to initialize a second custom class that inherits from Function, wrap that with a proxy, and then use the SliceArray as the actual target inside of the traps. It's definitely doable, but it's more complicated than one might expect.

I mentioned in the article that Remote Browser is an interesting project to look at to see some more advanced proxy usage. One of the things that it does is to define a CallableProxy class [1] which facilitates attaching a call handler to objects that otherwise wouldn't support them. The code itself isn't complicated at all, but it takes a bit of thought to make the pieces fit together just right.

- [1] - https://github.com/intoli/remote-browser/blob/9ffdd1b5b0a9b4...


We have a legacy angular2+ project (I know angular2+ is not that old) where we use a global object to store state. We are using ngrx in new code , but still need to access that monstrous global object. As other part of code is still updating that.

I'm thinking to replace that global object with recursive proxies so that all updates can be forwarded to ngrx store

And slowly replace all reads with ngrx selectors


"array[`${start}:${stop}:${step}`]. That’s not syntactic sugar… it’s syntactic aspartame at best." I lol'd



Just by looking at the implementation I dare to bet that this is the most expensive slice ever invented in JS. And all that conversion and processing to only allow for arr[-1], oh my..


But why?


For articles like this I often wonder this too. I don't know if it is developer Stockholm syndrome, a clever joke, or self-flagellation. It seems most articles on JS are aptly summarized as "here I am overcoming yet another critical defect of this poorly designed mess", which I suppose is laudable if you are forced to use JS all day.


I always joke with my students that the most popular projects in js are designed to not write js.

Tongue in cheek but still, webpack, babel, react, jquery, coffeescript, lowdash and typescript are all attempts to make the pain of using the js ecosystem go away by patching in things that has existed for decades elsewhere at the price of huge infrastructure and organisational cost.

That such a terrible tech remains with a monopoly on the most popular and awesome platform in the world still astonishes me to this day. And humbles me in a way.

But what infuriates me is that a lot of js devs will actually claim that it's great. The stockholm syndrom is strong indeed.


The take-away from the article is that a real syntax extension such as `a[1:3]` is not possible in the confines of ES6 because the language designers don't know why it's good to have that capability. You have to resort to external work-arounds like babel, sweet and others but they do not work in all circumstances and are all incompatible with each other.


It probably started out and just wondering if he could


Can anyone summarize what the resulting syntax looks like?


There's a table in the GitHub repository that shows the inputs and outputs for various equivalent slices in JavaScript and Python [1]. If you're familiar with the Python slicing syntax, that should make the syntax mapping fairly clear. The way to translate a Python slice definition to JavaScript is to use double square brackets instead of single ones and to replace the colons with commas. So array[::-1] becomes array[[,,-1]], array[3:] becomes array[[3,]], etc.

[1] - https://github.com/intoli/slice#for-people-who-know-python-a...


Why do so many JavaScript devs use 2 spaces for tabs? Its terrible for quickly reading through code


My theory of tab width is that it should be somewhat proportional to the average height of indented sections of code in that language.

That is, if a given language is really verbose and requires a lot of code to do simple things, the functions, if-statements, loops, etc will probably be a lot longer (like in C or Go) and so you need wider tabs to help visually align things.

But if a language encourages a style where functions are just a couple of lines long, if statements and loops are rare (or at least comparatively short), having excessively wide indentation can hinder readability.


Lots of developers work at Google, which enforces the use of two space tabs internally.

Personally I’ve come to appreciate it (and even prefer it).


Someone (@feross) created something called StandardJS, and even bought the domain standardjs.com, and started advertising it as a great style guide. I initially thought it was a joke or parody, as my own style is pretty much opposite of the guide.

Anyway, it says to use two spaces. It got super popular and here we are now.


JavaScript has commonly used 2 spaces for years before "StandardJS" (which is now out-of-date because certain es7 features necessitate line-terminating semicolons, but I digress).

CoffeeScript was a big catalyst, and I think Rails was as well, but it probably would have happened anyway. Of the languages I use regularly, the only ones for which 2 spaces is not accepted as normal are Python (which uses 4, probably because it was set in stone decades ago, before people even knew how to write code) and Go (whose auto-format uses tab characters, probably because Rob Pike is a stubborn prick).

Really, 4 spaces is an awful lot, unless your eyesight is failing.



Ruby infected webdev with this awfulness.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: