Como eu entendo o modelo de substituição (com transparência referencial (RT)), você pode decompor uma função em suas partes mais simples. Se a expressão for RT, você poderá descompor a expressão e sempre obter o mesmo resultado.
Sim, a intuição está certa. Aqui estão algumas dicas para ser mais preciso:
Como você disse, qualquer expressão de RT deve ter um single
"resultado". Ou seja, dada uma factorial(5)
expressão no programa, ele sempre deve produzir o mesmo "resultado". Portanto, se um determinado factorial(5)
está no programa e gera 120, deve sempre gerar 120, independentemente de qual "ordem de etapas" é expandida / calculada - independentemente do tempo .
Exemplo: a factorial
função
def factorial(n):
if n == 1:
return 1
return n * factorial(n - 1)
Existem algumas considerações com esta explicação.
Antes de tudo, lembre-se de que os diferentes modelos de avaliação (consulte pedido versus pedido normal) podem gerar "resultados" diferentes para a mesma expressão RT.
def first(y, z):
return y
def second(x):
return second(x)
first(2, second(3)) # result depends on eval. model
No código acima, first
e second
são referencialmente transparentes e, no entanto, a expressão no final gera "resultados" diferentes se avaliados na ordem normal e na ordem do aplicativo (no último, a expressão não é interrompida).
.... o que leva ao uso de "resultado" entre aspas. Como não é necessário que uma expressão seja interrompida, ela pode não produzir um valor. Então, usar "resultado" é meio embaçado. Pode-se dizer que uma expressão RT sempre produz o mesmo computations
em um modelo de avaliação.
Terceiro, pode ser necessário ver duas foo(50)
aparecendo no programa em locais diferentes como expressões diferentes - cada uma produzindo seus próprios resultados que podem diferir entre si. Por exemplo, se a linguagem permitir escopo dinâmico, ambas as expressões, embora sejam lexicamente idênticas, serão diferentes. Em perl:
sub foo {
my $x = shift;
return $x + $y; # y is dynamic scope var
}
sub a {
local $y = 10;
return &foo(50); # expanded to 60
}
sub b {
local $y = 20;
return &foo(50); # expanded to 70
}
O escopo dinâmico confunde, porque facilita pensar que x
é a única entrada para foo
, quando, na realidade, é x
e y
. Uma maneira de ver a diferença é transformar o programa em um equivalente sem escopo dinâmico - ou seja, passando explicitamente os parâmetros, em vez de definir foo(x)
, definimos foo(x, y)
e passamos y
explicitamente nos chamadores.
O ponto é que estamos sempre com uma function
mentalidade: dada uma certa entrada para uma expressão, recebemos um "resultado" correspondente. Se dermos a mesma entrada, devemos sempre esperar o mesmo "resultado".
Agora, e o código a seguir?
def foo():
global y
y = y + 1
return y
y = 10
foo() # yields 11
foo() # yields 12
O foo
procedimento interrompe o RT porque há redefinições. Ou seja, definimos y
em um ponto e, posteriormente, redefinimos o mesmo y
. No exemplo perl acima, os y
s são ligações diferentes, embora compartilhem o mesmo nome da letra "y". Aqui os y
são realmente os mesmos. É por isso que dizemos que (re) atribuição é uma meta operação: na verdade, você está alterando a definição do seu programa.
Grosso modo, as pessoas geralmente descrevem a diferença da seguinte maneira: em um ambiente sem efeitos colaterais, você tem um mapeamento input -> output
. Em um cenário "imperativo", você tem input -> ouput
no contexto de um state
que pode mudar com o tempo.
Agora, em vez de apenas substituir expressões por seus valores correspondentes, também é necessário aplicar transformações ao state
em cada operação que requer (e, é claro, as expressões podem consultar o mesmo state
para realizar cálculos).
Portanto, se em um programa livre de efeitos colaterais tudo o que precisamos saber para calcular uma expressão é sua entrada individual, em um programa imperativo, precisamos conhecer as entradas e todo o estado, para cada etapa computacional. O raciocínio é o primeiro a sofrer um grande golpe (agora, para depurar um procedimento problemático, você precisa da entrada e do core dump). Certos truques são impraticáveis, como memorização. Mas também simultaneidade e paralelismo se tornam muito mais desafiadores.
RT
desabilita o uso dosubstitution model.
O grande problema de não poder usar osubstitution model
é o poder de usá-lo para raciocinar sobre um programa?