Eu vi isso em paradigmas imperativos
f (x) + f (x)
pode não ser o mesmo que:
2 * f (x)
Mas, em um paradigma funcional, deve ser o mesmo. Eu tentei implementar os dois casos em Python e Scheme , mas para mim eles parecem bem simples.
Qual seria um exemplo que poderia apontar a diferença com a função fornecida?
f(x++)+f(x++)
pode não ser o mesmo que2*f(x++)
(? em C é especialmente agradável quando coisas como o que está escondido dentro de macros - fez I quebrou meu nariz em que você aposta)f(x++)+f(x++)
pode ser absolutamente qualquer coisa, já que está invocando um comportamento indefinido. Mas isso não está realmente relacionado à transparência referencial - o que não ajudaria nesta chamada, é 'indefinido' para funções referencialmente transparentes comosin(x++)+sin(x++)
também. Poderia ser 42, pode formatar seu disco rígido, poderia ter demônios voando para fora do nariz usuários ...Respostas:
A transparência referencial, referida a uma função, indica que você pode determinar o resultado da aplicação dessa função apenas observando os valores de seus argumentos. Você pode escrever funções transparentemente referenciais em qualquer linguagem de programação, por exemplo, Python, Scheme, Pascal, C.
Por outro lado, na maioria dos idiomas, você também pode escrever funções transparentes e não referenciais. Por exemplo, esta função Python:
não é referencialmente transparente, chamando de fato
e
produzirá valores diferentes, para qualquer argumento
x
. A razão para isso é que a função usa e modifica uma variável global; portanto, o resultado de cada chamada depende desse estado de alteração e não apenas do argumento da função.Haskell, uma linguagem puramente funcional, separa estritamente a avaliação da expressão na qual funções puras são aplicadas e sempre referencialmente transparentes, da execução da ação (processamento de valores especiais), que não é referencialmente transparente, ou seja, executar a mesma ação sempre que um resultado diferente.
Portanto, para qualquer função Haskell
e qualquer número inteiro
x
, é sempre verdade queUm exemplo de uma ação é o resultado da função de biblioteca
getLine
:Como resultado da avaliação da expressão, essa função (na verdade uma constante) produz primeiro um valor puro do tipo
IO String
. Valores desse tipo são valores como qualquer outro: você pode distribuí-los, colocá-los em estruturas de dados, compor usando funções especiais e assim por diante. Por exemplo, você pode fazer uma lista de ações da seguinte forma:As ações são especiais, pois você pode dizer ao tempo de execução do Haskell para executá-las escrevendo:
Nesse caso, quando o programa Haskell é iniciado, o tempo de execução percorre a ação vinculada
main
e a executa , possivelmente produzindo efeitos colaterais. Portanto, a execução da ação não é referencialmente transparente porque a execução da mesma ação duas vezes pode produzir resultados diferentes, dependendo do que o tempo de execução obtém como entrada.Graças ao sistema de tipos de Haskell, uma ação nunca pode ser usada em um contexto em que outro tipo é esperado e vice-versa. Portanto, se você deseja encontrar o comprimento de uma string, pode usar a
length
função:retornará 5. Mas se você quiser encontrar o comprimento de uma string lida no terminal, não poderá escrever
porque você recebe um erro de tipo:
length
espera uma entrada do tipo lista (e uma String é, de fato, uma lista), masgetLine
é um valor do tipoIO String
(uma ação). Dessa maneira, o sistema de tipos garante que um valor de ação comogetLine
(cuja execução é executada fora da linguagem principal e que pode ser transparente não referencialmente) não possa ser oculto dentro de um valor de tipo não-açãoInt
.EDITAR
Para responder à pergunta anterior, aqui está um pequeno programa Haskell que lê uma linha do console e imprime seu comprimento.
A ação principal consiste em duas subações que são executadas sequencialmente:
getline
de tipoIO String
,putStrLn
do tipoString -> IO ()
em seu argumento.Mais precisamente, a segunda ação é construída por
line
ao valor lido pela primeira ação,length
(calcular o comprimento como um número inteiro) e depoisshow
(transformar o número inteiro em uma sequência de caracteres),putStrLn
ao resultado deshow
.Nesse ponto, a segunda ação pode ser executada. Se você digitou "Olá", ele imprimirá "5".
Observe que, se você obtiver um valor de uma ação usando a
<-
notação, você só poderá usar esse valor dentro de outra ação, por exemplo, você não poderá escrever:porque
show (length line)
tem typeString
enquanto a notação requer que uma ação (getLine
do tipoIO String
) seja seguida por outra ação (por exemplo,putStrLn (show (length line))
do tipoIO ()
).EDIT 2
A definição de transparência referencial de Jörg W. Mittag é mais geral que a minha (eu votei positivamente sua resposta). Eu usei uma definição restrita porque o exemplo da pergunta se concentra no valor de retorno das funções e queria ilustrar esse aspecto. No entanto, RT geralmente se refere ao significado de todo o programa, incluindo alterações no estado global e interações com o ambiente (IO) causadas pela avaliação de uma expressão. Portanto, para uma definição geral correta, você deve consultar essa resposta.
fonte
IO
tipo de Haskell facilmente em qualquer idioma com lambdas e genéricos, mas como qualquer pessoa pode ligarprintln
diretamente, a implementaçãoIO
não garante pureza; seria apenas uma convenção.getLine
não ser referencialmente transparente está incorreto. Você está apresentandogetLine
como se ele avalia ou se reduz a alguma String, cuja String específica depende da entrada do usuário. Isto está incorreto.IO String
não contém uma String mais do que contémMaybe String
.IO String
é uma receita para talvez, possivelmente obter uma String e, como expressão, tão pura quanto qualquer outra em Haskell.No entanto, não é isso que significa transparência referencial. RT significa que você pode substituir qualquer expressão no programa pelo resultado da avaliação dessa expressão (ou vice-versa) sem alterar o significado do programa.
Tome, por exemplo, o seguinte programa:
Este programa é referencialmente transparente. Posso substituir uma ou ambas as ocorrências de
f()
com2
e ainda funcionará da mesma maneira:ou
ou
todos irão se comportar da mesma maneira.
Bem, na verdade, eu traí. Eu deveria ser capaz de substituir a chamada
print
por seu valor de retorno (que não é de todo valor) sem alterar o significado do programa. No entanto, claramente, se eu remover as duasprint
instruções, o significado do programa mudará: antes, ele imprimia algo na tela, depois não o fazia. A E / S não é referencialmente transparente.A regra simples é: se você pode substituir qualquer chamada de expressão, sub-expressão ou sub-rotina pelo valor de retorno dessa chamada de expressão, sub-expressão ou sub-rotina em qualquer lugar do programa, sem que o programa mude seu significado, você tem referências transparência. E o que isso significa, na prática, é que você não pode ter nenhuma E / S, não pode ter nenhum estado mutável, não pode ter efeitos colaterais. Em toda expressão, o valor da expressão deve depender apenas dos valores das partes constituintes da expressão. E em toda chamada de sub-rotina, o valor de retorno deve depender apenas dos argumentos.
fonte
print
exemplo. Talvez uma maneira de ver isso é que o que é impresso na tela faz parte do "valor de retorno". Se você pode substituirprint
com seu valor de retorno de função e a gravação equivalente no terminal, o exemplo funciona.4
e2 + 2
não intercambiável, pois eles têm diferentes tempos de execução, e o ponto principal da transparência referencial é que você pode substituir uma expressão pelo que ela avaliar. A consideração importante seria a segurança do thread.listOfSequence.append(n)
retornaNone
, portanto, você poderá substituir todas as chamadaslistOfSequence.append(n)
porNone
sem alterar o significado do seu programa. Você pode fazer aquilo? Caso contrário, não é referencialmente transparente.Partes desta resposta são obtidas diretamente de um tutorial inacabado de programação funcional , hospedado na minha conta do GitHub:
Considere um exemplo simples:
Em uma linguagem funcional pura, o lado esquerdo e o lado direito do sinal de igual são substituíveis um pelo outro nos dois sentidos. Ou seja, diferente de uma linguagem como C, a notação acima realmente afirma uma igualdade. Uma conseqüência disso é que podemos raciocinar sobre o código do programa como equações matemáticas.
Do wiki Haskell :
Para contrastar, o tipo de operação executada por linguagens do tipo C às vezes é chamado de atribuição destrutiva .
O termo puro é frequentemente usado para descrever uma propriedade de expressões, relevante para esta discussão. Para que uma função seja considerada pura,
De acordo com a metáfora da caixa preta, encontrada em numerosos livros matemáticos, os internos de uma função são completamente isolados do mundo exterior. Um efeito colateral é quando uma função ou expressão viola esse princípio - ou seja, é permitido que o procedimento se comunique de alguma forma com outras unidades de programa (por exemplo, para compartilhar e trocar informações).
Em resumo, a transparência referencial é uma obrigação para que as funções se comportem como verdadeiras , funções matemáticas também na semântica das linguagens de programação.
fonte
[here](link to source)
...", seguido de 2) formatação de citação adequada (use aspas, ou, melhor ainda,>
símbolo para isso). Também não faria mal se além de dar uma orientação geral, os endereços de responder à pergunta concreta perguntado sobre, neste caso cerca def(x)+f(x)
/2*f(x)
, consulte Como responder - caso contrário, pode parecer que você está simplesmente anunciando sua página