Como você chama uma função em que a mesma entrada sempre retorna a mesma saída, mas também tem efeitos colaterais?


43

Digamos que temos uma função pura normal, como

function add(a, b) {
  return a + b
}

E então nós a alteramos para que tenha um efeito colateral

function add(a, b) {
  writeToDatabase(Math.random())
  return a + b;
}

Até onde eu sei, não é considerada uma função pura, porque muitas vezes ouço as pessoas chamarem funções puras de "funções sem efeitos colaterais". No entanto, ele se comporta como uma função pura na medida em que retornará a mesma saída para as mesmas entradas.

Existe um nome diferente para esse tipo de função, ele não tem nome ou ainda é realmente puro e estou enganado sobre a definição de pureza?


91
"Não é uma função pura".
Ross Patterson

2
@RossPatterson, foi o que eu pensei também, mas, ao perguntar, aprendi sobre transparência referencial, então fico feliz por não ter mantido isso para mim.
M0meni

9
Se writeToDatabasefalhar, isso poderá desencadear uma exceção, fazendo com que sua segunda addfunção produza uma exceção às vezes, mesmo se chamada com os mesmos argumentos que antes não apresentavam problemas ... na maioria das vezes, os efeitos colaterais introduzem esse tipo de condições relacionadas a erros que quebram "pureza de entrada e saída".
Bakuriu

25
Algo que sempre fornece a mesma saída para uma determinada entrada é chamado determinístico .
Njzk2

2
@ njzk2: Verdade, mas também é sem estado . Uma função determinística com estado pode não fornecer a mesma saída para cada entrada. Exemplo: F(x)é definido para retornar truese for chamado com o mesmo argumento da chamada anterior. Claramente com a sequência {1,2,2} => {undefined, false, true}isso é determinístico, mas fornece resultados diferentes para F(2).
MSalters

Respostas:


85

Não tenho certeza sobre as definições universais de pureza, mas do ponto de vista de Haskell (uma linguagem em que os programadores tendem a se preocupar com coisas como pureza e transparência referencial), apenas a primeira de suas funções é "pura". A segunda versão do addnão é pura . Então, em resposta à sua pergunta, eu chamaria de "impura";)

De acordo com esta definição, uma função pura é uma função que:

  1. Depende apenas de sua entrada. Ou seja, dada a mesma entrada, ela sempre retornará a mesma saída.
  2. É referencialmente transparente: a função pode ser livremente substituída por seu valor e o "comportamento" do programa não muda.

Com essa definição, fica claro que sua segunda função não pode ser considerada pura, pois quebra a regra 2. Ou seja, os dois programas a seguir NÃO são equivalentes:

function f(a, b) { 
    return add(a, b) + add(a, b);
}

e

function g(a, b) {
    c = add(a, b);
    return c + c;
}

Isso ocorre porque, embora ambas as funções retornem o mesmo valor, a função fgravará no banco de dados duas vezes, mas ggravará uma vez! É muito provável que as gravações no banco de dados façam parte do comportamento observável do seu programa. Nesse caso, mostrei que sua segunda versão do add"não é" pura.

Se as gravações no banco de dados não são uma parte observável do comportamento do seu programa, as duas versões addpodem ser consideradas equivalentes e puras. Mas não consigo pensar em um cenário em que a gravação no banco de dados não importe. Até o registro é importante!


1
"Depende apenas de sua entrada" não é redundante devido à transparência referencial? O que implicaria que RT é sinônimo de pureza? (Eu estou ficando cada vez mais confuso sobre isso quanto mais fontes eu olhar para cima)
Ixrec

Eu concordo que é confuso. Só consigo pensar em exemplos inventados. Say f(x)depende não apenas de x, mas também de alguma variável global externa y. Então, se ftiver a propriedade de RT, você poderá trocar livremente todas as ocorrências com seu valor de retorno , desde que não toque y . Sim, meu exemplo é duvidoso. Mas o importante é: se fgravar no banco de dados (ou gravar em um log), perderá a propriedade do RT: agora não importa se você deixa o mundo yintocado, você sabe que o significado do seu programa muda dependendo de você realmente ligue fou simplesmente use seu valor de retorno.
30716 Andres F.

Humph. Digamos que tenhamos uma função que seja pura, exceto pelos efeitos colaterais, e que também tenha esse comportamento garantido em que suas duas amostras sejam equivalentes. (Na verdade, eu tive esse caso, então não é hipotético.) Acho que ainda não terminamos.
Joshua Joshua

2
Eu diria que a segunda função também poderia quebrar a regra 1. Dependendo do idioma e das práticas de manipulação de erros da API do banco de dados em uso, é possível que a função não retorne nada se o banco de dados estiver indisponível ou se a gravação do banco de dados falhar por algum motivo.
Zach Lipton

1
Desde que Haskell foi mencionado: No Haskell, adicionar um efeito colateral como esse requer a alteração da assinatura da função . (pense nisso como fornecer o banco de dados original como uma entrada adicional e obter o banco de dados modificado como um valor de retorno adicional da função). Na verdade, é possível modelar os efeitos colaterais de maneira bastante elegante no sistema de tipos dessa maneira, mas as principais linguagens de hoje não se importam o suficiente com efeitos colaterais e pureza para fazer isso.
ComicSansMS

19

Como você chama uma função [para a qual] a mesma entrada sempre retornará a mesma saída, mas também tem efeitos colaterais?

Essa função é chamada

determinista

Um algoritmo cujo comportamento pode ser completamente previsto a partir da entrada.

termwiki.com

Em relação ao estado:

Dependendo de qual definição de uma função você usa, uma função não tem estado. Se você vem do mundo orientado a objetos, lembre-se de que x.f(y)é um método. Em função, seria semelhante f(x,y). E se você gosta de fechamentos com escopo lexical fechado, lembre-se de que o estado imutável também pode fazer parte da expressão das funções. É apenas um estado mutável que afetaria a natureza determinística das funções. Portanto, f (x) = x + 1 é determinístico desde que o 1 não mude. Não importa onde o 1 está armazenado.

Suas funções são determinísticas. Seu primeiro também é uma função pura. Seu segundo não é puro.

Função pura

  1. A função sempre avalia o mesmo valor de resultado, dados os mesmos valores de argumento. O valor do resultado da função não pode depender de nenhuma informação ou estado oculto que possa ser alterado durante a execução do programa ou entre diferentes execuções do programa, nem de nenhuma entrada externa de dispositivos de E / S.

  2. A avaliação do resultado não causa nenhum efeito colateral ou saída semanticamente observável, como mutação de objetos mutáveis ​​ou saída para dispositivos de E / S.

wikipedia.org

O ponto 1 significa determinístico . O ponto 2 significa transparência referencial . Juntos, eles significam que uma função pura apenas permite que seus argumentos e seu valor retornado sejam alterados. Nada mais causa mudanças. Nada mais mudou.


-1. A gravação no banco de dados depende do estado externo que geralmente não pode ser determinado olhando nas entradas. O banco de dados pode estar indisponível por vários motivos e se a operação será bem-sucedida não é previsível. Este não é um comportamento determinístico.
Frax

A memória do sistema @Frax pode estar indisponível. A CPU pode estar indisponível. Ser determinista não garante sucesso. Garante que o comportamento bem-sucedido é previsível.
Candied_orange #

OOMing não é específico para nenhuma função, é uma categoria de problema diferente. Agora, vejamos o ponto 1. da sua definição de "função pura" (que de fato significa "determinística"): "O valor do resultado da função não pode depender de nenhuma informação ou estado oculto que possa mudar enquanto a execução do programa prossegue ou entre diferentes execuções de programa , nem pode depender de nenhuma entrada externa de dispositivos de E / S ". O banco de dados é esse tipo de estado, portanto, a função dos OPs claramente não atende a essa condição - não é determinística.
Frax

@candied_orange Eu concordaria se a gravação no DB dependesse apenas das entradas. Mas é Math.random(). Portanto, não, a menos que assumamos um PRNG (em vez de um RNG físico) E consideremos que os PRNGs indicam parte da entrada (o que não é, a referência é codificada), não é determinístico.
Marstato #

1
@candied_orange sua citação de estados determinísticos "um algoritmo cujo comportamento pode ser completamente previsto a partir da entrada". Escrever para IO, para mim, é definitivamente comportamento, não resultado.
Marstato #

9

Se você não se importa com o efeito colateral, é referencialmente transparente. É claro que é possível que você não se importe, mas alguém o faça, então a aplicabilidade do termo depende do contexto.

Não conheço um termo geral para exatamente as propriedades que você descreve, mas um subconjunto importante são aquelas que são idempotentes . Na ciência da computação, um pouco diferente da matemática *, uma função idempotente é aquela que pode ser repetida com o mesmo efeito; isto é, o bom efeito colateral de fazê-lo muitas vezes é o mesmo de fazê-lo uma vez.

Portanto, se seu efeito colateral fosse atualizar um banco de dados com um determinado valor em uma determinada linha ou criar um arquivo com conteúdo exatamente consistente, seria idempotente , mas se adicionado ao banco de dados ou anexado a um arquivo , então não faria.

Combinações de funções idempotentes podem ou não ser idempotentes como um todo.

* O uso de idempotente de maneira diferente na ciência da computação e na matemática parece ter resultado de um uso incorreto do termo matemático que foi então adotado porque o conceito é útil.


3
o termo "referencialmente transparente" é mais estritamente definido do que "alguém se importa" ou não. mesmo se desconsiderarmos as questões de E / S, como problemas de conexão, falta de cadeias de conexão, tempo limite etc., ainda é fácil mostrar que um programa que substitui (f x, f x)por let y = f x in (y, y)ficará sem espaço em disco - exceções duas vezes mais rápido que você poderia argumentar casos extremos com os quais você não se importa, mas com uma definição tão confusa, podemos chamar de new Random().Next()referencialmente transparente, porque diabos, eu não me importo com o número que recebo.
Sara

@kai: Dependendo do contexto, você pode ignorar os efeitos colaterais. Por outro lado, o valor de retorno de uma função aleatória não é um efeito colateral: é o seu principal efeito.
Giorgio

@Giorgio Random.Nextno .NET realmente tem efeitos colaterais. Muito mesmo. Se puder Next, atribua-o a uma variável e, em seguida, ligue Nextnovamente e atribua-o a outra variável, é provável que não sejam iguais. Por quê? Porque invocar Nextaltera algum estado interno oculto no Randomobjeto. Esse é o oposto polar da transparência referencial. Não entendo sua alegação de que "efeitos principais" não podem ser efeitos colaterais. No código imperativo, é mais comum do que não que o efeito principal seja um efeito colateral, porque os programas imperativos são stateful por natureza.
Sara

3

Eu não sei como essas funções são chamadas (ou se existe algum nome sistemático), mas eu chamaria uma função que não é pura (como outras respostas se escondem), mas sempre retorna o mesmo resultado se fornecida com os mesmos parâmetros "função de sua parâmetros "(em comparação com a função de seus parâmetros e algum outro estado). Eu chamaria isso apenas de função, mas, infelizmente, quando dizemos "função" no contexto da programação, queremos dizer algo que não precisa ser uma função real.


1
Acordado! É (informalmente) a definição matemática de "função", mas, como você diz, infelizmente "função" significa algo diferente nas linguagens de programação, onde está mais próximo de "um procedimento passo a passo necessário para obter um valor".
Andres F.

2

Depende basicamente de você se importar ou não com a impureza. Se a semântica desta tabela é que você não se importa com quantas entradas existem, então é pura. Senão, não é puro.

Ou, em outras palavras, tudo bem, desde que as otimizações baseadas na pureza não quebrem a semântica do programa.

Um exemplo mais realista seria se você estivesse tentando depurar essa função e adicionasse instruções de log. Tecnicamente, o log é um efeito colateral. Os logs tornam impuro? Não.


Bem, isto depende. Talvez os logs o tornem impuro, por exemplo, se você se importa com quantas vezes e em que horários, "INFO f () chamado" aparece em seu log. Que muitas vezes você fazer :)
Andres F.

8
-1 Logs são importantes. Na maioria das plataformas, a saída de qualquer tipo sincroniza implicitamente o thread de execução. O comportamento do seu programa depende de outras gravações de threads, de gravadores de log externos, às vezes de leituras de log e de descritores de estado dos arquivos. É tão puro quanto um balde de terra.
Basilevs

@AndresF. Bem, você provavelmente não se importa com o número literal de vezes. Provavelmente, você só se importa que seja registrado quantas vezes a função foi chamada.
DeadMG

@Basilevs O comportamento da função não depende absolutamente deles. Se a gravação do log falhar, você continua.
DeadMG

2
É uma questão de escolher ou não definir o criador de logs para fazer parte do ambiente de execução. Por outro exemplo, minha função pura ainda é pura se eu anexar um depurador ao processo e definir um ponto de interrupção nele? Do ponto de vista da pessoa que usa o depurador, claramente a função tem efeitos colaterais, mas normalmente analisamos um programa com a convenção de que isso "não conta". O mesmo pode (mas não precisa) para o registro usado para depuração, que eu presumo é o motivo pelo qual o rastreamento está ocultando sua impureza. Log de missão crítica, por exemplo, para auditoria, é claro, é um efeito colateral significativo.
Steve Jessop

1

Eu diria que a melhor coisa a se perguntar não é como o chamaríamos, mas como analisaríamos esse pedaço de código. E minha primeira pergunta-chave nessa análise seria:

  • O efeito colateral depende do argumento da função ou do resultado do efeito colateral?
    • Não: a "função eficaz" pode ser refatorada em uma função pura, uma ação eficaz e um mecanismo para combiná-los.
    • Sim: A "função eficaz" é uma função que produz um resultado monádico.

Isso é simples de ilustrar em Haskell (e essa frase é apenas meia piada). Um exemplo do caso "não" seria algo como isto:

double :: Num a => a -> IO a
double x = do
  putStrLn "I'm doubling some number"
  return (x*2)

Neste exemplo, a ação que executamos (imprime a linha "I'm doubling some number") não afeta a relação entre xe o resultado. Isso significa que podemos refatorá-lo dessa maneira (usando a Applicativeclasse e seu *>operador), o que mostra que a função e o efeito são de fato ortogonais:

double :: Num a => a -> IO a
double x = action *> pure (function x)
  where
    -- The pure function 
    function x = x*2  
    -- The side effect
    action = putStrLn "I'm doubling some number"

Então, neste caso, eu pessoalmente diria que é um caso em que você pode fatorar uma função pura. Muita programação de Haskell trata disso - aprendendo a fatorar as partes puras a partir do código eficaz.

Um exemplo do tipo "yes", em que as partes pura e eficaz não são ortogonais:

double :: Num a => a -> IO a
double x = do
  putStrLn ("I'm doubling the number " ++ show x)
  return (x*2)

Agora, a sequência impressa depende do valor de x. A parte da função (multiplicada xpor dois), no entanto, não depende do efeito, portanto ainda podemos fatorá-la:

logged :: (a -> b) -> (a -> IO x) -> IO b
logged function logger a = do
  logger a
  return (function a)

double x = logged function logger
  where function = (*2) 
        logger x putStrLn ("I'm doubling the number " ++ show x)

Eu poderia continuar explicando outros exemplos, mas espero que isso seja suficiente para ilustrar o ponto em que comecei: você não "chama" isso de algo, analisa como as partes puras e eficazes se relacionam e as fatora quando é necessário. para sua vantagem.

Essa é uma das razões pelas quais a Haskell usa sua Monadclasse tão extensivamente. Mônadas são (entre outras coisas) uma ferramenta para realizar esse tipo de análise e refatoração.


-2

As funções que se destinam a causar efeitos colaterais geralmente são chamadas de eficazes . Exemplo https://slpopejoy.github.io/posts/Effectful01.html


Apenas responda para mencionar o termo amplamente reconhecido eficaz e ele é votado abaixo ... A ignorância é uma bênção, suponho. ..
Ben Hutchison

"eficaz" é uma palavra que o autor dessa publicação cooptou para "ter efeitos colaterais". Ele mesmo diz isso.
Robert Harvey

Pesquisando função effectful revela abundância de evidências é um termo amplamente usado. O post do blog foi dado como um dos muitos exemplos, não como a definição. Nos círculos de programação funcional, onde as funções puras são o padrão, é necessário um termo positivo para descrever as funções que afetam deliberadamente os efeitos colaterais. Ou seja, mais do que apenas a ausência de pureza . Eficaz é esse termo. Agora, considere-se educado.
Ben Hutchison

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.