115

Is the following a pure function?

function test(min,max) {
   return  Math.random() * (max - min) + min;
}

My understanding is that a pure function follows these conditions:

  1. It returns a value computed from the parameters
  2. It doesn't do any work other than calculating the return value

If this definition is correct, is my function a pure function? Or is my understanding of what defines a pure function incorrect?

cs95
  • 379,657
  • 97
  • 704
  • 746
Kiwi Rupela
  • 2,238
  • 5
  • 24
  • 44
  • 68
    "It doesn't do any work other than calculating the return value" But it calls `Math.random()` which changes the state of the RNG. – Paul Draper Oct 31 '17 at 16:02
  • 1
    The second point is more like "it does not changes external (to the function) state"; and the first should be complemented somewhat like "it returns the SAME value computed from the SAME parameters", as people have writen below – MVCDS Oct 31 '17 at 20:54
  • Is there a notion of a semipure function, allowing for randomness? E.g. `test(a,b)` always returns the same object `Random(a,b)` (which can represent different concrete numbers)? If you keep `Random` symbolic it is pure in the classical sense, if you evaluate it early and put in numbers, maybe as a kind of optimization, the function still retains some "pureness". – jdm Nov 01 '17 at 08:29
  • 1
    "Anyone who considers arithmetical methods of producing random digits is, of course, in a state of sin." - John von Neumann – Steve Kuo Nov 02 '17 at 20:51
  • @jdm if it returns the same Random object, as opposed to a random number, then it is pure – user253751 Nov 02 '17 at 22:55
  • 1
    @jdm if you follow the thread of "semi-pure", where you consider functions pure modulo some *well-defined* side-effects, you might end up inventing monads. Welcome to the dark side. >:) – luqui Nov 03 '17 at 02:15
  • @immibis We would need an immutable Random object that would return a tuple (NewRandom, randomNumber), which we'd then have to return from this function to make it useful. Seems doable if rather weird. – Voo Nov 04 '17 at 12:24
  • @Voo In that case it's pure, but the purity is not very useful because you have to pass a different Random object every time. – user253751 Nov 05 '17 at 00:30
  • @immibis It's certainly awkward to use in Javascript, but you could use it just as the non-pure function just by passing state around (which is how functional programming works in general). – Voo Nov 05 '17 at 00:46
  • Related: https://stackoverflow.com/questions/37244023/is-date-now-referential-transparent . – atravers Nov 30 '21 at 01:01

9 Answers9

193

No, it's not. Given the same input, this function will return different values. And then you can't build a 'table' that maps the input and the outputs.

From the Wikipedia article for Pure function:

The function always evaluates the same result value given the same argument value(s). The function result value cannot depend on any hidden information or state that may change while program execution proceeds or between different executions of the program, nor can it depend on any external input from I/O devices

Also, another thing is that a pure function can be replaced with a table which represents the mapping from the input and output, as explained in this thread.

If you want to rewrite this function and change it to a pure function, you should pass the random value as an argument too

function test(random, min, max) {
   return random * (max - min) + min;
}

and then call it this way (example, with 2 and 5 as min and max):

test( Math.random(), 2, 5)
Christian Benseler
  • 7,907
  • 8
  • 40
  • 71
  • 2
    What if you were to re-seed the random generator each time inside the function before calling `Math.random`? – cs95 Oct 31 '17 at 20:05
  • 18
    @cᴏʟᴅsᴘᴇᴇᴅ Even then, it would still have side-effects (changing future `Math.random` output); for it to be pure, you'd have to somehow save the current RNG state, reseed it, call `Math.random`, and restore it to the previous state. – LegionMammal978 Oct 31 '17 at 20:09
  • 3
    @cᴏʟᴅsᴘᴇᴇᴅ All computed RNG is based around faking randomness. Something has to be running underneath that causes it to appear random and you can't account for that, making it impure. Also, and probably more important to your question, you can't seed Math.random – zfrisch Oct 31 '17 at 20:12
  • 15
    @LegionMammal978 …and do so atomically. – wchargin Oct 31 '17 at 20:25
  • @cᴏʟᴅsᴘᴇᴇᴅ: You might consider accepting an instance of `lang.util.Random`, though the state of that would of course get changed, making it impure, but allowing the caller to control what gets affected. Also as far as I can see `lang.util.Random` is Java but not JavaScript. – PJTraill Nov 01 '17 at 20:52
  • @cᴏʟᴅsᴘᴇᴇᴅ: ... or an instance of `util/concurrent/ThreadLocalRandom` or `util/security/SecureRandom` (which are actually subclasses). – PJTraill Nov 01 '17 at 21:01
  • Another way to make pure function from a function that uses an RNG is to pass in the RNG state into and out of the function: `function test(rng, min, max) { [rng, rand] = next(rng); return [rng, rand * (max - min) + min]; }` – Lie Ryan Nov 02 '17 at 03:13
  • @PJTraill there is no `lang.util.Random` in java either. Maybe you meant `java.util.Random`? Also, as this question is about javascript, not java, they are anyway unrelated here. – eis Nov 02 '17 at 06:38
  • @eis: Thanks, of course I meant `java.util.Random`. I thought it worth mentioning as you could presumably do or use something similar in JavaScript. – PJTraill Nov 02 '17 at 08:08
  • @LieRyan: As I read the Wikipedia definition of _pure_, changing the state of a passed argument is also not allowed. – PJTraill Nov 02 '17 at 08:09
  • @PJTrail: where did you see my version of the function change the state of passed argument? – Lie Ryan Nov 02 '17 at 09:12
  • 2
    @cᴏʟᴅsᴘᴇᴇᴅ There are ways to have RNGs that do operate with pure functions, but it involves passing the RNG state to the function and having the function return the replacement RNG state, which is how Haskell (a functional programming language that enforces functional purity) achieves it. – Pharap Nov 02 '17 at 11:39
  • @LieRyan: I was thinking that `next(rng)` would change the state of `rng`, but I confess I do not know what `next` does – is this a library function (which I could not find) or your own, and what is / where can I find its specification? – PJTraill Nov 02 '17 at 16:56
  • @PJTraill: `next()` is any function, that is part of the RNG, which, given the RNG state `rng`, returns a tuple containing the next RNG state `rng'` and the next random number in the RNG. In the code `[rng1, a] = next(rng); [rng2, b] = next(rng)`, it will always be the case that `rng1 == rng2` and `a == b`. To get the next number in the PRNG, you would be required to do: `[rng1, a] = next(rng); [rng2, b] = next(rng1)`. In functional languages like Haskell though, you'll likely be using monad here to avoid having to explicitly pass rng state around everywhere. – Lie Ryan Nov 03 '17 at 01:53
  • @PJTraill: note that RNG does not necessarily need to be a pseudo RNG for next() and test() to remain pure with above implementation, it could be a true (hardware) RNG, and you'd handle it the same way you do IO in pure languages. – Lie Ryan Nov 03 '17 at 01:58
53

The simple answer to your question is that Math.random() violates rule #2.

Many other answers here have pointed out that the presence of Math.random() means that this function is not pure. But I think it's worth saying why Math.random() taints functions that use it.

Like all pseudorandom number generators, Math.random() starts with a "seed" value. It then uses that value as the starting point for a chain of low-level bit manipulations or other operations that result in an unpredictable (but not really random) output.

In JavaScript, the process involved is implementation-dependent, and unlike many other languages, JavaScript provides no way to select the seed:

The implementation selects the initial seed to the random number generation algorithm; it cannot be chosen or reset by the user.

That's why this function isn't pure: JavaScript is essentially using an implicit function parameter that you have no control over. It's reading that parameter from data calculated and stored elsewhere, and therefore violates rule #2 in your definition.

If you wanted to make this a pure function, you could use one of the alternative random number generators described here. Call that generator seedable_random. It takes one parameter (the seed) and returns a "random" number. Of course, this number isn't really random at all; it is uniquely determined by the seed. That's why this is a pure function. The output of seedable_random is only "random" in the sense that predicting the output based on the input is difficult.

The pure version of this function would need to take three parameters:

function test(min, max, seed) {
   return  seedable_random(seed) * (max - min) + min;
}

For any given triple of (min, max, seed) parameters, this will always return the same result.

Note that if you wanted the output of seedable_random to be truly random, you'd need to find a way to randomize the seed! And whatever strategy you used would inevitably be non-pure, because it would require you to gather information from a source outside your function. As mtraceur and jpmc26 remind me, this includes all physical approaches: hardware random number generators, webcams with lens caps, atmospheric noise collectors -- even lava lamps. All of these involve using data calculated and stored outside the function.

senderle
  • 145,869
  • 36
  • 209
  • 233
  • 8
    Math.random() not only reads its "seed" but also modifies it, so that the next call will return something different. Depending on, and modifying, static state is definitely bad for a pure function. – Nate Eldredge Oct 31 '17 at 14:45
  • 2
    @NateEldredge, quite so! Though simply reading an implementation-dependent value is enough to break purity. For example, ever notice how Python 3 hashes aren't stable between processes? – senderle Oct 31 '17 at 15:12
  • 2
    How would this answer change if `Math.random` did not use a PRNG but was instead implemented using a hardware RNG? The hardware RNG doesn't really have state in the normal sense, but it does produce random values (and thus function output is still different regardless of input), right? – mtraceur Nov 01 '17 at 18:17
  • @mtraceur, that's correct. But I don't think the answer would change much. In fact, this is why I don't spend time talking about "state" in my answer. Reading from a hardware RNG also means reading from "data calculated and stored elsewhere." It's just that the data is calculated and stored in the physical medium of the computer itself as it interacts with its environment. – senderle Nov 01 '17 at 18:28
  • @mtraceur, thinking a bit more, I see that although I still think my answer wouldn't change, the definition of "doing work" in rule #2 above would have to be specified carefully in the case of a hardware RNG. Perhaps "it doesn't do any work" should really be "it doesn't depend on any work done." – senderle Nov 01 '17 at 18:36
  • @mtraceur, Or there might be a QM-based argument: when we read a random value, we cause a change in the state of the world through the mechanisms of [quantum decoherence](https://en.wikipedia.org/wiki/Quantum_decoherence). The state change is the work done. – senderle Nov 01 '17 at 18:40
  • Predicting the output based on the input is not difficult at all (after all, there is some code which implements just that). – Paŭlo Ebermann Nov 01 '17 at 19:38
  • @PaŭloEbermann, I can see how "predicting" isn't exactly right. But that is often how hash functions are described, and what you say is true of them too, isn't it? – senderle Nov 01 '17 at 20:07
  • 1
    This same logic applies even to more sophisticated randomization schemes, even ones like [Random.org's atmospheric noise](https://www.random.org). +1 – jpmc26 Nov 01 '17 at 21:15
  • @NateEldredge The RNG algorithm is unspecified and the RNG cannot be reseeded, so there is _no way to observe_ whether a `Math.random` call has actually happened. Therefore, calling `Math.random` and discarding the result is OK in a pure function. You cannot tell the presence or lack of such a call to `Math.random` apart by replacing the call to this pure function with its return value, because when doing so, you need a new execution, which has a new seed. –  Nov 04 '17 at 15:31
40

A pure function is a function where the return value is only determined by its input values, without observable side effects

By using Math.random, you are determining its value by something other than input values. It's not a pure function.

source

TKoL
  • 13,158
  • 3
  • 39
  • 73
25

No, it isn't a pure function because its output doesn't depend only on the input provided (Math.random() can output any value), while pure functions should always output the same value for same inputs.

If a function is pure, it's safe to optimize away multiple calls with the same inputs and just reuse the result of an earlier call.

P.S for me at least and for many others, redux made the term pure function popular. Straight from the redux docs:

Things you should never do inside a reducer:

  • Mutate its arguments;

  • Perform side effects like API calls and routing transitions;

  • Call non-pure functions, e.g. Date.now() or Math.random().

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847
Shubhnik Singh
  • 1,329
  • 10
  • 13
  • 3
    Although others have provided great answers, but i couldn't resist myself when redux doc's came to my mind and Math.random() specifically mentioned in them :) – Shubhnik Singh Oct 31 '17 at 12:36
21

From mathematical point of view, your signature is not

test: <number, number> -> <number>

but

test: <environment, number, number> -> <environment, number>

where the environment is capable of providing results of Math.random(). And actually generating the random value mutates the environment as a side effect, so you also return a new environment, which is not equal to the first one!

In other words, if you need any kind of input that does not come from initial arguments (the <number, number> part), then you need to be provided with execution environment (that in this example provides state for Math). The same applies to other things mentioned by other answers, like I/O or such.


As an analogy, you can also notice this is how object-oriented programming can be represented - if we say, e.g.

SomeClass something
T result = something.foo(x, y)

then actually we are using

foo: <something: SomeClass, x: Object, y: Object> -> <SomeClass, T>

with the object that has its method invoked being part of the environment. And why the SomeClass part of result? Because something's state could have changed as well!

Adam Kotwasinski
  • 4,377
  • 3
  • 17
  • 40
  • 7
    Worse yet, the environment is also mutated, so `test: -> ` it should be – Bergi Oct 31 '17 at 18:00
  • 1
    I'm not sure the OO example is very similar. `a.F(b, c)` can be seen as syntactic sugar for `F(a, b, c)` with a special rule to despatch to overloaded definitions of `F` based on the type of `a` (this is actually how Python represents it). But `a` is still explicit in both notations, whereas the environment in a non-pure function is never mentioned in the source code. – IMSoP Nov 02 '17 at 16:05
11

Pure functions always return the same value for same input. Pure functions are predictable and are referential transparent which means that we can replace the function call with the output returned and it will not change the working of the program.

https://github.com/MostlyAdequate/mostly-adequate-guide/blob/master/ch3.md

Rishabh Mishra
  • 498
  • 8
  • 21
10

In addition to the other answers that correctly point out how this function is non-deterministic, it also has a side-effect: it will cause future calls to math.random() to return a different answer. And a random-number generator that doesn’t have that property will generally perform some kind of I/O, such as to read from a random device provided by the OS. Either is verboten for a pure function.

Davislor
  • 14,674
  • 2
  • 34
  • 49
7

No, it isn't. You can't figure out the result at all, so this piece of code can't be tested. To make that code testable, you need to extract the component that generates the random number:

function test(min, max, generator) {
  return  generator() * (max - min) + min;
}

Now, you can mock the generator and test your code properly:

const result = test(1, 2, () => 3);
result == 4 //always true

And in your "production" code:

const result = test(1, 2, Math.random);
Héctor
  • 24,444
  • 35
  • 132
  • 243
  • 1
    ▲ for your thought for testability. With a little care you can also produce repeatable tests while accepting a `util.Random`, which you can seed at the start of a test run to repeat old behaviour or for a new (but repeatable) run. If multi-threading, you may be able to do this in the main thread and use that `Random` to seed repeatable thread-local `Random`s. However, as I understand it, `test(int,int,Random)` is not considered pure as it alters the state of the `Random`. – PJTraill Nov 01 '17 at 21:10
2

Would you be fine with the following:

return ("" + test(0,1)) + test(0,1);

be equivalent to

var temp = test(0, 1);
return ("" + temp) + temp;

?

You see, the definition of pure is a function whose output does not change with anything other than its inputs. If we say that JavaScript had a way to tag a function pure and take advantage of this, the optimizer would be allowed to rewrite the first expression as the second.

I have practical experience with this. SQL server allowed getdate() and newid() in "pure" functions and the optimizer would dedupe calls at will. Sometimes this would do something dumb.

Joshua
  • 40,822
  • 8
  • 72
  • 132