Uma função é imediatamente impura se receber uma função como parâmetro?

17

Como a pureza de um parâmetro de entrada é desconhecida até o tempo de execução, uma função é imediatamente considerada impura se ela assume uma função como parâmetro de entrada?

Relacionado: se uma função aplica uma função pura que é definida fora da função, mas não é passada como parâmetro, ela ainda é pura se atender aos critérios de não ter efeitos colaterais e a saída depender apenas da entrada?

Por contexto, estou escrevendo código funcional em JavaScript.

Dancrumb
fonte
Como um exemplo trivial de contador, considere:foo = function(function bar){ print(bar.toString()) }
David diz Reinstate Monica
1
@ DavidGrinberg Acho que não é um exemplo contrário e, na verdade, destaca um problema maior; se você possui funções que podem ser substituídas e não pode garantir que as implementações sejam livres de efeitos colaterais, não é possível garantir que a maioria das funções que pegam objetos e chamam seus métodos também seja pura. Talvez o bar toString () exclua alguns arquivos do disco?
21715 Joshua Taylor #:
3
@DavidGrinberg Mas acho que você está pensando em uma boa direção. foo = function(function bar) { return 3; } é puro e assume uma função como argumento.
21415 Joshua Taylor #:
@ JoshuaTaylor Fair point, eu não pensei nisso. Mas você já resolveu essencialmente o problema. Como uma correção alternativa, basta chamar o 'root' toString()(ou seja, o que você encontraria no objeto Java).
David diz Reinstate Monica

Respostas:

22

Desde que todos os valores usados ​​na função sejam definidos apenas por seus parâmetros, é uma função pura.

A faceta em que a saída é sempre a mesma para a mesma entrada é controlada se os parâmetros são puros. Se você assumir que os parâmetros (como um argumento de função) também são puros, então é puro.

Em uma linguagem como Javascript, onde a pureza não é imposta, isso significa que é possível fazer com que uma função pura seja de outro modo impura, invocando uma função impura passada como parâmetro.

Isso significa efetivamente que, para linguagens que não impõem pureza (ou seja, quase todas), é impossível definir uma função pura que invoque funções passadas como argumentos. Ainda é útil escrevê-los o mais puro possível e argumentar sobre eles como funções puras, mas é preciso ter cuidado, pois a suposição de que é puro será quebrada se você passar os argumentos errados.

Na minha experiência na prática, isso geralmente não é grande coisa - acho raro que funções impuras sejam usadas como argumentos de função para funções puras.

Daenyth
fonte
Com relação à sua afirmação de que "desde que todos os valores usados ​​na função sejam definidos apenas por seus parâmetros, é uma função pura". O que acontece no caso de constantes? Se eu tiver uma função areaOfCircle r => Math.Pi * r * r, será areaOfCirclenão pura, pois não usa apenas parâmetros?
David Arno
2
@DavidArno Esse é um ponto justo. De acordo com a transparência referencial, referir-se a um valor externo estático não seria diferente de codificá-lo, portanto ainda seria puro.
Daenyth
1
"isso significa que é possível fazer uma função pura ter um comportamento impuro" - por definição, uma função pura não pode ter um comportamento impuro. Você está cometendo o erro de pensar que uma função f(f2)que invoca f2não depende transitivamente de algo em que se f2baseia. Uma função que pode invocar funções passadas arbitrárias não é pura.
User2357112 suporta Monica
2
@ Daenyth: Melhor, mas ainda pressupõe que a função precise invocar a função transmitida. Pode ser algo parecido function compose(f, g) {return function h(x) {return f(g(x));};}, o que é puro, apesar de ter funções como argumentos.
User2357112 suporta Monica
1
"Acho raro que funções impuras sejam usadas como argumentos de função para funções puras". - não é uma linguagem funcional, mas certas funções da biblioteca C ++ têm avisos específicos que predicam argumentos devem ser (alguns aproximados) puros. Então, em certo sentido, isso significa que não é apenas raro, mas nunca acontece validamente. Mas, em outro sentido, o fato de que eles têm que proibir isso é porque as pessoas às vezes querem fazê-lo. Por exemplo, eles querem passar de findrotina um predicado impuro que retorne "true" para o terceiro item correspondente que encontrar, ou algo parecido.
21715 Steve Jobs (
19

Como a pureza de um parâmetro de entrada é desconhecida até o tempo de execução, uma função é imediatamente considerada impura se ela assume uma função como parâmetro de entrada?

Não. Contra-exemplo:

function pure(other_function) {
    return 1;
}

Não importa se other_functioné uma função pura, uma função impura ou não uma função. A purefunção é pura.

Outro contra-exemplo:

function identity(x) {
    return x;
}

Esta função é pura, mesmo que xseja uma função impura. identity(impure_function)sempre retornará impure_function, não importa quantas vezes você repita a chamada. Não importa se identity(impure_function)()sempre retorna a mesma coisa; o valor de retorno de uma função não afeta sua pureza.


Em geral, se uma função pode chamar uma função que foi passada como argumento, não é pura. Por exemplo, uma função function call(f) {f();}não é pura, porque, embora não faça menção a nenhum estado global ou mutável, fpode ser algo assim alertque causa efeitos colaterais visíveis.

Se uma função aceita funções como argumentos, mas não as chama ou faz com que sejam chamadas, pode ser pura. Ainda pode ser impuro se fizer alguma outra coisa impura. Por exemplo, function f(ignored_function) {alert('This isn't pure.');}é impuro, mesmo que nunca ligue ignored_function.

user2357112 suporta Monica
fonte
4
Essa resposta parece excessivamente pedante. Podemos deduzir da pergunta que a preocupação é com o parâmetro de função que está sendo chamado. A existência de funções que podem / tomam outras funções como parâmetro sem invocá-las não afeta essa questão.
walpen
13
@walpen: A questão não faz menção de invocar o argumento. Não há razão para supor que o interlocutor tenha percebido que uma função pode ter outra função como entrada sem invocá-la. É importante apontar suposições ocultas como essa, em vez de apenas assumir que você deveria assumi-las.
User2357112 suporta Monica
12

Como a pureza de um parâmetro de entrada é desconhecida até o tempo de execução, uma função é imediatamente considerada impura se ela assume uma função como parâmetro de entrada?

Tecnicamente, sim, a menos que haja alguma maneira no seu idioma para garantir que a função de entrada também seja pura.

se uma função aplica uma função pura definida fora da função, mas não é passada como parâmetro, ainda é pura se ela atender aos critérios de não ter efeitos colaterais e a saída depender apenas da entrada?

Sim. Então, vamos nos concentrar no que importa aqui. Chamar uma função pura ou não não é, por si só, útil. As funções puras são úteis porque produzir a mesma saída para qualquer entrada e não depender do estado ou causar efeitos colaterais é um conjunto de propriedades muito útil. Isso significa que, uma vez executada sua função, você pode "lembrar" a resposta para essa entrada e ela sempre será verdadeira. Você também não precisa executar a função novamente para gerar efeitos colaterais. E você pode executar essa função em paralelo (ou fora de ordem) com outras funções e saber que elas não terão nenhuma interação oculta que se comporte mal.

Essas propriedades úteis ainda são válidas se a função usar outras funções somente leitura puras para executar seu trabalho, independentemente de como as referencie.

Telastyn
fonte
5

Como Telastyn disse: Tecnicamente, sim, a menos que haja alguma maneira no seu idioma para garantir que a função de entrada também seja pura.

Isso não é hipotético, existem realmente boas maneiras de garantir isso. Pelo menos em uma linguagem fortemente tipada.

Uma função ~ tão pura que você escreveria em JavaScript como

function foo(f) {
   return f(1) + 2;
}

pode ser traduzido diretamente para Haskell:

foo :: (Int -> Int) -> Int
foo f = f 1 + 2

Agora, em JavaScript, você pode fazer coisas más, como

js> foo (function(x) {console.log("muharhar"); return 0})
muharhar
2

Isso não é possível em Haskell . A razão é que algo como efeito colateral console.log()deve sempre ter um tipo de resultado IO something, não apenas somethingsozinho.

GHCi> foo (\x -> print "muarhar" >> return 0)

<interactive>:7:12:
    Couldn't match expected type ‘Int’ with actual type ‘IO b0’
    In the expression: print "muarhar" >> return 0
    In the first argument of ‘foo’, namely
      ‘(\ x -> print "muarhar" >> return 0)’
    In the expression: foo (\ x -> print "muarhar" >> return 0)

Para essa expressão digitar, precisaríamos dar fooa assinatura de tipo

foo :: (Int -> IO Int) -> Int

Mas acontece que não posso mais implementá-lo: como a função argumento tem IOcomo resultado, não posso usá-la dentro foo.

<interactive>:8:44:
    Couldn't match expected type ‘Int’ with actual type ‘IO Int’
    In the first argument of ‘(+)’, namely ‘f 1’
    In the expression: f 1 + 2

A única maneira pela qual eu poderia usar uma IOação fooé se o resultado de footiver o IO Intpróprio tipo :

foo :: (Int -> IO Int) -> IO Int
foo f = do
   f1 <- f 1
   return (f1 + 2)

Mas, neste momento, fica claro pela assinatura fooque também não é uma função pura.

leftaroundabout
fonte
1
Antes de você dizer "impossível", dê uma olhada em unsafeIO:-)
Bergi
2
@ Bergi: na verdade, isso não faz parte do Haskell, mas de sua interface de função externa: permitir afirmar que uma função definida em alguma outra linguagem é pura, que o compilador Haskell obviamente não pode deduzir da assinatura de tipo, porque outras linguagens geralmente não têm coisa como IO. Aliás, também pode ser usado para causar confusão, ocultando efeitos colaterais em uma função "pura", mas isso é realmente inseguro em Haskell, porque não há realmente uma maneira confiável de especificar a ordem de avaliação das funções puras.
leftaroundabout
Sim, é verdade. No entanto, acho que já vi isso sendo usado para "ocultar" os efeitos colaterais benéficos em uma função "pura", onde a segurança da abordagem tinha que ser estabelecida antes.
Bergi 24/10/2015
@ Bergi Você na maioria das vezes não deveria estar usando unsafeIO; é uma escotilha de escape de último recurso que contorna as garantias do sistema de tipos e, portanto, o seu não é um bom ponto.
Andrés F.
0

Não não é.

Se a função passada for impura E sua função chamar a função passada, sua função será considerada impura.

A relação pura / impura é um pouco como sync / async em JS. Você pode usar livremente código puro de impuro, mas não o contrário.

Bobby Marinoff
fonte
Essa resposta não adiciona qualquer coisa que não foi já explicado em um presente ... Por favor, reveja respostas anteriores antes de reafirmar as coisas :)
Andres F.
E a analogia sync / async?
Bobby Marinoff 03/03