92 comments

[ 3.0 ms ] story [ 167 ms ] thread
If we want to be pedantic, there is no such thing as a pure JavaScript function. If the stack is full, the invocation of ANY function will overflow the stack. That's a side effect to the outside world. Pure functions exist under Plato's Theory of the Form conception of the world, but not on a fixed and finite physical computer. http://www.johndcook.com/blog/2010/05/18/pure-functions-have...

If we don't want to pedantic, then a fraction of the 74% of who took the function as 'pure' probably took this to mean being equivalent to referential transparency. That's good enough, and I believe the author would agree.

I hope we can start shifting our discussions from 'is this pure?' to 'Does this function behave like a Mathematical Function in all the situations I will encounter in my code?'

It's a good exercise to think about assumptions, but we shouldn't be so paranoid as to check if native JS methods, namely Array.isArray, have been overridden (unless security is a concern). When we speak about the Fibonacci sequence, it's nonsensical to talk about f("pie"). The domain being integers is implied in the semantics of the function. In the veins of "keep an open mind but not so open that your brain falls out", I propose "keep writing code with pure functions, but not so pure that your code becomes cluttered."

I ran into this article a little while ago and I had a similar sentiment. I get the point staltz is making but it felt pedantic and heavy handed, hell you can ignore JavaScript entirely and the same is true for just about every programming language. Seems that there is a trend towards ideology in the JavaScript community lately that bothers me. People obsess over pure functions and FRP and talk so highly about these things like mutation and non-FRP are the worst things on the planet when it comes to code.
The trouble with trying to apply words like "community" to millions upon millions of people with basically nothing but a surface connection (like they all have used javascript, for instance) is explained nicely in Vonnegut's "Cat's Cradle"
"The JavaScript community" need not include everyone who has used javascript. Why do you assume GP was intending it to do so?
I made only one assumption - that "the JavaScript community" is a granfalloon. Any further assumptions you assume I made are not valid.
(comment deleted)
(comment deleted)
yes we run on imperative hardware, a pure functional language can run out of memory too (but I agree that Staltz's stance is unhelpful)
>Pure functions exist under Plato's Theory of the Form conception of the world, but not on a fixed and finite physical computer

In the same vein you can say that C is a Turing complete language (i.e. you can use C to simulate a Turing machine), so there can be no complete implementation of C on a computer because no computer can simulate a Turing machine (since real computers are only state machines, which are inferior to Turing machines).

When talking about computer-science terms like "pure function" or "turing complete" things tend to stay a lot more sane if you just assume that even in the real world you will not run out of memory (just as a lot of programming stays more sane under that assumption).

> If we want to be pedantic, there is no such thing as a pure JavaScript function. If the stack is full, ...

Under this definition, Haskell has no pure functions either.

I think that's a fine assertion to make, because Haskell functions can die at runtime because of pattern match failure or an explicit evaluation of "bottom" (undefined).

    Prelude> undefined
    *** Exception: Prelude.undefined

    Prelude> let x 2 = 4 in x 1
    *** Exception: <interactive>:7:5-11: Non-exhaustive patterns in function x
Neither of these behaviors is indicated in the type definition:

    Prelude> :t undefined
    undefined :: t

    Prelude> :t let x 2 = 4 in x
    let x 2 = 4 in x :: (Eq a, Num a, Num a1) => a -> a1
I was promised a -> a but got a runtime exception instead.
isn't bottom part of any data type, including a?
It is, yes, but people often proclaim "haskell has solved the 'null' problem", and it hasn't because bottom is the exact same thing as null.
> bottom is the exact same thing as null

No it's not because you can't check for bottom (purely) and thus you can't use it as a flag or sentinel value.

You can get full pattern match checking via -fwarn-incomplete-patterns. Crashing on bottom is a concession to the fact that crashing is more useful than infinite loops. The semantics of the program exiting are the same as the semantics of the program infinite looping. So I'd say it's as pure as you can get on a finite state computer.
> You can get full pattern match checking via -fwarn-incomplete-patterns.

Only at definition site, not use site, for both better and worse.

As a matter of fact, if your JS code is running on a pages where other pieces of code could be loaded, then it will regularly encounter native JS methods being overridden (Prototype.js is the worst offender so far).
>When we speak about the Fibonacci sequence, it's nonsensical to talk about f("pie").

Indeed it is, but I think you are taking his example literally. That problem can appear in more subtle ways.

Consider some other examples:

f(string)

Does f works for every string? What if it's unicode. What if it's an empty string? What if it's a sequence type(like an iterator over a stream) that returns the letters of a string?

mean(numbers)

What is the biggest number I can pass? If I pass a list of integers does it return a float or an integer? If I pass an empty list, does it return 0, -1, null, undefined?

There are several situations on which you can encounter similar situations. I do not disagree that all that type information has a cost, your type signature can become more complicated, your cognitive load can be increased, your code can be less flexible, it can be harder to integrate with existing technologies, but since the problem still exists I think it's very reasonable that the author is trying to keep that discussion alive.

The conventional definition of "pure" is that the result of the function is the same for the same input. You might include "throws an exception" as a possible "result" for a JavaScript function, or you might not (generally not).

So the fact that your implementation of mean() returns a float when you expected an int (honestly, there are no ints in JavaScript, just floats which happen to have integer values, why would you expect that?) or returns undefined doesn't mean that the function is impure, it just means that the function doesn't do what you thought that it should do.

For example, I can write the function:

    function addOne(x) { return x + 1; }
I think there is no conceivable generally useful definition of "pure" under which this function is not pure, and yet... it could overflow the stack, it could run out of heap memory if you pass a string depending on how much heap memory is available...

The generally useful definition of "pure" is just that if f is pure, I can save y = f(x) and use y instead of f(x), or vice versa, or I can eliminate f(x) completely if I don't need the result. The fact that extreme corner cases, like out-of-memory situations, can result in slightly different behavior is something that we like to gloss over.

If you compare Haskell with JavaScript, all JavaScript functions would run in the `IO` monad because you can always (even implicitly as in the article) run an impure action even if everything else seems pure. In Haskell, if your function is not monadic, you can't "run" the monadic action. On the other hand, you don't see exceptions in Haskell's type signatures and you can throw them in pure code. Hm! ;-)
Sorry... in Haskell there is performUnsafeIO. Nothing in Haskell is therefore guaranteed to be pure!

Hello Downvoter: I know it is silly but the article is talking about equally silly JS by overriding valueOf.

The question is asked providing the entire implementation of the respective sum functions in terms of primitives. There's no room for unsafePerformIO in the Haskell code (assuming prelude's `(+)` and `foldl`).

There could be if the Haskell version was typed on `Num a`, but it's typed on `Int`, which is a standard type with a known pure Num instance.

Ok sure but I was meaning in general.

Anyway pass in (repeat 0) and you get a side effect. :-)

> Ok sure but I was meaning in general.

So was I, the article denotes that JS functions can be impure because values carry a "purity stigma" (the value itself can embed impure operations in its manipulation or evaluation).

> Anyway pass in (repeat 0) and you get a side effect. :-)

That's a good point, laziness also carries a "purity stigma". You could actually sneak in unsafePerformIO with an unfold or a lazy list construction.

Haskell is well-known not to be pure in supporting non-terminating computation.
Sure, that's an issue with Haskell - much better to use Idris.
The assumption in this article is that as long as a line of code as the same characters, its output will be the same. But this makes no sense. Consider:

    var arr = [0,1,2]
    sum(arr);
    arr = [1,2,3];
    sum(arr);
The two sums will have different results. What the article does is in essence calling

    sum([Math.Random(), Math.Random(), Math.Random()]);
    sum([Math.Random(), Math.Random(), Math.Random()]);
I don't see why one would expect the output of such a call to be always the same, if you are in all effect changing the input. Even if the characters you typed are the same, if the values that are passed around change the result of a function call will change. "Pure" is a property with regards to the values passed to a function, not to the characters typed to call it.
I believe as well that the article is imprecise, since it elaborates on a trick question and tries to just inform about the term "pure" being hard to define, especially in a dynamic language.

To be referencing an exact quote of the article to make it very clear:

  var arr = [{}, {}, {}];
  arr[0].valueOf = arr[1].valueOf = arr[2].valueOf = Math.random;
Obviously this relies on multiple function calls, so the output of the original function `sum` is still predictable. I think predictability and the absence of side effects is still a much better definition for pure functions.

It's also a necessary definition in JS. Saying that 'the same call of the function with similar enough arguments will always yield the same output' is not a sufficient definition and way too simplified.

The author uses the definition that a pure function must return identical outputs on identical inputs. I actually think a better definition is that the output of a function cannot depend on anything but it's inputs. If we use that definition then the first example he gives where the `sum` function violates pureness is actually not a violation. This is also justified by interpreting a `.valueOf = Math.random` field as a random variable, making the procedure `sum` a map from a random variable to a random variable.
I think it's commonly agreed that pure functions depend only on their inputs AND cannot have side effects. So when introducing 'Math.random' the function would be impure as it has side effects.
Is it then the variable (or the variable accessor) that is impure, or the function that is impure?
The variable accessor is a function of type Variable -> Field, which the function sum is calling. Because of that, the function sum is impure, as it's calling an impure function. I would argue that the object is just a wrapper around a function, and in essence making the sum function implicitly higher order, as you're passing it a function to retrieve a value, rather than an actual value.
Math.random doesn't necessarily have side affects. If it's storing a seed value, and incrementing that each time; then yes it stores mutable state which is a side affect because future invocations of that function will then depend upon that pre-existing state.

If however Math.random is passively listening for background radiation through some sensor perhaps then it doesn't really have side affects does it? Nor does it have mutable state, for that matter.

But it would still be impure according the true definition of what is a pure function, I believe.

I thought it was a bit pedantic as well, at first. But looking at Haskell, I can see why the author made this point. Haskell functions won't accept random values as input, since they're monadic values.
Replace Math.random with this, then:

    ((j) => () => j++)(0)
Are there two identical inputs to Math.random that yield different results? If the answer to that question is yes, the function isn't pure.
That's what I thought. It boils down to the question of whether:

    f => f()
is a pure function? If so, then so is the author's example. If not, then I don't see how any JS function that takes an argument (and evaluates it) can be pure. Under that definition, I'm not sure it makes sense to talk about.
> If not, then I don't see how any JS function that takes an argument (and evaluates it) can be pure.

A JS function could take an argument, use it but not evaluate it (or not evaluate it in all cases). `typeof` doesn't evaluate its argument for instance.

Certainly it could. I don't know what that has to do with my comment though.
Is Haskell's `(++) :: [a] -> [a] -> [a]` pure? What if I pass `unsafePerformIO` to it?

For theoretical purposes it might make sense to say pure functions cannot call impure functions, but I think a more practical definition includes functions that are provided as arguments.

Haven't the faintest. I don't speak Haskell, 's why I asked.

I'd thought pureness was a property of function definitions, irrespective of what they get passed, but living in Javascript land I've never had occasion to be concerned with the distinction. I kind of suspect it's not really something that's useful to talk about.

Not asking you directly, I agree with you and was trying to rephrase the notion.
If you introduce unsafe into a program, it is not pure. You can run a linter or something to check if your program uses unsafe anywhere.

Purity is a spectrum, not a binary, The more impurities you add, the more likely your program will not behave reasonably (as in, not in accordance with the simple logical rules of referential transparency).

The definition of a pure function is pretty well defined https://en.wikipedia.org/wiki/Pure_function. Math.random depends on hidden state, so (generally) any function using it isn't pure. This doesn't matter much in a language like Javascript, but other languages depend upon this definition (like the lazy function evaluation in Haskell).
It's not so well-defined. In particular, "side effect" is hard to pin down. For instance, a good definition ought to consider potential non-termination a side effect. In that way, many functions are impure.
It's a mathematical definition, of course it will break down a bit when you introduce it into the real world. I guess a more pragmatic definition would be "The ability for a runtime to delay, or repeat the execution of a function (with its arguments), and always get the same result".
I mean that it's not even so well defined mathematically. Starting from a weak place and then moving it to the "real world" makes things quite tricky.
> I mean that it's not even so well defined mathematically.

Which part of it isn't? The distinction between "potentially non-terminating" and its negation is exactly the distinction between partial and total functions.

The definition of pure function (or, dually, side effect) is difficult to pin down. I've heard a number of definitions, but the best ones are far more technical than you'd see in casual conversation and the casual ones often admit holes.
> I've heard a number of definitions, but the best ones are far more technical than you'd see in casual conversation

To be sure, but there's a big difference between casual definitions being subtly wrong and:

> I mean that it's not even so well defined mathematically.

Most mathematical definitions, at least the interesting ones, aren't really suitable for casual conversation!

Right, so I think where we're disagreeing is that the casual ones are subtly wrong. I think they're often pretty significantly wrong and that this post was essentially poking into that notion. When purity gets on the table lots of divergent ideas tend to be tossed out to nail it down and a lot of confusion results.

The best casual definition I know of is "a pure function, f, is one such that all variation in the function's result arises from variation in its input and that all information from the function's call is contained in its result: throwing the result away is equivalent to having never called the function"

A bit of a mouthful, but it's intuitive and arises from a formal definition that works out pretty nicely. Otoh, it's complex enough that it's rarely used in its complete form. Finally, it's still subtle enough that it can lead to questions about what side effects are exactly (non-termination is an immediate example).

Anyway, I just want to continue to suggest that understanding pure functions, really, is a more difficult task than people make it out to be.

Is that definition of pure closer to mthq's definition or the one in the article? I'm not sure.

It says "same argument value(s)", but how does that apply when the argument is a function? The article seems to be saying that if the same argument is passed in every time, then the function is only pure if it returns the same value every time, regardless of whether the argument is a pure function or not.

A pure function can't call a non pure function. So you can pass in a non pure function so long as you don't call it.
Pretty well defined?

> This article needs additional citations for verification ... (July 2014)

Reposted from https://news.ycombinator.com/item?id=12530652

I disagree with some of the claims, as I think these sorts of edge-cases mostly serve to highlight how imprecise the given pure/impure distinction is, and how we can't always take a semantic concept from one language (e.g. Haskell) and apply it in another (JS).

For example, if you always get "TypeError" when calling "sum()", you can replace "sum()" with its result ("throw TypeError") and get the same behaviour; it doesn't break referential transparency, so is it "pure"? What do we even mean by "result" in a language with exception handling? Do thrown exceptions count, or only return values?

Similarly, using "valueOf" doesn't show that the sum function is impure, it shows that the sum function is higher-order. If we mapped 'Math.random' over an array, does that make 'map' impure? What does it mean to be pure/impure when any variable can be replaced by a 'valueOf' function/procedure?

If two expressions call the same procedures, the same number of times, in the same order, and return the same pure function of their outputs, are those two expressions the same? Would replacing one with the other maintain referential transparency, and does that imply "purity"?

I think the answer is that such examples need more fine-grained notions than just "pure"/"impure". Otherwise it's just arguing over the semantics of English, rather than semantics of programs.

Defining a pure function as "a function that is pure as long as you use it purely" isn't a very useful definition. (Note distinction between "not very useful" and "useless".) It isn't very useful to humans, who do not have the cognitive bandwidth to process the resulting graph of purity and impurity through any non-trivial code base, especially in dynamically-typed language like Javascript where the purity of a function's invocation can be changed at runtime with the full arbitrariness of a Turing-complete language. It isn't useful at all to compilers or JITs, who if they can not be assured a function is definitely, 100% always pure must always check in one way or another and thus lose optimization opportunities.

It's sort of useful in terms of looking at leaf nodes of the structure of the program and determining that this call is pure (today, at least) and thus reasoning with it hyperlocally, but that's pretty much it, and that's not that great of an advantage over what programmers already do. And such reasoning is still quite weak given that it can be invalidated by changes to the local environment, like complicated objects getting passed in.

I think purity isn't too confusing here, and it's not really a Haskell concept being used, it's general purpose. It's just that the answer given by the post is correct; it's essentially impossible to write pure Javascript code. It's also, for much the same reasons, impossible to write pure Python, Ruby, or Perl; there's so many ways to invoke something that runs arbitrary unconstrained code (properties, method overrides, class-level hacks, metaprogramming, redefinitions of symbols used by the function) that it's very difficult or impossible to write pure code. (I think you could write a pure id function in JS. But in light of the fact that x + 10 can be impure, that would seem to be about the limit.)

And this fact is not just academic; it's part of the reason why it seems like JS engines have plateaued at around 8-10x slower than C. These issues may be academic to most JS programmers, but they are brutally, unavoidably practical to someone writing a JIT.

> I think purity isn't too confusing here, and it's not really a Haskell concept being used, it's general purpose. It's just that the answer given by the post is correct; it's essentially impossible to write pure Javascript code.

I completely agree, especially when comparing languages. But if we're focusing on a particular language, like JS, then it's not particularly useful to make distinctions like "pure"/"impure" which essentially lump everything into one category. Once we've said "it's all impure", there's not really anything else to say, unless we make new distinctions like the ones I made above.

Ah, yes, I agree completely. Definitions that bin everything into one bin are useless. I'm not sure there's a useful definition of pure for JS, though. (Or Python, Ruby, or Perl, or anything else that dynamic.)

The one where you just sort of wing it and say "Yeah, function (x) { return x + 10 } is pureish enough" is really tempting, but it will stab you in the back sooner or later, guaranteed.

Rather than talking in terms of “purity”, it's better to talk in terms of “effects”. Raising an exception is an effect. Returning different results when passed the same arguments twice is an effect. I/O is an effect. Looping forever (also known as “divergence” in some circles) is an effect. What you call “purity” is just the absence of effects.

From the point of view of programming language semantics, what all effects have in common is that they reduce the extent to which equational reasoning is applicable to our programs. For example,

    f(x) == f(x)
doesn't always hold if `f` is a non-deterministic procedure, and

    function foo_bar() {
        var x = foo();
        var y = bar();
        return (x, y);
    }
isn't the same as

    function bar_foo() {
        var y = bar();
        var x = foo();
        return (x, y);
    }
if `foo` and `bar` have effects that don't commute.

---

Higher-order functions also give us an interesting possibility: effect polymorphism. Let's consider the humble `map` function, operating on lists:

    map _ [] = []
    map f (x :: xs) = f x :: map f xs
Intuitively, it's clear that `map f` is an effectful procedure if and only if `f` is an effectful procedure. This notion can be formalized by giving `map` the following type signature:

    forall (a b : Type) (f : Effect). (a -f-> b) -> (List a -f-> List b)
Where `Foo -Bar-> Qux` means “procedure with argument type Foo, effect Bar, and return type Qux”, and `Foo -> Bar` is sugar for `Foo -Pure-> Bar`.
Algebraic effects are a way to make well-behaved languages (Haskell, Idris, etc.) more intuitive/generic/terse/etc. to write; I'm not sure how useful they are for reasoning about an arbitrary snippet of JS taken from the wild.

One problem stated in the article is we can't even rely on things like variable substitution to be effect-free, due to mechanisms like "valueOf". For example, in your "map" definition, the value of "f" called at the head of the list may differ from the value of "f" passed to the recursive call.

With this level of monkey-patching possible, it becomes quite hard to say anything about a piece of code other than "it's return type is Any, it might throw an exception, and it could perform arbitrary effects" :(

> I'm not sure how useful they are for reasoning about an arbitrary snippet of JS taken from the wild.

Probably not much, indeed.

> One problem stated in the article is we can't even rely on things like variable substitution to be effect-free

I have no idea how to make sense of this. The presence or absence of effects is an object language notion. Variable substitution is an operation on the syntax of this object language, and syntax necessarily lives in a metalanguage (e.g., the language in which a compiler or interpreter is written).

EDIT: Forgot about explicit substitutions, which make substitution an explicit reduction step in the object language. Even then, I don't see how substitution can be effectful by itself.

> For example, in your "map" definition, the value of "f" called at the head of the list may differ from the value of "f" passed to the recursive call.

At least in a call-by-value language, this is should never the case. Funny things can happen if lexical environments are first-class objects, of course.

> I have no idea how to make sense of this.

Maybe I should have used a different phrase. I meant that an expression consisting of a variable (e.g. "x") may be evaluated as if it were a dynamically-dispatched function call (e.g. "x.valueOf()"), so even the most innocent-looking expression may contain hooks to which someone may have attached an effect.

Even though JS is call-by-value, implicitly-called methods like "valueOf" allow values in normal form to act like thunks with arbitrary side-effects, which causes a dependence on evaluation order even for such innocent-looking expressions as "x".

Wow, this is terrible. I already knew I didn't know JavaScript. But I didn't know the extent to which I don't know JavaScript.
Obviously due to JavaScript being a scripting language and dynamically typed, we have to make certain assumptions so that we can even consider calling any function "pure".

It's nice that André wants to warn us here about the fact that the term "pure" is more or less accurate depending on the language.

In JS it can depend heavily on previous assumptions and the context, but I'd argue that this is often only due to the arguments that are being passed to a function. A non-safe function that isn't type will always be impure in some contexts. But for sanity reasons we must be able to make assumptions about the arguments.

Defining what a pure function in JavaScript is seems to be the difficult problem here.

Maybe we should call a function pure if "given identical, pure arguments, it returns identical, pure results". You would also need to define when an object is pure. That's already difficult, as the semantics of JavaScript objects are so weird. It's quite likely that we would have to replace "identical" by some weaker notion of relatedness which is merely preserved by "pure" functions...

Anyway, this might be useful as a static analysis for a JavaScript vm. It depends on how much wild JavaScript code is actually "pure" in any reasonable sense of the word. Let's just sum this up and say that JavaScript is really not a nice language to analyze.

Good article. Yes, the idea of purity becomes less useful in OO languages. That's not specific to JavaScript or even to dynamic typing. Try summing the elements of a Collection<Integer> in Java and your code will also be impure, because Collection is an interface whose implementation can do anything. I agree that JS goes too far in allowing you to impersonate numbers, but most real-world code uses interfaces in some way, so you can't make it pure without changing your language into Haskell.
Is this function pure?

    function make_pair(x, y) {
        return { former: x, latter: y };
    }
I would use my dynamic type checker/enforcer to make it pure:

  var types= require( 'types.js' );

  function sum(arr) {

    arr = types.forceArray( arr );
    var z = 0;
    for (var i = 0; i < arr.length; i++) {
      z += types.forceNumber( arr[i], 0 );
    }
    return z;
  }
I would also use some `demand` function or argument-validator in JavaScript to validate that, thats a good practice in anyway, like: `function sum(arr) { goodArrayOfNumbers(arr); ... }`.

I think that; in JavaScript you NEED to validate your arguments with something like that, you really cant assume nothing. ("Dont assume it, prove it!").

Trivia: assuming the input to `sum` below is guaranteed to always be a non-trickery array of numbers, so it always returns the same result for the same input, is the function 'pure'?

    var x = 0;

    function sum(arr) {
        x += 1;

        return arr.reduce(function(a, b) { return a + b; }, 0);
    }
I don't understand how this is ambiguous. `sum` is changing state outside its scope (a side-effect). There is no definition of "pure" I'm aware of that would include this implementation.
For those of you that write in JS (and not TypeScript), do you use "void 0" instead of "undefined"? I use undefined and would hate to switch to void 0, since it's less clear and unnecessary thanks to tools like JSHint.
`undefined` is a variable that can (could? It may have changed with strict mode or ES6) be set globally to arbitrary values.

You can use `undefined` safely with an IIFE wrapper:

    (function(undefined) {
        // you're safe here
    }());
Prior to ES5 undefined was overwritable that is why void 0 guaranteed to produce undefined. With ES5 and later using undefined directly is safe.
If you're using valueOf like that then you already did something wrong. Reevaluate your life choices.
I bet you can still break that last code block by having an isArray accessor that returns the "proper" function the first time and a broken one the second time. :-)
Given the emerging importance of separating "pure" code from "side-effecting" code, what frontend JS techs actually enforce this division, as opposed to just leaving it up to the fastidious developer? Elm? Haskell/Haste?

    sum(); // TypeError: Cannot read property 'length' of undefined

That doesn't mean it's not pure.

    var arr = [{}, {}, {}];
    arr[0].valueOf = arr[1].valueOf = arr[2].valueOf = Math.random;
    sum(arr); // 2.393660612848899
    sum(arr); // 2.3418339292845998
    sum(arr); // 2.15048094452324
Oh come on. By that logic there are no pure functions in C, because you can play with #define. Definitions are meant to be useful.
No

    (map println [1 2 3])
Map is RT even when println is not
Don't pure functions come with the disclaimer that no one does anything stupid like overriding the Date constructor or valueOf?

Otherwise, we have to either come up with a different name for pure functions with a modified meaning, or write a bunch of absolutely ridiculous defensive code in every single function we want to truly be pure.

so var sum=(...a) => a.reduce((x,y)=>x+y) is pure?
If we're being nitpicky about type checking...

    function f() { return Math.random() < 0.5; }
    f.toString = function () { return 'function toArray() { [native code] }' }
    Array.isArray = f;
What, use Function.prototype.toString instead? What if someone tampered with that too?
I mean what happens when someone does

    Array.isArray = () => true;
    Array.isArray.toString  = () => 'function isArray() { [native code] }';
:/
Premise is interesting, but by the end, JS fails the author's standard of purity not really because of its function implementation (or any variant thereof) but because of its dynamic-weak type system.
Dear god this article is annoying. I could spend an hour nitpicking the bs-ey wonk that André is spewing here, but that would be pointless, so I'd like to make a more macro point: Can we please get away from pedantism and trivial analysis as a display of intelligence?