Is your JavaScript function actually pure?
staltz.com
staltz.com
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."
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).
Under this definition, Haskell has no pure functions either.
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.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.
Only at definition site, not use site, for both better and worse.
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.
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.
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.
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 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.
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.
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`.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" :(
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.
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".
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.
((j) => () => j++)(0) 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.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.
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.
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.
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).
Which part of it isn't? The distinction between "potentially non-terminating" and its negation is exactly the distinction between partial and total functions.
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!
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.
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.
> This article needs additional citations for verification ... (July 2014)
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.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.
Hello Downvoter: I know it is silly but the article is talking about equally silly JS by overriding valueOf.
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.
Anyway pass in (repeat 0) and you get a side effect. :-)
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.
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. 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?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.
As for this:
var a = {}; a.valueOf = Math.random;
var fn = x => x * 10;
fn(a); // 5.107926817373938
fn(a); // 3.4100775757245416
fn(a); // 5.1903831613695095
// Same input, different outputs!
Calling that "same input" just because it has the same syntax "a" is just ... plain silly. It's obviously "a" that is impure not the "fn" multiplication function. "a" is behaving like a macro which denotes the retrieval of a pseudo-random number.It's simply not reasonable extend the definition of a functions' purity to include *the behavior of the expression(s) which are evaluated to provide its arguments.
(I would say that it's still not reasonable even under non-strict evaluation.)
> Well, in an actual functional programming language like PureScript or Haskell we can:
>
> sum :: [Int] -> Int
> sum = foldl (+) 0
>
> The list cannot be undefined, cannot be null.
Yes it can? main = putStr "The result is: " >> print (sum undefined)
The result is:
Program error: Prelude.undefined
So by the same logic Haskell doesn't have pure functions, either?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.
There's a point at which the pursuit of purity becomes the pursuit of sanctity, in the sense of Haidt's moral foundations (https://en.wikipedia.org/wiki/Moral_foundations_theory). It's not about a rational tradeoff at that point, it's about easing an anxiety and sense of uncleanliness. I say this because I'm prone to doing it too, and I've noticed that I can spend a lot more time and energy than is worthwhile "cleaning" code.
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.
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!").
var x = 0;
function sum(arr) {
x += 1;
return arr.reduce(function(a, b) { return a + b; }, 0);
} 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;
} function make_pair(x, y) {
return { former: x, latter: y };
}You can use `undefined` safely with an IIFE wrapper:
(function(undefined) {
// you're safe here
}()); Array.isArray = () => true;
Array.isArray.toString = () => 'function isArray() { [native code] }';
:/"Is Your JavaScript Function Actually Pure? An impractical guide to Pedantic JavaScript."
(map println [1 2 3])
Map is RT even when println is not