4.1.6 Declarações Internas
Em JavaScript, o escopo de uma declaração é o bloco inteiro que imediatamente envolve a declaração, não apenas a porção do bloco começando no ponto onde a declaração ocorre. Esta seção examina mais de perto essa escolha de design.
Vamos revisitar o par de funções mutuamente recursivas is_even e is_odd da Seção 3.2.3, declaradas localmente no corpo de uma função f.
function f(x) {
function is_even(n) {
return n === 0
? true
: is_odd(n - 1);
}
function is_odd(n) {
return n === 0
? false
: is_even(n - 1);
}
return is_even(x);
}
Nossa intenção aqui é que o nome is_odd no corpo da função is_even deve se referir à função is_odd que é declarada após is_even. O escopo do nome is_odd é o bloco corpo inteiro de f, não apenas a porção do corpo de f começando no ponto onde a declaração de is_odd ocorre. De fato, quando consideramos que is_odd é ela mesma definida em termos de is_even—de modo que is_even e is_odd são funções mutuamente recursivas—vemos que a única interpretação satisfatória das duas declarações é considerá-las como se os nomes is_even e is_odd estivessem sendo adicionados ao ambiente simultaneamente. Mais geralmente, na estrutura de bloco, o escopo de um nome local é o bloco inteiro no qual a declaração é avaliada.
A avaliação de blocos no avaliador metacircular da seção 4.1.1 alcança tal escopo simultâneo para nomes locais escaneando as declarações no bloco e estendendo o ambiente atual com um frame contendo vinculações para todos os nomes declarados antes de avaliar as declarações. Assim, o novo ambiente no qual o corpo do bloco é avaliado já contém vinculações para is_even e is_odd, e qualquer ocorrência de um desses nomes refere-se à vinculação correta. Uma vez que suas declarações são avaliadas, esses nomes são vinculados aos seus valores declarados, ou seja, objetos de função que têm o ambiente estendido como sua parte de ambiente. Assim, por exemplo, quando is_even é aplicado no corpo de f, seu ambiente já contém a vinculação correta para o símbolo is_odd, e a avaliação do nome is_odd no corpo de is_even recupera o valor correto.
Exercício 4.16
Considere a função f_3 da seção 1.3.2:
function f_3(x, y) {
const a = 1 + x * y;
const b = 1 - y;
return x * square(a) + y * b + a * b;
}
-
Desenhe um diagrama do ambiente em vigor durante a avaliação da expressão de retorno de
f_3. -
Ao avaliar uma aplicação de função, o avaliador cria dois frames: um para os parâmetros e um para os nomes declarados diretamente no bloco corpo da função, em oposição a um bloco interno. Uma vez que todos esses nomes têm o mesmo escopo, uma implementação poderia combinar os dois frames. Altere o avaliador de modo que a avaliação do bloco corpo não crie um novo frame. Você pode assumir que isso não resultará em nomes duplicados no frame (o exercício 4.17 justifica isso).
💡 Mostrar solução — tente primeiro!💡 Esconder solução
1. O diagrama. A aplicação f_3(3, 4) (digamos) cria dois frames encadeados:
global (square, f_3, …)
↑
E1: x = 3, y = 4 (frame dos parâmetros)
↑
E2: a = 13, b = -3 (frame do bloco corpo)
A expressão de retorno é avaliada em E2, enxergando a e b ali e x, y em E1.
2. Combinar os frames. Em apply, em vez de avaliar o corpo como um bloco (que estende o ambiente com um frame novo), avalia-se a sequência interna do bloco diretamente no ambiente dos parâmetros, com os nomes locais varridos para o mesmo frame:
// no apply, para funções compostas:
const body = function_body(fun);
const locals = scan_out_declarations(body);
const values = list_of_unassigned(locals);
const program_env = extend_environment(
append(parameters(fun), locals),
append(args, values),
function_environment(fun));
evaluate_sequence(block_body_statements(body), program_env);
Um frame a menos por aplicação — válido porque parâmetros e locais diretos têm o mesmo escopo, e o exercício 4.17 garante que não há duplicatas.
Exercício 4.17
Eva Lu Ator está escrevendo programas nos quais declarações de função e outras declarações estão intercaladas. Ela precisa ter certeza de que as declarações são avaliadas antes que as funções sejam aplicadas. Ela reclama: "Por que o avaliador não pode cuidar dessa tarefa e içar todas as declarações de função para o início do bloco em que aparecem? Declarações de função fora de blocos devem ser içadas para o início do programa."
-
Modifique o avaliador seguindo a sugestão de Eva.
-
Os designers do JavaScript decidiram seguir a abordagem de Eva. Discuta esta decisão.
-
Além disso, os designers do JavaScript decidiram permitir que o nome declarado por uma declaração de função seja reatribuído usando atribuição. Modifique sua solução de acordo e discuta esta decisão.
💡 Mostrar solução — tente primeiro!💡 Esconder solução
1. Içar declarações de função. Na avaliação de um bloco, além de varrer os nomes declarados (que já viram "*unassigned*"), o avaliador percorre a sequência, separa as declarações de função e as avalia primeiro, na ordem em que aparecem, antes das demais declarações:
function eval_block(component, env) {
const body = block_body(component);
const locals = scan_out_declarations(body);
const new_env = extend_environment(
locals, list_of_unassigned(locals), env);
const fun_decls = filter(is_function_declaration, body_statements(body));
for_each(decl => evaluate(decl, new_env), fun_decls);
return evaluate_sequence(
filter(s => ! is_function_declaration(s), body_statements(body)),
new_env);
}
2. A decisão. O hoisting deixa Eva chamar funções antes do ponto do texto em que foram declaradas — conveniente para organizar programas com as definições auxiliares no fim, e essencial para funções mutuamente recursivas intercaladas com outras declarações. O custo é que o programa deixa de poder ser lido de cima para baixo como uma sequência: a ordem textual e a ordem de avaliação divergem, e um leitor pode se surpreender ("de onde veio esse nome?"). É uma troca deliberada de simplicidade semântica por ergonomia.
3. Permitir reatribuição. Basta que a varredura registre nomes de declaração de função como variáveis (como let), não como constantes — atribuições posteriores encontram um vínculo mutável. A decisão é discutível: torna possível o idioma de sobrescrever uma função (útil para instrumentação, como o trace deste site!), mas abre a porta para um nome de função significar coisas diferentes em momentos diferentes — mais uma dose do veneno da atribuição do capítulo 3, agora aplicada às próprias funções.
Exercício 4.18
Funções recursivas são obtidas de uma maneira indireta em nosso interpretador: Primeiro declare o nome que se referirá à função recursiva e atribua a ele o valor especial "*unassigned*"; depois defina a função recursiva no escopo desse nome; e finalmente atribua a função definida ao nome. Quando a função recursiva é aplicada, quaisquer ocorrências do nome no corpo se referem apropriadamente à função recursiva. Surpreendentemente, é possível especificar funções recursivas sem usar declarações ou atribuição. O programa a seguir calcula 10 fatorial aplicando uma função fatorial recursiva:1
💡 Mostrar solução — tente primeiro!💡 Esconder solução
O programa aplica o truque de passar a função para si mesma:
(n => (fact => fact(fact, n))
((ft, k) => k === 1 ? 1 : k * ft(ft, k - 1)))
(10);
Como funciona, de dentro para fora: a lambda (ft, k) => … não se refere a si mesma por nome — recebe a si própria como o argumento ft, e a chamada recursiva é ft(ft, k - 1): passa a função adiante de novo. A lambda fact => fact(fact, n) dá o pontapé, aplicando a função a ela mesma. Nenhum nome é declarado, nenhuma atribuição acontece — a recursão emerge só de aplicação de funções.
Experimente (funciona em JavaScript de verdade!):
É o embrião do operador Y do cálculo lambda: o padrão f(f) interno é o que os teóricos chamam de auto-aplicação, e generalizá-lo dá recursão para qualquer função sem suporte da linguagem.
(n => (fact => fact(fact, n))
((ft, k) => k === 1
? 1
: k * ft(ft, k - 1)))(10);
-
Verifique (avaliando a expressão) que isso realmente calcula fatoriais. Elabore uma expressão análoga para calcular números de Fibonacci.
-
Considere a função
fdada acima:
function f(x) {
function is_even(n) {
return n === 0
? true
: is_odd(n - 1);
}
function is_odd(n) {
return n === 0
? false
: is_even(n - 1);
}
return is_even(x);
}
Preencha as expressões faltantes para completar uma declaração alternativa de f, que não tem declarações internas de função:
function f(x) {
return ((is_even, is_odd) => is_even(is_even, is_odd, x))
((is_ev, is_od, n) => n === 0 ? true : is_od(??, ??, ??),
(is_ev, is_od, n) => n === 0 ? false : is_ev(??, ??, ??));
}
📝 Encontrou algo errado nesta página?
Sua ajuda é muito importante para melhorar a qualidade da tradução!
🐛 Encontrou um erro?
Se você encontrou:
- Erro de tradução (palavra incorreta, termo técnico errado)
- Erro de ortografia ou gramática
- Link quebrado
- Código de exemplo que não funciona
- Problema de formatação
❓ Tem uma dúvida?
Se você tem:
- Dúvida sobre o conteúdo desta seção
- Pergunta sobre um conceito do SICP
- Dificuldade em entender algum exemplo
- Questão sobre a tradução de algum termo
💡 Tem uma sugestão de melhoria?
Se você quer sugerir:
- Melhoria na explicação
- Exemplo adicional
- Recurso visual (diagrama, ilustração)
- Qualquer outra ideia
🌍 Quer discutir a tradução?
Se você quer debater:
- Escolha de tradução de algum termo
- Consistência de terminologia
- Nuances do português
Obrigado por ajudar a melhorar o SICP.js PT-BR! ✨