Uma função que chama Math.random () é pura?


112

O seguinte é uma função pura?

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

Meu entendimento é que uma função pura segue estas condições:

  1. Ele retorna um valor calculado a partir dos parâmetros
  2. Ele não faz nenhum trabalho além de calcular o valor de retorno

Se esta definição estiver correta, minha função é uma função pura? Ou meu entendimento do que define uma função pura está incorreto?


66
"Ele não faz nenhum trabalho além de calcular o valor de retorno" Mas ele chama o Math.random()que muda o estado do RNG.
Paul Draper

1
O segundo ponto é mais parecido com "não muda o estado externo (para a função)"; e o primeiro deve ser complementado algo como "ele retorna o MESMO valor calculado a partir dos MESMOS parâmetros", como as pessoas
escreveram

Existe uma noção de uma função semipura, permitindo a aleatoriedade? Por exemplo, test(a,b)sempre retorna o mesmo objeto Random(a,b)(que pode representar diferentes números concretos)? Se você mantiver o Randomsimbólico, ele é puro no sentido clássico, se você avaliá-lo cedo e colocar em números, talvez como uma espécie de otimização, a função ainda retém alguma "pureza".
jdm

1
"Qualquer pessoa que considere métodos aritméticos de produção de dígitos aleatórios está, é claro, em estado de pecado." - John von Neumann
Steve Kuo

1
@jdm se você seguir a linha de "semi-puro", onde considera funções módulo puro alguns efeitos colaterais bem definidos , você pode acabar inventando mônadas. Bem vindo ao lado sombrio. > :)
luqui

Respostas:


185

Não, não é. Dada a mesma entrada, esta função retornará valores diferentes. E então você não pode construir uma 'tabela' que mapeie a entrada e as saídas.

Do artigo da Wikipedia para a função Pure :

A função sempre avalia o mesmo valor de resultado dado o (s) mesmo (s) valor (es) de argumento. O valor do resultado da função não pode depender de qualquer informação oculta ou estado que pode mudar enquanto a execução do programa prossegue ou entre diferentes execuções do programa, nem pode depender de qualquer entrada externa de dispositivos de E / S

Além disso, outra coisa é que uma função pura pode ser substituída por uma tabela que representa o mapeamento da entrada e saída, conforme explicado neste tópico .

Se você quiser reescrever esta função e alterá-la para uma função pura, você deve passar o valor aleatório como um argumento também

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

e chame-o desta forma (por exemplo, com 2 e 5 como mínimo e máximo):

test( Math.random(), 2, 5)

2
E se você propusesse novamente o gerador aleatório a cada vez dentro da função antes de chamar Math.random?
cs95

16
@ cᴏʟᴅsᴘᴇᴇᴅ Mesmo assim, ainda teria efeitos colaterais (alterando a Math.randomprodução futura ); para que seja puro, você teria que salvar de alguma forma o estado RNG atual, propagá-lo novamente, chamá- Math.randomlo e restaurá-lo ao estado anterior.
LegionMammal978

2
@ cᴏʟᴅsᴘᴇᴇᴅ Todo o RNG calculado é baseado na falsa aleatoriedade. Algo deve estar rodando por baixo que faz com que pareça aleatório e você não pode explicar isso, tornando-o impuro. Além disso, e provavelmente o mais importante para sua pergunta, você não pode semear
Math.random

14
@ LegionMammal978… e fazê-lo atomicamente.
wchargin

2
@ cᴏʟᴅsᴘᴇᴇᴅ Existem maneiras de ter RNGs que operam com funções puras, mas envolve passar o estado RNG para a função e fazer com que a função retorne o estado RNG de substituição, que é como Haskell (uma linguagem de programação funcional que impõe pureza funcional) atinge isto.
Pharap

50

A resposta simples para sua pergunta é que Math.random()viola a regra nº 2.

Muitas outras respostas aqui indicaram que a presença de Math.random()significa que essa função não é pura. Mas acho que vale a pena dizer por Math.random() que as funções de contaminação que o usam.

Como todos os geradores de números pseudoaleatórios, Math.random()começa com um valor "semente". Em seguida, ele usa esse valor como ponto de partida para uma cadeia de manipulações de bits de baixo nível ou outras operações que resultam em uma saída imprevisível (mas não realmente aleatória ).

Em JavaScript, o processo envolvido depende da implementação e, ao contrário de muitas outras linguagens, o JavaScript não fornece nenhuma maneira de selecionar a semente :

A implementação seleciona a semente inicial para o algoritmo de geração de números aleatórios; não pode ser escolhido ou redefinido pelo usuário.

É por isso que essa função não é pura: JavaScript está essencialmente usando um parâmetro de função implícito sobre o qual você não tem controle. Ele está lendo esse parâmetro de dados calculados e armazenados em outro lugar e, portanto, viola a regra nº 2 em sua definição.

Se você quiser fazer disso uma função pura, poderá usar um dos geradores de números aleatórios alternativos descritos aqui . Chame aquele gerador seedable_random. Leva um parâmetro (a semente) e retorna um número "aleatório". Claro, esse número não é realmente aleatório; é determinado exclusivamente pela semente. É por isso que esta é uma função pura. A saída de seedable_randomé apenas "aleatória" no sentido de que é difícil prever a saída com base na entrada.

A versão pura desta função precisaria de três parâmetros:

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

Para qualquer dado triplo de (min, max, seed)parâmetros, isso sempre retornará o mesmo resultado.

Observe que, se você quiser que a saída de seedable_randomseja realmente aleatória, precisará encontrar uma maneira de tornar a semente aleatória! E qualquer estratégia que você usasse seria inevitavelmente não pura, porque exigiria que você reunisse informações de uma fonte externa à sua função. Como o mtraceur e o jpmc26 me lembram, isso inclui todas as abordagens físicas: geradores de números aleatórios de hardware , webcams com tampas de lente , coletores de ruído atmosférico - até lâmpadas de lava . Tudo isso envolve o uso de dados calculados e armazenados fora da função.


8
Math.random () não apenas lê sua "semente", mas também a modifica, de forma que a próxima chamada retorne algo diferente. Dependendo e modificando, o estado estático é definitivamente ruim para uma função pura.
Nate Eldredge

2
@NateEldredge, é isso mesmo! Embora a simples leitura de um valor dependente da implementação seja suficiente para quebrar a pureza. Por exemplo, já percebeu como os hashes do Python 3 não são estáveis ​​entre os processos?
remetente em

2
Como essa resposta mudaria se Math.randomvocê não usasse um PRNG, mas fosse implementado usando um RNG de hardware? O hardware RNG realmente não tem estado no sentido normal, mas ele produz valores aleatórios (e, portanto, a saída da função ainda é diferente, independentemente da entrada), certo?
mtraceur 01 de

@mtraceur, correto. Mas não acho que a resposta mudaria muito. Na verdade, é por isso que não perco tempo falando sobre "estado" em minha resposta. Ler de um RNG de hardware também significa ler de "dados calculados e armazenados em outro lugar". Só que os dados são calculados e armazenados no meio físico do próprio computador à medida que ele interage com seu ambiente.
remetente em

1
Essa mesma lógica se aplica até mesmo a esquemas de randomização mais sofisticados, mesmo aqueles como o ruído atmosférico da Random.org . +1
jpmc26

38

Uma função pura é uma função em que o valor de retorno é determinado apenas por seus valores de entrada, sem efeitos colaterais observáveis

Ao usar Math.random, você está determinando seu valor por algo diferente dos valores de entrada. Não é uma função pura.

fonte


25

Não, não é uma função pura porque sua saída não depende apenas da entrada fornecida (Math.random () pode produzir qualquer valor), enquanto as funções puras devem sempre produzir o mesmo valor para as mesmas entradas.

Se uma função for pura, é seguro otimizar várias chamadas com as mesmas entradas e apenas reutilizar o resultado de uma chamada anterior.

PS, pelo menos para mim e para muitos outros, redux tornou o termo função pura popular. Direto dos documentos redux :

Coisas que você nunca deve fazer dentro de um redutor:

  • Transforme seus argumentos;

  • Execute efeitos colaterais como chamadas de API e transições de roteamento;

  • Chame funções não puras, por exemplo, Date.now () ou Math.random ().


3
Embora outros tenham fornecido ótimas respostas, eu não pude resistir a mim mesmo quando redux docs vieram à minha mente e Math.random () especificamente mencionado neles :)
Shubhnik Singh

20

Do ponto de vista matemático, sua assinatura não é

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

mas

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

onde o environmenté capaz de fornecer resultados Math.random(). E, de fato, gerar o valor aleatório altera o ambiente como um efeito colateral, então você também retorna um novo ambiente, que não é igual ao primeiro!

Em outras palavras, se você precisa de qualquer tipo de entrada que não venha dos argumentos iniciais (a <number, number>parte), então você precisa de um ambiente de execução (que neste exemplo fornece estado para Math). O mesmo se aplica a outras coisas mencionadas por outras respostas, como I / O ou algo semelhante.


Como uma analogia, você também pode notar que é assim que a programação orientada a objetos pode ser representada - se dissermos, por exemplo

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

então, na verdade, estamos usando

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

com o objeto que tem seu método invocado sendo parte do ambiente. E por que a SomeClassparte do resultado? Porque somethingo estado de também poderia ter mudado!


7
Pior ainda, o ambiente também está mutado, então test: <environment, number, number> -> <environment, number>deveria estar
Bergi

1
Não tenho certeza se o exemplo OO é muito semelhante. a.F(b, c)pode ser visto como um açúcar sintático para F(a, b, c)com uma regra especial para despachar para definições sobrecarregadas de com Fbase no tipo de a(na verdade é assim que Python o representa). Mas aainda é explícito em ambas as notações, enquanto o ambiente em uma função não pura nunca é mencionado no código-fonte.
IMSoP


10

Além das outras respostas que apontam corretamente como essa função é não determinística, ela também tem um efeito colateral: fará com que chamadas futuras math.random()para retornem uma resposta diferente. E um gerador de números aleatórios que não tem essa propriedade geralmente executa algum tipo de E / S, como ler de um dispositivo aleatório fornecido pelo sistema operacional. Qualquer um é proibido para uma função pura.


7

Não, não é. Você não consegue descobrir o resultado de forma alguma, portanto, esse pedaço de código não pode ser testado. Para tornar esse código testável, você precisa extrair o componente que gera o número aleatório:

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

Agora, você pode simular o gerador e testar seu código corretamente:

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

E em seu código de "produção":

const result = test(1, 2, Math.random);

1
▲ pelo seu pensamento para testabilidade. Com um pouco de cuidado, você também pode produzir testes repetíveis enquanto aceita um util.Random, que pode ser propagado no início de uma execução de teste para repetir o comportamento antigo ou para uma nova (mas repetível) execução. Se for multi-threading, você poderá fazer isso no thread principal e usá-lo Randompara semear threads locais repetíveis Random. No entanto, pelo que entendi, test(int,int,Random)não é considerado puro, pois altera o estado do Random.
PJTraill 01 de

2

Você estaria bem com o seguinte:

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

ser equivalente a

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

?

Veja, a definição de puro é uma função cuja saída não muda com nada além de suas entradas. Se dissermos que o JavaScript tem uma maneira de marcar uma função pura e tirar vantagem disso, o otimizador terá permissão para reescrever a primeira expressão como a segunda.

Tenho experiência prática com isso. Servidor SQL permitido getdate()e newid()em funções "puras" e o otimizador eliminaria as chamadas à vontade. Às vezes, isso faria algo estúpido.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.