¿Es una función que llama a Math.random () pura?

112

¿Es lo siguiente una función pura?

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

Tengo entendido que una función pura sigue estas condiciones:

  1. Devuelve un valor calculado a partir de los parámetros.
  2. No hace ningún trabajo más que calcular el valor de retorno

Si esta definición es correcta, ¿es mi función una función pura? ¿O mi comprensión de lo que define una función pura es incorrecta?

Kiwi Rupia
fuente
66
"No hace ningún trabajo más que calcular el valor de retorno" Pero llama lo Math.random()que cambia el estado del RNG.
Paul Draper
1
El segundo punto es más como "no cambia el estado externo (a la función)"; y el primero debe complementarse de alguna manera como "devuelve el MISMO valor calculado a partir de los MISMOS parámetros", como la gente ha escrito a continuación
MVCDS
¿Existe la noción de una función semipura que permita la aleatoriedad? Por ejemplo, test(a,b)siempre devuelve el mismo objeto Random(a,b)(que puede representar diferentes números concretos). Si mantiene Randomsimbólico, es puro en el sentido clásico, si lo evalúa temprano y lo pone en números, tal vez como una especie de optimización, la función aún conserva algo de "pureza".
jdm
1
"Cualquiera que considere métodos aritméticos para producir dígitos aleatorios está, por supuesto, en un estado de pecado". - John von Neumann
Steve Kuo
1
@jdm si sigues el hilo de "semi-puro", donde consideras que las funciones son módulo puro y algunos efectos secundarios bien definidos , podrías terminar inventando mónadas. Bienvenido al lado oscuro. > :)
luqui

Respuestas:

185

No, no es. Dada la misma entrada, esta función devolverá valores diferentes. Y luego no puede construir una 'tabla' que mapee la entrada y las salidas.

Del artículo de Wikipedia para la función pura :

La función siempre evalúa el mismo valor de resultado dado los mismos valores de argumento. El valor del resultado de la función no puede depender de ninguna información oculta o estado que pueda cambiar mientras se ejecuta la ejecución del programa o entre diferentes ejecuciones del programa, ni puede depender de ninguna entrada externa de los dispositivos de E / S.

Además, otra cosa es que una función pura se puede reemplazar con una tabla que representa el mapeo de la entrada y la salida, como se explica en este hilo .

Si desea reescribir esta función y cambiarla a una función pura, también debe pasar el valor aleatorio como argumento

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

y luego llámelo de esta manera (ejemplo, con 2 y 5 como mínimo y máximo):

test( Math.random(), 2, 5)
Christian Benseler
fuente
2
¿Qué pasaría si tuviera que volver a sembrar el generador aleatorio cada vez dentro de la función antes de llamar Math.random?
cs95
16
@ cᴏʟᴅsᴘᴇᴇᴅ Incluso entonces, todavía tendría efectos secundarios (cambiando la Math.randomproducción futura ); para que sea puro, tendría que guardar de alguna manera el estado actual de RNG, reiniciarlo, llamarlo Math.randomy restaurarlo al estado anterior.
LegionMammal978
2
@ cᴏʟᴅsᴘᴇᴇᴅ Todo el RNG calculado se basa en simular la aleatoriedad. Algo tiene que estar ejecutándose por debajo que hace que parezca aleatorio y no puedes dar cuenta de eso, lo que lo hace impuro. Además, y probablemente lo más importante para su pregunta, no puede sembrar Math.random
zfrisch
14
@ LegionMammal978… y hacerlo de forma atómica.
wchargin
2
@ cᴏʟᴅsᴘᴇᴇᴅ Hay formas de tener RNG que operan con funciones puras, pero implica pasar el estado de RNG a la función y hacer que la función devuelva el estado de RNG de reemplazo, que es como Haskell (un lenguaje de programación funcional que refuerza la pureza funcional) logra eso.
Pharap
50

La respuesta simple a su pregunta es que Math.random()viola la regla # 2.

Muchas otras respuestas aquí han señalado que la presencia de Math.random()significa que esta función no es pura. Pero creo que vale la pena decir por qué Math.random() corrompe las funciones que lo usan.

Como todos los generadores de números pseudoaleatorios, Math.random()comienza con un valor "semilla". Luego usa ese valor como punto de partida para una cadena de manipulaciones de bits de bajo nivel u otras operaciones que dan como resultado una salida impredecible (pero no realmente aleatoria ).

En JavaScript, el proceso involucrado depende de la implementación y, a diferencia de muchos otros lenguajes, JavaScript no proporciona ninguna forma de seleccionar la semilla :

La implementación selecciona la semilla inicial para el algoritmo de generación de números aleatorios; el usuario no puede elegirlo ni restablecerlo.

Es por eso que esta función no es pura: JavaScript esencialmente está usando un parámetro de función implícito sobre el que no tienes control. Está leyendo ese parámetro a partir de datos calculados y almacenados en otro lugar y, por lo tanto, viola la regla n. ° 2 de su definición.

Si desea convertir esto en una función pura, puede usar uno de los generadores de números aleatorios alternativos que se describen aquí . Llame a ese generador seedable_random. Toma un parámetro (la semilla) y devuelve un número "aleatorio". Por supuesto, este número no es realmente aleatorio; está determinado únicamente por la semilla. Por eso es una función pura. La salida de seedable_randomes solo "aleatoria" en el sentido de que es difícil predecir la salida basándose en la entrada.

La versión pura de esta función necesitaría tomar tres parámetros:

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

Para cualquier triple de (min, max, seed)parámetros, siempre devolverá el mismo resultado.

Tenga en cuenta que si desea que la salida de seedable_randomsea verdaderamente aleatoria, ¡deberá encontrar una manera de aleatorizar la semilla! Y cualquier estrategia que utilizara inevitablemente no sería pura, porque requeriría que recopilara información de una fuente fuera de su función. Como me recuerdan mtraceur y jpmc26 , esto incluye todos los enfoques físicos: generadores de números aleatorios de hardware , cámaras web con tapas de lentes , colectores de ruido atmosférico , incluso lámparas de lava . Todo esto implica el uso de datos calculados y almacenados fuera de la función.

remitente
fuente
8
Math.random () no solo lee su "semilla" sino que también la modifica, de modo que la próxima llamada devolverá algo diferente. Dependiendo y modificando, el estado estático es definitivamente malo para una función pura.
Nate Eldredge
2
@NateEldredge, ¡bastante! Aunque simplemente leer un valor dependiente de la implementación es suficiente para romper la pureza. Por ejemplo, ¿alguna vez notó cómo los hashes de Python 3 no son estables entre procesos?
remitente
2
¿Cómo cambiaría esta respuesta si Math.randomno se usara un PRNG sino que se implementara usando un RNG de hardware? El hardware RNG realmente no tiene estado en el sentido normal, pero produce valores aleatorios (y por lo tanto, la salida de la función sigue siendo diferente independientemente de la entrada), ¿verdad?
mtraceur
@mtraceur, eso es correcto. Pero no creo que la respuesta cambie mucho. De hecho, es por eso que no dedico tiempo a hablar de "estado" en mi respuesta. Leer desde un RNG de hardware también significa leer desde "datos calculados y almacenados en otro lugar". Es solo que los datos se calculan y almacenan en el medio físico de la computadora en sí mientras interactúa con su entorno.
remitente
1
Esta misma lógica se aplica incluso a esquemas de aleatorización más sofisticados, incluso a aquellos como el ruido atmosférico de Random.org . +1
jpmc26
38

Una función pura es una función en la que el valor de retorno solo está determinado por sus valores de entrada, sin efectos secundarios observables

Al usar Math.random, está determinando su valor por algo diferente a los valores de entrada. No es una función pura.

fuente

TKoL
fuente
25

No, no es una función pura porque su salida no depende solo de la entrada proporcionada (Math.random () puede generar cualquier valor), mientras que las funciones puras siempre deben generar el mismo valor para las mismas entradas.

Si una función es pura, es seguro optimizar varias llamadas con las mismas entradas y simplemente reutilizar el resultado de una llamada anterior.

PD, al menos para mí y para muchos otros, redux hizo popular el término función pura . Directamente de los documentos de redux :

Cosas que nunca debes hacer dentro de un reductor:

  • Mute sus argumentos;

  • Realice efectos secundarios como llamadas a API y transiciones de enrutamiento;

  • Llame a funciones no puras, por ejemplo, Date.now () o Math.random ().

Shubhnik Singh
fuente
3
Aunque otros han proporcionado excelentes respuestas, no pude resistirme a mí mismo cuando me vinieron a la mente los documentos redux y Math.random () se mencionó específicamente en ellos :)
Shubhnik Singh
20

Desde el punto de vista matemático, su firma no es

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

pero

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

donde el environmentes capaz de proporcionar resultados Math.random(). Y de hecho, generar el valor aleatorio muta el entorno como efecto secundario, por lo que también devuelve un nuevo entorno, ¡que no es igual al primero!

En otras palabras, si necesita algún tipo de entrada que no provenga de los argumentos iniciales (la <number, number>parte), entonces debe contar con un entorno de ejecución (que en este ejemplo proporciona el estado Math). Lo mismo se aplica a otras cosas mencionadas por otras respuestas, como E / S o tal.


Como analogía, también puede notar que así es como se puede representar la programación orientada a objetos, si decimos, por ejemplo,

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

entonces en realidad estamos usando

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

con el objeto que tiene su método invocado siendo parte del entorno. ¿Y por qué la SomeClassparte de resultado? ¡Porque somethingel estado también podría haber cambiado!

Adam Kotwasinski
fuente
7
Peor aún, el medio ambiente también está mutado, por test: <environment, number, number> -> <environment, number>lo que debería serlo
Bergi
1
No estoy seguro de que el ejemplo de OO sea muy similar. a.F(b, c)puede verse como un azúcar sintáctico F(a, b, c)con una regla especial para enviar a definiciones sobrecargadas de Fsegún el tipo de a(así es como lo representa Python). Pero asigue siendo explícito en ambas notaciones, mientras que el entorno en una función no pura nunca se menciona en el código fuente.
IMSoP
10

Además de las otras respuestas que señalan correctamente cómo esta función no es determinista, también tiene un efecto secundario: hará que futuras llamadas math.random()a devuelvan una respuesta diferente. Y un generador de números aleatorios que no tiene esa propiedad generalmente realizará algún tipo de E / S, como leer desde un dispositivo aleatorio proporcionado por el sistema operativo. Cualquiera de los dos está prohibido para una función pura.

Davislor
fuente
7

No, no lo es. No puede averiguar el resultado en absoluto, por lo que este fragmento de código no se puede probar. Para que ese código sea comprobable, debe extraer el componente que genera el número aleatorio:

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

Ahora, puede burlarse del generador y probar su código correctamente:

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

Y en su código de "producción":

const result = test(1, 2, Math.random);
Héctor
fuente
1
▲ para su pensamiento de probabilidad. Con un poco de cuidado, también puede producir pruebas repetibles mientras acepta un util.Random, que puede sembrar al comienzo de una ejecución de prueba para repetir el comportamiento anterior o para una ejecución nueva (pero repetible). Si es un subproceso múltiple, es posible que pueda hacer esto en el subproceso principal y usarlo Randompara sembrar Randoms locales de subprocesos repetibles . Sin embargo, según tengo entendido, test(int,int,Random)no se considera puro ya que altera el estado del Random.
PJTraill
2

Estaría bien con lo siguiente:

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

ser equivalente a

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

?

Verá, la definición de puro es una función cuya salida no cambia con nada más que sus entradas. Si decimos que JavaScript tiene una forma de etiquetar una función pura y aprovechar esto, el optimizador podría reescribir la primera expresión como la segunda.

Tengo experiencia práctica con esto. El servidor SQL está permitido getdate()y newid()en funciones "puras" y el optimizador deduplica las llamadas a voluntad. A veces esto haría algo tonto.

Joshua
fuente