top | item 17380223

JavaScript: Does It Mutate?

130 points| plurby | 7 years ago |doesitmutate.xyz | reply

77 comments

order
[+] ris|7 years ago|reply
The greatest sinners are methods that both mutate and return their result. It's possible for people to use some of these for years and not realize they're mutating. This is something python has largely got right.
[+] masklinn|7 years ago|reply
> The greatest sinners are methods that both mutate and return their result.

Go's `append` builtin is guilty of exactly that sin. And worse, it does not always mutate its parameter in-place, so things get hellish if you start aliasing/shallow-copying slices because depending on the size of the underlying array, appends could overwrite the other slice's data or make the two slices diverge entirely.

[+] drinchev|7 years ago|reply
I did that mistake with `sort`. There was a nasty bug that I tried to fix, when I realised that `sort` actually mutates the array and returns it as well. That was pretty good lesson to learn.
[+] bastawhiz|7 years ago|reply
As someone who writes JavaScript full time, this is my biggest gripe with the language. Folks sorting arrays inline without first copying them is one of the things that I pretty consistently call out in code reviews.
[+] coldtea|7 years ago|reply
It would be even better if it was a linting/compile or at least a runtime error to assign a variable to the result of one of those functions.

Python let's you do it, and you get "None" as the result, which makes such mistakes slower to discover.

This would require some way to mark a function as returning or void.

[+] goldenkey|7 years ago|reply
Realize in C and JS and most languages, the standard equality '=' operator, does exactly this.

For example, console.log(2*(a = 10)) would print 20 while setting a to 10

[+] quotemstr|7 years ago|reply
So you hate all builder-object methods that return the builder for further building? Ok.
[+] jacobush|7 years ago|reply
Which is interesting given how easy it is to mutate in Python.
[+] baby|7 years ago|reply
That's OpenSSL for you.
[+] baby|7 years ago|reply
That's why I really like the ! In Ruby. If used in a function name it indicates that it will mutate its arguments. It's so straightforward instead of just not knowing or having to use references or pointers.
[+] michaelmior|7 years ago|reply
That's not really true. First, it's entirely a convention and isn't always followed. Second, there are many many methods which mutate objects and don't have a bang. For example, most of the methods which mutate arrays.
[+] stilley2|7 years ago|reply
Julia does this too. It's brilliant.

Side note: I just tried solving an actual problem in julia for the first time. That language does a lot of stuff right, at least for my definition of right.

[+] bglusman|7 years ago|reply
Also commented below but the idea here was not actually mutation but safe and dangerous versions of methods... E.g, below insert is mentioned and it has no safer version so it has no ! version... Elixir adopted this convention from Ruby also, but there is never any mutation in Elixir of course as BEAM doesn't allow it.
[+] bjoli|7 years ago|reply
This is something ruby inherited from scheme. Scheme library devs are however a lot more militant about it than ruby devs, so everything that mutates has a ! after it.
[+] dpwm|7 years ago|reply
Pytorch does something similar, except with an underscore at the end of the name. As the arguments are tensors, it's a really handy convention and the API communicates whether a method or function modifies an argument.

I don't believe this was always the case, so it's probably caused a lot of deprecation warnings for some people.

[+] phyzome|7 years ago|reply
This is why I really like Rust's "mut" keyword that enforces whether a function argument or method target is mutable. :-)
[+] joshribakoff|7 years ago|reply
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...

Under "instances" there is a list of mutators, accessors, and iterators that I find more useful since it is grouped and alphabetized.

Another pitfall I have seen Dev's make is to assume .filter() will return the original array even if the callback returns true for every element. In fact it always returns a copy, even if all elements pass the filter

I recommend the npm package deep-freeze in conjunction with unit testing, makes this way easier to debug.

[+] sharpercoder|7 years ago|reply
I have done quite some implementations now of things in an immutable way. Easy to test, easy to spot bugs, easy to work with. Come back a few months later, and purity is removed on many things despite clear comments on the why it is immutable.

I welcome any effort to make developers aware of immutability.

One simple thing to help here is to have the "mutability: flag marked on every method in MDN.

[+] incadenza|7 years ago|reply
Yeah, similar to how lodash makes it explicit in the docs. That would be great.
[+] k__|7 years ago|reply
Nice idea, but for a website that tells you about what functions mutate, its indicator for this property is rather small.

I'd maybe even throw out all non-mutating functions in the first place.

If it's on that page it mutates, the end.

[+] konsumer|7 years ago|reply
It should be noted that you can still mutate in these. Like it's not at all recommended, but you have access to the original array in many of the functional "non-mutating" Array methods, for example: https://codepen.io/konsumer/pen/bKKYJO?editors=0010

This can make things very hard to troubleshoot.

[+] konsumer|7 years ago|reply
And whether it mutates is kind of a surprise. Like reduce doesn't, even though it works similar to forEach, map, and every.
[+] mabynogy|7 years ago|reply
I use those functions a lot and I'm still impressed by how fast they are ran by current JS interpreters (especially V8).

I did a layer over strings/arrays/objects to get nearly the same behavior everywhere. An example of those functions (in my custom programming language) https://p.sicp.me/h4MJE.js and the compiled code https://p.sicp.me/ctr6M.js.

[+] beaconstudios|7 years ago|reply
just use ramda ¯\_(ツ)_/¯
[+] marcosdumay|7 years ago|reply
Or Haskell, or a lot of other languages.

It's so empowering to be able to answer this question with an instantaneous "no, of course not, it can't be". People that never experienced it have no idea.

[+] 3131s|7 years ago|reply
That's my feeling too, would rather not have to trust myself to remember any of this.

Or Lodash-FP...

[+] hjek|7 years ago|reply
Looking at a site like this makes me appreciate a language like Clojure more.

I was quite surprised a while ago when I learned that `sort` in Common Lisp can mutate somehow randomly.

> The sorting operation can be destructive in all cases.

http://clhs.lisp.se/Body/f_sort_.htm

[+] kbp|7 years ago|reply
Somewhat relatedly, a very common mistake in Lisp is modifying literals; a lot of people don't realise that there's a difference between writing '(a b c) and writing (list 'a 'b 'c), but QUOTE's description in the spec plainly says "The consequences are undefined if literal objects (including quoted objects) are destructively modified." To demonstrate the difference in practice (DELETE is the destructive version of REMOVE):

    CL-USER> (defun abc-without (letter) (delete letter '(a b c)))
    ABC-WITHOUT
    CL-USER> (abc-without 'a)
    (B C)
    CL-USER> (abc-without 'b)
    (A C)
    CL-USER> (abc-without 'c)
    (A)
This behaves strangely because it modifies the constant list (A B C) rather than consing up a new one on each invocation (like calling LIST or COPY-SEQ would), so by the end the list it passes to DELETE has been reduced to (A).
[+] klibertp|7 years ago|reply
You'll find people much better suited to explain this on HN, but I'd guess it's about efficiency. CL was defined long ago and one of the goals was for it to be fast. It really is, but there is a pervasive mutability in most of it because of this, with functions like nreverse, rplca, nconc and so on.
[+] lispm|7 years ago|reply
Add something like this to your library and you are fine:

  (defun isort (sequence predicate &key (key #'identity)) 
    (sort (copy-seq sequence) predicate :key key))
[+] mannykannot|7 years ago|reply
Three other questions of similar importance are "which exceptions might it throw?" "what are its exception-safety guarantees?" and "is it thread-safe?" (they do not all apply in all cases, of course.)
[+] jakelazaroff|7 years ago|reply
Re: "is it thread-safe?", the answer is yes. JavaScript is single-threaded and, since SharedArrayBuffer was disabled in all major browsers in response to Spectre, there's no concept of shared memory.
[+] erikpukinskis|7 years ago|reply
TL;DR: sort and reverse are the tricky mutators in my opinion. Everything else is more obvious.
[+] stockkid|7 years ago|reply
It'd be useful to filter by whether methods mutate.
[+] jawns|7 years ago|reply
It's clear at first glance to someone familiar with the language that the site is about Javascript methods, but for the benefit of those who are not very familiar, it would be nice if the site called that out in the title or a subhed.
[+] sctb|7 years ago|reply
Thanks, we've updated the title. We don't always need to call out such things, but when a headline has broader enticement than what it actually delivers then we're into misleading or clickbait territory.
[+] michaelhoffman|7 years ago|reply
I work in computational genomics so programming languages in general was not even the first thing I thought of.
[+] int0x80|7 years ago|reply
That was exactly my first thought. Do we just assume javascript this days? Not even a title or two liner of what is going on.
[+] Filligree|7 years ago|reply
Why should the site need to call that out? It's about Javascript.

A better approach would be to edit the title here, and any other place linking to it.

[+] draw_down|7 years ago|reply
Well, you'd hope the read-only functions like `every`, `map` etc don't mutate.

For the ones that do, it might be a good idea to provide examples of how to accomplish the same thing but without mutating. For example, show how to use `concat` where you'd use `push`.