Pular para o conteúdo principal

1.3.2 Construindo Funções Usando Expressões Lambda

Ao usar sum como na seção 1.3.1, parece terrivelmente inconveniente ter que declarar funções triviais como pi_term e pi_next apenas para poder usá-las como argumentos para nossa função de ordem superior. Em vez de declarar pi_next e pi_term, seria mais conveniente ter uma forma de especificar diretamente "a função que retorna sua entrada incrementada em 4" e "a função que retorna o recíproco de sua entrada vezes sua entrada mais 2". Podemos fazer isso introduzindo a expressão lambda como uma forma sintática para criar funções. Usando expressões lambda, podemos descrever o que queremos como

Exemplo de Código
⛓ sessão — este bloco compartilha o ambiente da página: as declarações dos blocos anteriores são avaliadas antes deleCódigo editável · Ctrl+Enter executa · Esc solta o foco

e

Exemplo de Código
⛓ sessão — este bloco compartilha o ambiente da página: as declarações dos blocos anteriores são avaliadas antes deleCódigo editável · Ctrl+Enter executa · Esc solta o foco

Então podemos expressar nossa função pi_sum sem declarar nenhuma função auxiliar:

Exemplo de Código
Código editável · Ctrl+Enter executa · Esc solta o foco

Novamente, usando uma expressão lambda, podemos escrever a função integral sem ter que declarar a função auxiliar add_dx:

Exemplo de Código
Código editável · Ctrl+Enter executa · Esc solta o foco

Em geral, expressões lambda são usadas para criar funções da mesma forma que declarações de funções, exceto que nenhum nome é especificado para a função e a palavra-chave return e as chaves são omitidas (se houver apenas um parâmetro, os parênteses em torno da lista de parâmetros também podem ser omitidos, como nos exemplos que vimos).1

(parâmetros) => expressão

A função resultante é tão uma função quanto uma que é criada usando uma instrução de declaração de função. A única diferença é que ela não foi associada a nenhum nome no ambiente. Consideramos

Exemplo de Código
Código editável · Ctrl+Enter executa · Esc solta o foco

como sendo equivalente a2

Exemplo de Código
Código editável · Ctrl+Enter executa · Esc solta o foco

Podemos ler uma expressão lambda da seguinte forma:

x => x + 4
// ^ ^ ^ ^
// parâmetro x que retorna x mais 4

Como qualquer expressão que tem uma função como seu valor, uma expressão lambda pode ser usada como a expressão de função em uma aplicação, como em

Exemplo de Código
⛓ sessão — este bloco compartilha o ambiente da página: as declarações dos blocos anteriores são avaliadas antes deleCódigo editável · Ctrl+Enter executa · Esc solta o foco

ou, de forma mais geral, em qualquer contexto onde normalmente usaríamos um nome de função.3

Usando const para criar nomes locais

Outra aplicação de expressões lambda é na criação de nomes locais. Frequentemente precisamos de nomes locais em nossas funções além daqueles que foram vinculados como parâmetros. Por exemplo, suponha que desejamos calcular a função

f(x,y)=x(1+xy)2+y(1y)+(1+xy)(1y)f(x,y) = x(1+xy)^2 + y(1-y) + (1+xy)(1-y)

que também poderíamos expressar como

a=1+xyb=1yf(x,y)=xa2+yb+ab\begin{array}{lll} a &=& 1+xy\\ b &=& 1-y\\ f(x,y) &=& xa^2+yb+ab \end{array}

Ao escrever uma função para calcular ff, gostaríamos de incluir como nomes locais não apenas xx e yy mas também os nomes de quantidades intermediárias como aa e bb. Uma maneira conveniente de declarar nomes locais é usando const dentro do corpo da função. Usando const, a função pode ser escrita como

Exemplo de Código
Código editável · Ctrl+Enter executa · Esc solta o foco

Nomes que são declarados com const dentro de um bloco têm o corpo do bloco imediatamente envolvente como seu escopo.45

Instruções condicionais

Vimos que muitas vezes é útil declarar nomes que são locais para declarações de funções. Quando as funções ficam grandes, devemos manter o escopo dos nomes o mais restrito possível. Considere por exemplo expmod no exercício 1.26.

Exemplo de Código
Código editável · Ctrl+Enter executa · Esc solta o foco

Esta função é desnecessariamente ineficiente, porque contém duas chamadas idênticas:

expmod(base, exp / 2, m);

Embora isso possa ser facilmente corrigido neste exemplo usando a função square, isso não é tão fácil em geral. Sem usar square, seríamos tentados a introduzir um nome local para a expressão da seguinte forma:

function expmod(base, exp, m) {
const half_exp = expmod(base, exp / 2, m);
return exp === 0
? 1
: is_even(exp)
? (half_exp * half_exp) % m
: (base * expmod(base, exp - 1, m)) % m;
}

Isso tornaria a função não apenas ineficiente, mas na verdade não terminante! O problema é que a declaração de constante aparece fora da expressão condicional, o que significa que ela é executada mesmo quando o caso base exp === 0 é atendido. Para evitar esta situação, fornecemos instruções condicionais e permitimos que instruções de retorno apareçam nos ramos da instrução. Usando uma instrução condicional, podemos escrever a função expmod da seguinte forma:

Exemplo de Código
Código editável · Ctrl+Enter executa · Esc solta o foco

A forma geral de uma instrução condicional é

if (predicado) { bloco-consequente } else { bloco-alternativo }

Como nas expressões condicionais, o predicado é avaliado primeiro. Se ele for averdadeiro, o interpretador avalia o bloco-consequente (a sequência de instruções entre as chaves depois do predicado), e se ele for falso, o interpretador avalia o bloco-alternativo (a sequência de instruções entre as chaves depois de else).6

Observe que qualquer declaração de constante que ocorra em qualquer um dos blocos tem o respectivo bloco como seu escopo. Por exemplo, no expmod acima, o half_exp no consequente só é visível lá.

Exercício 1.34

Suponha que declaramos

Exemplo de Código
Código editável · Ctrl+Enter executa · Esc solta o foco

Então temos

Exemplo de Código
⛓ sessão — este bloco compartilha o ambiente da página: as declarações dos blocos anteriores são avaliadas antes deleCódigo editável · Ctrl+Enter executa · Esc solta o foco
Exemplo de Código
⛓ sessão — este bloco compartilha o ambiente da página: as declarações dos blocos anteriores são avaliadas antes deleCódigo editável · Ctrl+Enter executa · Esc solta o foco

O que acontece se (perversamente) pedirmos ao interpretador para avaliar a aplicação f(f)? Explique.

💡 Mostrar solução — tente primeiro!💡 Esconder solução

Avaliar f(f) pelo modelo de substituição:

f(f)
f(2) // g é f, então g(2) é f(2)
2(2) // agora g é 2, e tentamos aplicar 2 como função

O interpretador tenta aplicar o número 2 como se fosse uma função — e isso é um erro de tipo. Em JavaScript a mensagem é algo como TypeError: g is not a function. Experimente no bloco acima: acrescente f(f); e execute.

A pergunta é boa porque força a seguir a substituição mecanicamente, sem intuição salvadora: nada quebra no primeiro passo — f é um argumento perfeitamente válido — e o erro só aparece quando o 2 chega à posição de função.


Notas de Rodapé

1 Na seção 2.1.3, estenderemos a sintaxe de expressões lambda para permitir um bloco como corpo em vez de apenas uma expressão, como nas instruções de declaração de função.

2 Em JavaScript, existem diferenças sutis entre as duas versões: Uma instrução de declaração de função é automaticamente "içada" (hoisted) (movida) para o início do bloco envolvente ou para o início do programa se ocorrer fora de qualquer bloco, enquanto uma declaração de constante não é movida. Nomes declarados com declaração de função podem ser reatribuídos usando atribuição (seção 3.1.1), enquanto nomes declarados com declarações de constante não podem. Neste livro, evitamos esses recursos e tratamos uma declaração de função como equivalente à declaração de constante correspondente.

3 Entender definições internas bem o suficiente para ter certeza de que um programa significa o que pretendemos requer um modelo mais elaborado do processo de avaliação do que apresentamos neste capítulo. No entanto, as sutilezas não surgem com definições internas de funções. Voltaremos a este problema nas seções 3.2.4 e 4.1.6, depois de aprendermos mais sobre a avaliação de blocos.

4 Observe que um nome declarado em um bloco não pode ser usado antes que a declaração seja totalmente avaliada, independentemente de o mesmo nome ser declarado fora do bloco. Assim, no programa abaixo, a tentativa de usar o a declarado no nível superior para fornecer um valor para o cálculo do b declarado em f não pode funcionar.

const a = 1;
function f(x) {
const b = a + x;
const a = 5;
return a + b;
}
f(10);

O programa leva a um erro, porque o a em a + x é usado antes que sua declaração seja avaliada. Retornaremos a este programa na seção 4.1.6 (exercício 4.19), depois que aprendermos mais sobre avaliação.

5 O modelo de substituição pode ser expandido para dizer que, para uma declaração de constante, o valor da expressão depois de = é substituído pelo nome antes de = no resto do corpo do bloco (após a declaração), semelhante à substituição de argumentos por parâmetros na avaliação de uma aplicação de função.

6 O nome "lambda" é usado porque a notação de expressão lambda tem suas raízes no lambda calculus, uma linguagem formal matemática introduzida pelo lógico matemático Alonzo Church (1941). Church desenvolveu o lambda calculus para fornecer uma base matemática rigorosa para estudar as noções de função e aplicação de função. O lambda calculus se tornou uma ferramenta básica para pesquisa matemática em semântica de linguagens de programação.

📝 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

Reporte um bug →

❓ 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

Inicie uma discussão →

💡 Tem uma sugestão de melhoria?

Se você quer sugerir:

  • Melhoria na explicação
  • Exemplo adicional
  • Recurso visual (diagrama, ilustração)
  • Qualquer outra ideia

Sugira uma melhoria →

🌍 Quer discutir a tradução?

Se você quer debater:

  • Escolha de tradução de algum termo
  • Consistência de terminologia
  • Nuances do português

Discussão de tradução →

Obrigado por ajudar a melhorar o SICP.js PT-BR! ✨