[[ch_with_match]]
== Instruções `with`, `match`, e blocos `else`
:example-number: 0
:figure-number: 0

[quote, Raymond Hettinger, um eloquente evangelista de Python]
____

Gerenciadores de contexto podem vir a ser quase tão importantes quanto a própria
sub-rotina. Só arranhamos a superfície das possibilidades. [...] Basic tem uma
instrução `with`, há instruções `with` em várias linguagens. Mas elas não fazem
a mesma coisa, todas fazem algo muito raso, economizam consultas a atributos com
o operador ponto (`.`), elas não configuram e desfazem ambientes. Não pense que
é a mesma coisa só porque o nome é igual. A instrução `with` é mais que
isso.footnote:[Palestra de abertura da PyCon US 2013: https://fpy.li/18-1["What
Makes Python Awesome" ("_O que torna Python incrível_")]; a parte sobre `with`
começa em 23:00 e termina em 26:15.]

____


Este((("with, match, and else blocks", "topics covered"))) capítulo é sobre
mecanismos de controle de fluxo não muito comuns em outras linguagens e que, por
essa razão, podem ser ignorados ou subutilizados em Python. São eles:

* A instrução `with` e o protocolo de gerenciamento de contexto
* A instrução `match/case` para casamento de padrões (_pattern matching_)
* A cláusula `else` nas instruções `for`, `while`, e `try`

A instrução `with` cria um contexto temporário e o desfaz com segurança, sob o
controle de um objeto gerenciador de contexto. Isso previne erros e reduz código
repetitivo, tornando as APIs ao mesmo tempo mais seguras e mais fáceis de usar.
Programadores Python estão encontrando muitos usos para blocos `with` além do
fechamento automático de arquivos.

Já estudamos casamento de padrões em capítulos anteriores, mas aqui veremos como a
gramática de uma linguagem de programação pode ser expressa como padrões de
sequências.
Por isso `match/case` é uma ferramenta eficiente para criar processadores de
linguagem fáceis de entender e de estender. Vamos examinar um interpretador
completo de subconjunto pequeno mas funcional da linguagem Scheme. As
mesmas ideias poderiam ser aplicadas no desenvolvimento de uma linguagem de
templates ou uma DSL (_Domain-Specific Language_, Linguagem de
Domínio Específico) para representar regras de negócio em um sistema maior.

A cláusula `else` não é grande coisa, mas ajuda a transmitir a intenção por trás
do código quando usada corretamente com as instruções `for`, `while` e `try`.

=== Novidades neste capítulo

A <<pattern_matching_case_study_sec>> é nova: um exemplo maior de casamento de padrões.

Também((("with, match, and else blocks", "significant changes to"))) atualizei a
<<context_utilities_sec>> para incluir alguns recursos do módulo `contextlib`
adicionados desde o Python 3.6, e os novos gerenciadores de contexto
agrupados entre parênteses, desde o Python 3.10.

Vamos começar com a poderosa instrução `with`.

[[context_managers_sec]]
=== Instrução `with` e gerenciadores de contexto

Objetos((("context managers", "purpose of")))((("with, match, and else blocks",
"context managers and with blocks", id="wmebcontextm18"))) gerenciadores de
contexto servem para controlar uma instrução `with`, da mesma forma que
iteradores servem para controlar uma instrução `for`.

A((("with, match, and else blocks", "purpose of with statements"))) instrução
`with` foi projetada para simplificar alguns usos comuns de `try/finally`, que
garantem que alguma operação seja realizada após um bloco de código, mesmo que o
bloco termine com um `return`, uma exceção, ou uma chamada `sys.exit()`. O
código no bloco `finally` normalmente libera um recurso crítico ou restaura um
estado anterior que havia sido temporariamente modificado.

A((("context managers", "creative uses for"))) comunidade Python
está encontrando novos usos criativos para gerenciadores de contexto.
Alguns exemplos, da biblioteca padrão, são:

* Gerenciar transações no módulo `sqlite3`—veja
https://fpy.li/a3[«Usando a conexão como gerenciador de contexto»].

* Manipular travas, condições e
semáforos de forma segura—como descrito na
https://fpy.li/9q[«documentação do módulo `threading`»].

* Configurar ambientes customizados para operações
aritméticas com objetos `Decimal`—veja a
https://fpy.li/9r[«documentação de `decimal.localcontext`»].

* Remendar (_patch_) objetos para testes—veja a função
https://fpy.li/9s[«`unittest.mock.patch`»].

A((("context managers",
"methods included in interface")))((("&#x005F;&#x005F;enter&#x005F;&#x005F;")))((("&#x005F;&#x005F;exit&#x005F;&#x005F;")))
interface gerenciador de contexto consiste dos métodos `+__enter__+` and
`+__exit__+`. No topo do `with`, Python chama o método `+__enter__+` do objeto
gerenciador de contexto. Quando o bloco `with` encerra ou termina por qualquer
razão, Python chama `+__exit__+` no objeto gerenciador de contexto.

O((("context managers", "demonstrations of", id="CMdemo18"))) exemplo mais comum
é garantir que um arquivo seja fechado. O <<with_file_demo>> é uma
demonstração detalhada do uso do `with` para fechar um arquivo.

[[with_file_demo]]
.Demonstração do uso de um objeto arquivo como gerenciador de contexto
====
[source, python]
----
>>> with open('mirror.py') as fp:  # <1>
...     src = fp.read(60)  # <2>
...
>>> len(src)
60
>>> fp  # <3>
<_io.TextIOWrapper name='mirror.py' mode='r' encoding='UTF-8'>
>>> fp.closed, fp.encoding  # <4>
(True, 'UTF-8')
>>> fp.read(60)  # <5>
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: I/O operation on closed file.
----
====

<1> `fp` está vinculado ao arquivo de texto aberto, pois o método `+__enter__+`
do arquivo devolve `self`.

<2> Lê `60` caracteres Unicode de `fp`.

<3> A variável `fp` continua disponível após o bloco;
uma instrução `with` não define um novo
escopo, como faz a instrução `def`.

<4> Podemos acessar atributos do objeto `fp`.

<5> Mas não podemos mais ler texto de `fp` pois, no final do bloco `with`, o
método `+TextIOWrapper.__exit__+` foi invocado, e este método fecha o arquivo.

A nota `①` no <<with_file_demo>> é sutil mas muito importante:
o objeto gerenciador de contexto é o resultado da avaliação da
expressão após o `with`, mas o valor vinculado à variável alvo (na cláusula
`as`) é o resultado devolvido pelo método `+__enter__+` do objeto gerenciador de
contexto.

Acontece que a função `open()` devolve uma instância de `TextIOWrapper`,
e o método `+__enter__+` dessa classe devolve `self`.
Mas em uma classe diferente, o método `+__enter__+` pode
devolver algum outro objeto em vez do próprio gerenciador de contexto.

Quando o fluxo de controle sai do bloco `with` de qualquer forma, o método
`+__exit__+` é invocado no objeto gerenciador de contexto, e não no que quer que
`+__enter__+` tenha devolvido.

A cláusula `as` da instrução `with` é opcional. No caso de `open`, sempre
precisamos obter uma referência para o arquivo, para podermos invocar seus
métodos. Mas alguns gerenciadores de contexto devolvem `None`, pois não têm
um objeto útil para entregar ao usuário.

O <<looking_glass_demo_1>> mostra o funcionamento de um gerenciador de contexto
perfeitamente frívolo, projetado para ressaltar a diferença entre o gerenciador
de contexto e o objeto devolvido por seu método `+__enter__+`.

[[looking_glass_demo_1]]
.Testando a classe gerenciadora de contexto `LookingGlass`
====
[source, python]
----
include::../code/18-with-match/mirror.py[tags=MIRROR_DEMO_1]
----
====

<1> O gerenciador de contexto é uma instância de `LookingGlass`; Python chama
`+__enter__+` no gerenciador de contexto e o resultado é vinculado a `what`.

<2> Exibe uma `str`, depois o valor da variável alvo `what`. A saída de cada
`print` é invertida.

<3> Agora o bloco `with` terminou. Podemos ver que o valor devolvido por
`+__enter__+`, armazenado em `what`, é a string `'JABBERWOCKY'`.

<4> A saída do programa não está mais invertida.


O <<looking_glass_ex>> mostra a implementação de `LookingGlass`.

[[looking_glass_ex]]
.mirror.py: código da classe gerenciadora de contexto `LookingGlass`
====
[source, python]
----
include::../code/18-with-match/mirror.py[tags=MIRROR_EX]
----
====

<1> Python invoca `+__enter__+` sem argumentos além de `self`.

<2> Armazena o método `sys.stdout.write`  original, para podermos restaurá-lo mais tarde.

<3> Faz um _monkey-patch_ em `sys.stdout.write`, substituindo-o com nosso próprio método.

<4> Devolve a string `'JABBERWOCKY'`, apenas para termos algo para colocar na variável alvo `what`.

<5> Nosso substituto de `sys.stdout.write` inverte o argumento `text` e chama a implementação original.

<6> Se tudo correu bem, Python chama `+__exit__+` com `None, None, None`; se
ocorreu uma exceção, os três argumentos recebem dados da exceção,
como descrito logo após este exemplo.

<7> Restaura o método original em `sys.stdout.write`.

<8> Se a exceção não é `None` e seu tipo é `ZeroDivisionError`, exibe uma mensagem...

<9> ...e devolve `True`, para informar o interpretador que a exceção foi tratada.

<10> Se `+__exit__+` devolve `None` ou qualquer valor _falso_, qualquer exceção
levantada dentro do bloco `with` será propagada.

[TIP]
====

Quando aplicações reais tomam o controle da saída padrão, elas frequentemente
desejam substituir `sys.stdout` com outro objeto similar a um arquivo por algum
tempo, depois voltar ao original. O gerenciador de contexto
https://fpy.li/18-6[`contextlib.redirect_stdout`] faz exatamente isso: passe a
ele seu objeto similar a um arquivo que substituirá `sys.stdout`.

====

O interpretador chama o método `+__enter__+` sem qualquer argumento—além do
`self` implícito. Os três argumentos passados a `+__exit__+` são:

`exc_type`:: A classe da exceção (por exemplo, `ZeroDivisionError`).

`exc_value`:: A instância da exceção. Algumas vezes, parâmetros passados para o
construtor da exceção—tal como a mensagem de erro—podem ser encontrados em
`exc_value.args`.

`traceback`:: Um objeto `traceback`.footnote:[Os três argumentos recebidos por
`self` são exatamente o que você obtém se chama
https://fpy.li/18-7[`sys.exc_info()`] no bloco `finally` de uma instrução
`try/finally`. Isso faz sentido, considerando que a instrução `with` tem por
objetivo substituir a maioria dos usos de `try/finally`, e invocar
`sys.exc_info()` é muitas vezes necessário para determinar que ação de limpeza é
necessária.]

Para uma visão detalhada de como funciona um gerenciador de contexto, vejamos o
<<looking_glass_demo_2>>, onde `LookingGlass` é usado fora de um bloco `with`,
de forma que podemos invocar manualmente seus métodos `+__enter__+` e
`+__exit__+`.

[[looking_glass_demo_2]]
.Exercitando o `LookingGlass` sem um bloco `with`
====
[source, python]
----
include::../code/18-with-match/mirror.py[tags=MIRROR_DEMO_2]
----
====
<1> Instancia e inspeciona a instância de `manager`.

<2> Chama o método `+__enter__+` do manager e guarda o resultado em `monster`.

<3> `monster` é a string `'JABBERWOCKY'`. O identificador `True` aparece
invertido, porque toda a saída via `stdout` passa pelo método `write`, que
modificamos em `+__enter__+`.

<4> Chama `+manager.__exit__+` para restaurar o `stdout.write` original.
((("",startref="CMdemo18")))


.Gerenciadores de contexto entre parênteses
[TIP]
====

Python((("context managers", "parenthesized in Python 3.10"))) 3.10 adotou
https://fpy.li/pep617[um novo parser] (analisador sintático),
mais poderoso que o antigo
https://fpy.li/18-8[parser LL(1)].
Isso permitiu introduzir novas sintaxes que não eram viáveis anteriormente.
Uma melhoria na sintaxe foi permitir gerenciadores de contexto agrupados
entre parênteses, assim:

[source, python]
----
with (
    CtxManager1() as example1,
    CtxManager2() as example2,
    CtxManager3() as example3,
):
    ...
----

Antes do 3.10, as linhas acima teriam que ser escritas como blocos `with`
aninhados.

====

A biblioteca padrão inclui o pacote `contextlib`, com funções, classes e
decoradores convenientes para desenvolver, combinar e usar gerenciadores
de contexto.

[[context_utilities_sec]]
==== Utilitários da `contextlib`

Antes((("context managers", "contextlib utilities"))) de desenvolver suas
próprias classes gerenciadoras de contexto, dê uma olhada em
https://fpy.li/9t[`contextlib`] 
(Utilitários para contextos da instrução `with`),
na documentação de Python.
Pode ser que você esteja prestes a escrever algo que já existe, ou talvez exista
uma classe ou algum invocável que tornará seu trabalho mais fácil.

Além do gerenciador de contexto `redirect_stdout` mencionado logo após o
<<looking_glass_ex>>, o `redirect_stderr` foi acrescentado no Python 3.5—ele faz
o mesmo que seu par mais antigo, mas com as saídas direcionadas para `stderr`.

O pacote `contextlib` também inclui:

`closing`:: Uma função para criar gerenciadores de contexto a partir de objetos
que forneçam um método `close()` mas não implementam a interface
`+__enter__/__exit__+`.

`suppress`:: Um gerenciador de contexto para ignorar temporariamente exceções
passadas como parâmetros.

`nullcontext`:: Um gerenciador de contexto que não faz nada, para simplificar a
lógica condicional em torno de objetos que podem não implementar um gerenciador
de contexto adequado. Ele serve como um substituto quando o código condicional
antes do bloco `with` pode ou não fornecer um gerenciador de contexto para a
instrução `with`. Adicionado no Python 3.7.

O módulo `contextlib` fornece classes e um decorador que são mais largamente
aplicáveis que os decoradores mencionados acima:

`@contextmanager`:: Um decorador que permite construir um gerenciador de
contexto a partir de uma simples função geradora, em vez de criar uma classe e
implementar a interface. Veja a <<using_cm_decorator_sec>>.

`AbstractContextManager`:: Uma ABC que formaliza a interface gerenciador de
contexto, e torna um pouco mais fácil criar classes gerenciadoras de contexto,
através de subclasses—adicionada no Python 3.6.

`ContextDecorator`:: Uma classe base para definir gerenciadores de contexto
baseados em classes que podem também ser usadas como decoradores de função,
rodando a função inteira dentro de um contexto gerenciado.

`ExitStack`:: Um gerenciador de contexto que permite entrar em um número
variável de gerenciadores de contexto. Quando o bloco ++with++ termina,
++ExitStack++ chama os métodos `+__exit__+` dos gerenciadores de contexto
empilhados na ordem LIFO (Last In, First Out, _Último a Entrar, Primeiro a
Sair_). Use essa classe quando você não sabe de antemão em quantos gerenciadores
de contexto será necessário entrar no bloco `with`; por exemplo, ao abrir ao
mesmo tempo todos os arquivos de uma lista arbitrária de arquivos.

Com Python 3.7, `contextlib` acrescentou `AbstractAsyncContextManager`,
`@asynccontextmanager`, e `AsyncExitStack`. Eles são similares aos utilitários
equivalentes sem a parte `async` no nome, mas projetados para uso com a nova
instrução `async with`, tratada no <<ch_async>>.

Entre estas ferramentas, a mais fácil de usar é o decorador
`@contextmanager`, então ele merece mais atenção. Este decorador também é
interessante por mostrar um uso da instrução `yield` não relacionado a iteração.

[[using_cm_decorator_sec]]
==== Usando o @contextmanager

O((("@contextmanager decorator", id="atcontextm18")))((("context managers",
"@contextmanager decorator", id="CMatcontextm18"))) decorador `@contextmanager`
é uma ferramenta elegante e prática, que une três recursos distintos de Python:
um decorador de função, um gerador, e a instrução `with`.

Usar o `@contextmanager` reduz o código repetitivo na criação de um gerenciador
de contexto: em vez de escrever toda uma classe com métodos
`+__enter__/__exit__+`, você só precisa implementar um gerador com uma única
instrução `yield`, que deve produzir o que o método `+__enter__+` deveria
devolver.

Em um gerador decorado com `@contextmanager`, o `yield` divide o corpo da função
em duas partes: tudo que vem antes do `yield` será executado no início do bloco
`with`, quando o interpretador chama `+__enter__+`; o código após o `yield` será
executado quando `+__exit__+` é invocado, no final do bloco.

O <<looking_glass_gen_ex>> substitui a classe `LookingGlass` do
<<looking_glass_ex>> por uma função geradora.

[[looking_glass_gen_ex]]
.mirror_gen.py: um gerenciador de contexto implementado com um gerador
====
[source, python]
----
include::../code/18-with-match/mirror_gen.py[tags=MIRROR_GEN_EX]
----
====
<1> Aplica o decorador `contextmanager`.
<2> Preserva o método `sys.stdout.write` original.
<3> `reverse_write` pode invocar `original_write` mais tarde,
pois ele está disponível em sua clausura (closure).
<4> Substitui `sys.stdout.write` por `reverse_write`.
<5> Produz o valor que será vinculado à variável alvo na cláusula `as` da
instrução `with`. O gerador é suspenso neste ponto, enquanto o corpo do `with` é
executado.
<6> Quando o fluxo de controle sai do bloco `with`, a execução continua após o
`yield`; neste ponto o `sys.stdout.write` original é restaurado.

O <<looking_glass_gen_demo>> mostra a função `looking_glass` em operação.

[[looking_glass_gen_demo]]
.Testando a função gerenciadora de contexto `looking_glass`
====
[source, python]
----
include::../code/18-with-match/mirror_gen.py[tags=MIRROR_GEN_DEMO_1]
----
====
<1> A única diferença do <<looking_glass_demo_1>> é o nome do gerenciador de
contexto:`looking_glass` em vez de `LookingGlass`.

O decorador `contextlib.contextmanager` envolve a função em uma classe que
implementa os métodos `+__enter__+` e `+__exit__+`.footnote:[A classe real se
chama `_GeneratorContextManager`. Se você quiser saber exatamente como ela
funciona, leia seu https://fpy.li/18-10[código-fonte] na __Lib/contextlib.py__
de Python 3.10.]

O método `+__enter__+` daquela classe:

. Invoca a função geradora para obter um objeto gerador—vamos chamá-lo de `gen`.
. Invoca `next(gen)` para executar o gerador até o `yield`.
. Devolve o valor produzido por `next(gen)`, para permitir que o usuário o
vincule a uma variável usando o cláusula `as` da instrução `with`.

Quando o bloco `with` termina, o método `+__exit__+`:

. Verifica se uma exceção foi passada no argumento `exc_type`; em caso afirmativo,
`gen.throw(exception)` é invocado, fazendo com que a exceção seja levantada
na posição do `yield`, dentro do corpo da função geradora.

. Caso contrário, `next(gen)` é invocado, retomando a execução do corpo da função
geradora após o `yield`.

<<<
O <<looking_glass_gen_ex>> tem um defeito:
se uma exceção for levantada no corpo do bloco `with`,
o interpretador Python vai capturá-la e levantá-la novamente na expressão
`yield` dentro de `looking_glass`. Mas não há tratamento de erro ali, então o
gerador `looking_glass` vai terminar sem nunca restaurar o método
`sys.stdout.write` original, deixando o sistema em um estado inconsistente.

O <<looking_glass_gen_exc_ex>> agora trata a exceção
`ZeroDivisionError`, tornando-o funcionalmente
equivalente ao <<looking_glass_ex>>, que é uma classe.

[[looking_glass_gen_exc_ex]]
.mirror_gen_exc.py: gerenciador de contexto baseado em gerador com tratamento de erro—mesmo comportamento de <<looking_glass_ex>>
====
[source, python]
----
include::../code/18-with-match/mirror_gen_exc.py[tags=MIRROR_GEN_EXC]
----
====
<1> Cria uma variável para uma possível mensagem de erro; essa é a primeira mudança em relação a <<looking_glass_gen_ex>>.
<2> Trata `ZeroDivisionError`, fixando uma mensagem de erro.
<3> Desfaz o _monkey-patching_ de `sys.stdout.write`.
<4> Mostra a mensagem de erro, se ela foi determinada.

Lembre-se de que o método `+__exit__+` diz ao interpretador que ele tratou a
exceção ao devolver um valor _verdadeiro_; nesse caso, o interpretador suprime a
exceção.

Por outro lado, se `+__exit__+` não devolver explicitamente um valor, o
interpretador recebe o habitual `None`, e propaga a exceção. Com o
`@contextmanager`, o comportamento default é invertido: o método `+__exit__+`
fornecido pelo decorador assume que qualquer exceção enviada para o gerador está
tratada e deve ser suprimida.

[TIP]
====

Ter um `try/finally` (ou um bloco `with`) em torno do `yield` é o preço
inescapável do uso de `@contextmanager`, porque você nunca sabe o que os
usuários do seu gerenciador de contexto vão fazer dentro do bloco
`with`.footnote:[Essa dica é uma citação literal de um comentário de Leonardo
Rochael, um dos revisores técnicos desse livro. Muito bem colocado, Leo!]

====

Um recurso pouco conhecido do `@contextmanager` é que os geradores decorados com
ele também podem ser usados como decoradores.footnote:["Pouco conhecido"
porque pelo menos eu e os outros revisores técnicos não sabíamos disso até Caleb
Hattingh nos contar. Obrigado, Caleb!] Isso ocorre porque `@contextmanager` é
implementado com a classe `contextlib.ContextDecorator`.

O <<looking_glass_gen_deco_demo>> mostra o gerenciador de contexto
`looking_glass` do <<looking_glass_gen_ex>> sendo usado como um decorador.

[[looking_glass_gen_deco_demo]]
.O gerenciador de contexto `looking_glass` também funciona como um decorador.
====
[source, python]
----
include::../code/18-with-match/mirror_gen.py[tags=MIRROR_GEN_DECO]
----
====
<1> `looking_glass` faz seu trabalho antes e depois do corpo de `verse` rodar.
<2> Isso confirma que o `sys.write` original foi restaurado.

Compare o <<looking_glass_gen_deco_demo>> com o <<looking_glass_gen_demo>>, onde
`looking_glass` é usado como um gerenciador de contexto.

Um exemplo real interessante de `++@contextmanager++` fora da biblioteca
padrão é descrito em
https://fpy.li/18-11[_Easy in-place file rewriting_]
(Reescrita de arquivo no mesmo lugar]
de Martijn Pieters. O <<inplace_ex>> mostra como usar a
função `inplace` apresentada naquele artigo.

[[inplace_ex]]
.Um gerenciador de contexto para reescrever arquivos no lugar
====
[source, python]
----
import csv

with inplace(csvfilename, 'r', newline='') as (infh, outfh):
    reader = csv.reader(infh)
    writer = csv.writer(outfh)

    for row in reader:
        row += ['new', 'columns']
        writer.writerow(row)
----
====

A função `inplace` constrói um gerenciador de contexto que fornece a você duas
referências para o mesmo arquivo (`infh` e `outfh` no exemplo), permitindo
que seu código leia e escreva nele ao mesmo tempo. Isto é mais fácil de usar que
a https://fpy.li/9v[«função `fileinput.input`»] da biblioteca padrão (que, por
sinal, também fornece um gerenciador de contexto).

Se você quiser estudar o código-fonte do `inplace` de Martijn (listado no
https://fpy.li/18-11[«post»]), encontre a((("yield keyword")))((("keywords",
"yield keyword"))) palavra reservada `yield`: tudo antes dela lida com
configurar o contexto, que implica criar um arquivo de backup, então abrir e
produzir referências para os objetos de leitura e escrita que
serão devolvidos pela chamada a `+__enter__+`. O processamento do `+__exit__+`
após o `yield` fecha os objetos vinculados ao arquivo e, se algo deu errado,
restaura o arquivo do backup.

Isto conclui nossa revisão da instrução `with` e dos gerenciadores de contexto.

Vamos agora estudar a instrução `match/case` em um exemplo maior
do que aqueles que vimos quando falamos de casamenteo de padrões nos capítulos
anteriores.((("", startref="wmebcontextm18")))((("", startref="CMatcontextm18")))((("",
startref="atcontextm18")))

<<<
[[pattern_matching_case_study_sec]]
=== Estudo de caso: `match/case` no _lis.py_

Na((("lis.py interpreter", "topics covered")))((("with, match, and else blocks",
"pattern matching in lis.py", id="WMEBlispy18")))((("pattern matching", "in
lis.py interpreter", secondary-sortas="lis.py", id="PMlispy18")))
https://fpy.li/ck[«Seção 2.6.1»] (vol.1), vimos exemplos de sequências de padrões
extraídos da função `evaluate` do interpretador _lis.py_ de Peter Norvig,
portado para Python 3.10. Nesta seção quero apresentar uma visão geral
do _lis.py_, e também explorar todas as cláusulas `case` de `evaluate`,
explicando não apenas os padrões, mas também o que o interpretador faz em cada
`case`.

Além de mostrar mais casamento de padrões, escrevi essa seção por três razões:

. O _lis.py_ de Norvig é um lindo exemplo de código Python idiomático.
. A simplicidade do Scheme é uma aula magna de design de linguagens.
. Aprender como um interpretador funciona me deu um entendimento mais profundo
sobre Python e sobre linguagens de programação em geral—interpretadas ou
compiladas.

Antes de olhar o código Python, vamos ver um pouquinho de Scheme, para você
poder entender este estudo de caso—pensando em quem nunca viu Scheme e Lisp
antes.


==== A sintaxe do Scheme

No((("lis.py interpreter", "Scheme syntax", id="LPIschemesyn18")))((("Scheme
language", id="scheme18"))) Scheme não há diferença sintática entre expressões e
instruções (_statements_), como temos em Python. Também não existem operadores infixos. Todas
as expressões usam a notação prefixa, como `(+ x 13)` em vez de `x + 13`. A
mesma notação prefixa é usada para chamadas de função—por exemplo, `(gcd x
13)`—e formas especiais—por exemplo, `(define x 13)`, que em Python
escreveríamos como uma instrução de atribuição: `x = 13`.

A((("S-expression"))) notação usada no Scheme e na maioria dos dialetos de Lisp
é conhecida como _S-expression_ (expressão-S).footnote:[As pessoas reclamam
sobre o excesso de parênteses no Lisp, mas um bom
editor e a indentação consistente do código praticamente resolvem essa questão.
O maior problema de legibilidade é o
uso da mesma notação `(f ...)` para invocar funções e aplicar formas especiais como
`(define ...)`, `(if ...)` e `(quote ...)`, que têm comportamentos muito
diferentes de qualquer função.]

O <<ex_gcd_scheme>> mostra um exemplo simples em Scheme.

<<<
[[ex_gcd_scheme]]
.Maior divisor comum em Scheme
====
[source, scheme]
----
(define (mod m n)
    (- m (* n (quotient m n))))

(define (gcd m n)
    (if (= n 0)
        m
        (gcd n (mod m n))))

(display (gcd 18 45))
----
====

O <<ex_gcd_scheme>> mostra três expressões em Scheme:
duas definições de função—`mod` e `gcd`—e uma chamada a `display`,
que vai devolver 9, o resultado de `(gcd 18 45)`.
O <<ex_gcd_python>> é o mesmo código em Python (mais curto que a explicação em português do
https://fpy.li/9w[«algoritmo recursivo de Euclides»]).

[[ex_gcd_python]]
.Igual ao <<ex_gcd_scheme>>, mas escrito em Python
====
[source, python]
----
def mod(m, n):
    return m - (m // n * n)

def gcd(m, n):
    if n == 0:
        return m
    else:
        return gcd(n, mod(m, n))

print(gcd(18, 45))
----
====

Em Python idiomático, eu usaria o operador `%` em vez de reinventar `mod`, e
seria mais eficiente usar um laço `while` em vez de recursão. Minha intenção foi mostrar
duas definições de funções, e fazer os exemplos o mais similares possível, para
ajudar você a ler o código Scheme.

O Scheme não tem instruções de laço como `while` ou `for`.
Toda iteração é feita com recursão.
Observe que não há atribuições nos exemplos em Python e Scheme. O uso intensivo
de recursão e o uso mínimo de atribuição são marcas registradas do estilo
funcional de programação.footnote:[Para que a iteração por recursão seja prática e
eficiente, o Scheme e outras linguagens funcionais otimizam certas chamadas
recursivas. Para ler mais sobre isso, veja o
<<soapbox_with_match>>.]

Agora vamos revisar o código da versão Python 3.10 do _lis.py_.
O código-fonte completo, com testes, está no diretório
https://fpy.li/18-15[_18-with-match/lispy/py3.10/_],
do repositório https://fpy.li/code[_fluentpython/example-code-2e_].((("", startref="scheme18")))((("", startref="LPIschemesyn18")))


==== Importações e tipos

O <<lis_top_ex>> mostra((("lis.py interpreter", "imports and types"))) as
primeiras linhas do _lis.py_. O uso do `TypeAlias` e do operador de união de
tipos `|` exige Python 3.10.

[[lis_top_ex]]
.lis.py: início do arquivo
====
[source, python]
----
include::../code/18-with-match/lispy/py3.10/lis.py[tags=IMPORTS]
----
====

Os tipos definidos são:

`Symbol`:: Só um alias para `str`. Em _lis.py_, `Symbol` é usado para
identificadores; não há um tipo de dados string, com operações como fatiamento
(_slicing_), partição (_splitting_), etc.footnote:[Mas o segundo interpretador de
Norvig, https://fpy.li/18-16[_lispy.py_], suporta strings como um tipo de dado,
e também traz recursos avançados como macros sintáticas, continuações, e
chamadas de cauda otimizadas. Entretanto, o _lispy.py_ é quase três vezes maior
que o _lis.py_—é mais difícil de entender.]

`Atom`:: Um elemento sintático simples, tal como um número ou um `Symbol`—ao
contrário de uma estrutura composta, formada por vários elementos distintos,
como uma lista.

`Expression`:: Os componentes básicos de programas Scheme são expressões feitas
de átomos e listas, possivelmente aninhadas.

[[lispy_parser_sec]]
==== O parser

O parser (_analisador sintático_)((("lis.py interpreter", "parser",
id="lispyparser18"))) de Norvig tem 36 linhas de código que exibem o poder de
Python aplicado ao tratamento da sintaxe recursiva simples das expressões-S—sem
strings, comentários, macros e outros recursos que tornam a análise sintática do
Scheme padrão mais complicada (<<lis_parser_ex>>).

[[lis_parser_ex]]
.lis.py: as principais funções do analisador
====
[source, python]
----
include::../code/18-with-match/lispy/py3.10/lis.py[lines=27..36]
    # mais código do analisador omitido na listagem do livro
----
====

A principal função deste grupo é `parse`, que recebe uma expressão-S em forma de
`str` e devolve um objeto `Expression`, como definido no <<lis_top_ex>>: um
`Atom` ou uma `list` que pode conter mais átomos e listas aninhadas.

Norvig usa um truque elegante em `tokenize`: ele acrescenta espaços antes e
depois de cada parêntese na entrada, e então a particiona com `split`, resultando em uma lista
de _tokens_ (símbolos sintáticos) com `'('` e `')'` como itens separados. Este
atalho funciona porque não há um tipo string no pequeno Scheme de _lis.py_,
então todo `'('` ou `')'` é um delimitador de expressão. O código recursivo do
analisador está em `read_from_tokens`, uma função de 14 linhas que você pode ler
no repositório https://fpy.li/18-17[_fluentpython/example-code-2e_]. Vou pular
isso, pois quero me concentrar em outras partes do interpretador.

<<<
Aqui estão alguns doctests extraídos do https://fpy.li/18-18[_lispy/py3.10/examples_test.py_]:

[source, python]
----
include::../code/18-with-match/lispy/py3.10/examples_test.py[tags=PARSE]
----

As regras de avaliação deste subconjunto do Scheme são simples:

. Um símbolo sintático que se pareça com um número é tratado como um `float` ou
um `int`.

. Todo o resto que não seja um `'('` ou um `')'` é considerado um `Symbol`—uma
`str`, a ser usado como um identificador. Isso inclui texto no código-fonte como
`{plus}`, `set!`, e `make-counter`, que são três identificadores válidos em Scheme,
mas não em Python.

. Expressões dentro de `'('` e `')'` são avaliadas recursivamente como listas
contendo átomos ou listas aninhadas que podem conter átomos ou mais listas
aninhadas.

Usando a terminologia do interpretador Python, a saída de `parse` é uma AST
(_Abstract Syntax Tree_—Árvore Sintática Abstrata): uma representação
conveniente de um programa Scheme como listas aninhadas formando uma estrutura
similar a uma árvore, onde a lista mais externa é o tronco, listas internas são
os galhos, e os átomos são as folhas (<<ast_fig>>).((("",
startref="lispyparser18")))

[role="width-80"]
[[ast_fig]]
.Uma expressão `lambda` de Scheme, representada como código-fonte (sintaxe concreta de expressões-S), como uma árvore, e como uma sequência de objetos Python (sintaxe abstrata).
image::../images/flpy_1801.png[align="center",pdfwidth=10cm]

Agora veremos como é construído o ambiente (_environment_) que fornece
as definições usadas pelos programas dos usuários,
semelhante ao módulo `builtins` do Python.

[[lispy_environ_sec]]
==== O ambiente

A((("lis.py interpreter", "Environment class", id="lispyenv18"))) classe
`Environment` estende `collections.ChainMap`, acrescentando o método `change`,
para atualizar um valor dentro de um dos dicts encadeados que as instâncias de
`ChainMap` guardam no atributo `self.maps`, que é lista de mapeamentos. O método
`change` é necessário para suportar a instrução `(set! …)` do Scheme, descrita mais
tarde.

<<<
[[environment_class_ex]]
._lis.py_: a classe `Environment`
====
[source, python]
----
include::../code/18-with-match/lispy/py3.10/lis.py[tags=ENV_CLASS]
----
====

Observe que o método `change` só atualiza chaves existentes.footnote:[O
comentário `++# type: ignore[index]++` está ali por causa do issue
https://fpy.li/18-19[#6042] no _typeshed_, que segue sem resolução quando esse
capítulo está sendo revisado. `ChainMap` é anotado como `MutableMapping`, mas a
dica de tipo no atributo `maps` diz que ele é uma lista de `Mapping`,
indiretamente tornando todo o `ChainMap` imutável até onde o Mypy entende.]
Tentar mudar uma chave não encontrada causa um `KeyError`.

Esse doctest mostra como `Environment` funciona:

[source, python]
----
include::../code/18-with-match/lispy/py3.10/examples_test.py[tags=ENVIRONMENT]
----

<1> Ao ler os valores, `Environment` funciona como `ChainMap`: as chaves são
procuradas nos mapeamentos aninhados da esquerda para a direita. Por isso o
valor de `a` no `outer_env` é encoberto pelo valor em `inner_env`.

<2> Atribuir com `[]` sobrescreve ou insere novos itens, mas sempre no primeiro
mapeamento, `inner_env` nesse exemplo.

<3> `env.change('b', 333)` busca a chave `b` e atribui a ela um novo valor no
mesmo lugar, no `outer_env`

A seguir temos a função `standard_env()`, que constrói e devolve um
`Environment` carregado com funções pré-definidas, similar ao módulo
`+__builtins__+` de Python, que está sempre disponível (<<lis_std_env_ex>>).

[[lis_std_env_ex]]
.lis.py: `standard_env()` constrói e devolve o ambiente global
====
[source, python]
----
include::../code/18-with-match/lispy/py3.10/lis.py[lines=77..85]
            # omitidas: várias definições de funções
include::../code/18-with-match/lispy/py3.10/lis.py[lines=92..97]
            # omitidas: várias definições de funções
include::../code/18-with-match/lispy/py3.10/lis.py[lines=111..116]
----
====

Resumindo, o mapeamento `env` é carregado com:

* Todas as funções do módulo `math` de Python;
* Operadores selecionados do módulo `op` de Python;
* Funções simples porém poderosas construídas com o `lambda` de Python;
* Estruturas e entidades embutidas de Python, algumas renomeadas, como `callable`
para `procedure?`, ou mapeadas diretamente, como `round`.

==== O REPL

O código do ((("lis.py interpreter", "REPL (read-eval-print-loop)"))) REPL
(read-eval-print-loop, _laço-lê-calcula-imprime_ ) de Norvig é fácil de entender,
mas não é amigável para o usuário (veja o <<ex_lispy_repl>>). Se nenhum argumento de
linha de comando é passado a _lis.py_, a função `repl()` é invocada por
`main()`—definida no final do módulo. No prompt de `lis.py>`, devemos digitar
expressões corretas e completas; se esquecermos de fechar um parêntese,
_lis.py_ se encerra.footnote:[Enquanto estudava o _lis.py_ e o _lispy.py_ de
Norvig, comecei uma versão chamada https://fpy.li/18-20[_mylis_], que acrescenta
alguns recursos, incluindo um REPL que aceita expressões-S parciais e espera a
continuação, como o REPL do Python que sabe quando não terminamos uma instrução e apresenta um prompt
secundário (`\...`) até entrarmos uma instrução completa, que possa
ser analisada e avaliada. O _mylis_ também trata alguns erros de forma graciosa,
mas ele ainda é fácil de quebrar. Não é nem de longe tão robusto quanto o REPL
do Python.]

[role="pagebreak-before less_space"]
[[ex_lispy_repl]]
.As funções do REPL
====
[source, python]
----
include::../code/18-with-match/lispy/py3.10/lis.py[tags=REPL]
----
====

Segue uma breve explicação sobre essas duas funções:

`repl(prompt: str = 'lis.py> ') -> NoReturn`::
    Chama `standard_env()` para provisionar as funções embutidas para o ambiente global,
    então entra em um laço infinito, lendo e avaliando cada linha de entrada,
    calculando-a no ambiente global, e exibindo o resultado—a menos que seja `None`.
    O `global_env` pode ser modificado por  `evaluate`.
    Por exemplo, quando o usuário define uma nova variável global ou uma função nomeada,
    ela é armazenada no primeiro mapeamento do ambiente—o `dict` vazio
    na chamada ao construtor de `Environment` na primeira linha de `repl`.

<<<

`lispstr(exp: object) -> str`::
    A função inversa de `parse`:
    dado um objeto Python representando a AST de uma expressão,
    `lispstr` devolve o código-fonte correspondente.
    Por exemplo, dado `['{plus}', 2, 3]`, o resultado é `'({plus} 2 3)'`.

==== O avaliador de expressões

Agora((("lis.py interpreter", "evaluate function", id="lispyeval18"))) podemos
apreciar a beleza do avaliador de expressões de Norvig—ainda mais elegante
com `match/case`. A função `evaluate` no <<ex_evaluate_match>> recebe uma
`Expression` (construída por `parse`) e um `Environment`.

O corpo de `evaluate` é composto por uma única instrução `match` com uma
expressão `exp` como sujeito. Os padrões de `case` expressam a sintaxe e a
semântica do Scheme com uma clareza impressionante.

[[ex_evaluate_match]]
.`evaluate` recebe uma expressão e calcula seu valor
====
[source, python]
----
include::../code/18-with-match/lispy/py3.10/lis.py[tags=EVALUATE]
----
====

Vamos estudar cada cláusula `case` e o que cada uma faz.
Em algumas casos coloquei comentários mostrando uma expressão-S que
casaria com o padrão quando transformada em uma lista de Python. Os doctests
extraídos de https://fpy.li/18-21[_examples_test.py_] demonstram cada `case`.


[[eval_atom_sec]]
===== avaliando números

[source, python]
----
    case int(x) | float(x):
        return x
----

Padrão&#x3a;::
    Instância de `int` ou `float`.

Ação&#x3a;::
    Devolve o próprio valor.

Exemplo&#x3a;::
+
[source, python]
----
include::../code/18-with-match/lispy/py3.10/examples_test.py[tags=EVAL_NUMBER]
----

===== avaliando símbolos

[source, python]
----
    case Symbol(var):
        return env[var]
----

Padrão&#x3a;::
    Instância de `Symbol`, isto é, uma `str` usada como identificador.

Ação&#x3a;::
    Consulta `var` em `env` e devolve seu valor.

Exemplos&#x3a;::
+
[source, python]
----
include::../code/18-with-match/lispy/py3.10/examples_test.py[tags=EVAL_SYMBOL]
----

===== (quote …)

A forma especial `quote` trata átomos e listas como dados em vez de expressões
a serem avaliadas.

[source, python]
----
    # (quote (99 bottles of beer))
    case ['quote', x]:
        return x
----

Padrão&#x3a;::
    Lista começando com o símbolo `'quote'`, seguido de uma expressão `x`.

Ação&#x3a;::
    Devolve `x` sem avaliá-la.

Exemplos&#x3a;::
+
[source, python]
----
include::../code/18-with-match/lispy/py3.10/examples_test.py[tags=EVAL_QUOTE]
----

Sem `quote`, cada expressão no teste geraria um erro:

[role="pagebreak-before less_space"]
* `no-such-name` seria buscado no ambiente, gerando um `KeyError`
* `(99 bottles of beer)` não pode ser avaliado, pois o número 99 não é
um `Symbol` nomeando uma forma especial, um operador ou uma função
* `(/ 10 0)` geraria um `ZeroDivisionError`

.Por que linguagens têm palavras reservadas?
****

Apesar((("reserved keywords")))((("keywords", "reserved keywords"))) de ser simples,
`quote` não pode ser implementada como uma função em Scheme.
Seu poder especial é impedir que o interpretador avalie `(f 10)` na expressão `(quote (f 10))`:
o resultado é apenas uma lista com um `Symbol` e um `int`.
Por outro lado, em uma chamada de função como `(abs (f 10))`,
o interpretador primeiro calcula o resultado de `(f 10)` antes de invocar `abs`.
Por isso `quote` é uma palavra reservada:
ela precisa ser tratada de uma forma especial.

De modo geral, palavras reservadas são necessárias para:

* Introduzir regras especiais de avaliação,
como `quote` e `lambda`—que não avaliam nenhuma de suas sub-expressões
* Mudar o fluxo de controle, como em `if` e chamadas de função—que
também têm regras especiais de avaliação
* Para gerenciar o ambiente, como em `define` e `set`

Por isso também Python, e linguagens de programação em geral,
precisam de palavras reservadas.
Pense em `def`, `if`, `yield`, `import`, `del`,
e o que elas fazem em Python.
****


===== (if …)

[source, python]
----
    # (if (< x 0) 0 x)
    case ['if', test, consequence, alternative]:
        if evaluate(test, env):
            return evaluate(consequence, env)
        else:
            return evaluate(alternative, env)
----

Padrão&#x3a;::
    Lista começando com `'if'` seguida de três expressões:
    `test`, `consequence`, e `alternative`.

Ação&#x3a;::
    Avalia `test`:
    * Se verdadeira, avalia `consequence` e devolve seu valor.
    * Caso contrário, avalia `alternative` e devolve seu valor.

Exemplos&#x3a;::
+
[source, python]
----
include::../code/18-with-match/lispy/py3.10/examples_test.py[tags=EVAL_IF]
----

Os ramos `consequence` e `alternative` devem ser expressões simples. Se mais de
uma expressão for necessária em um ramo, você pode combiná-las com `(begin exp1
exp2…)`, fornecida como uma função em _lis.py_—veja o <<lis_std_env_ex>>.

===== (lambda …)

A forma `lambda` do Scheme define funções anônimas.
Ela não sofre das limitações da `lambda` de Python:
qualquer função que pode ser escrita em Scheme pode
ser escrita usando a sintaxe `(lambda …)`.

[source, python]
----
    # (lambda (a b) (/ (+ a b) 2))
    case ['lambda' [*parms], *body] if body:
        return Procedure(parms, body, env)
----

Padrão&#x3a;::
    Lista começando com `'lambda'`, seguida de:
    * Lista de zero ou mais nomes de parâmetros
    * Uma ou mais expressões coletadas em `body`
    (a expressão guarda `if body` garante que `body` não pode ser vazio).

Ação&#x3a;::
    Cria e devolve uma nova instância de `Procedure` com os nomes de parâmetros,
    a lista de expressões como o corpo da função, e o ambiente atual.

Exemplo&#x3a;::
+
[source, python]
----
include::../code/18-with-match/lispy/py3.10/examples_test.py[tags=EVAL_LAMBDA]
----

A classe `Procedure` implementa o uma clausura (_closure_):
um objeto invocável contendo nomes de parâmetros, um corpo de função,
e uma referência ao ambiente no qual a `Procedure` está sendo declarada.
Vamos estudar o código de `Procedure` daqui a pouco.


[role="pagebreak-before less_space"]
===== (define …)

A palavra reservada `define` é usada em duas formas sintáticas diferentes.
A mais simples é:

[source, python]
----
    # (define half (/ 1 2))
    case ['define', Symbol(name), value_exp]:
        env[name] = evaluate(value_exp, env)
----

Padrão&#x3a;::
    Lista começando com `'define'`, seguido de um `Symbol` e uma expressão.

Ação&#x3a;::
    Avalia a expressão e coloca o valor resultante em `env`,
    usando `name` como chave.

<<<
Exemplo&#x3a;::
+
[source, python]
----
include::../code/18-with-match/lispy/py3.10/examples_test.py[tags=EVAL_DEFINE]
----

O doctest para este `case` cria um `global_env`, para podermos verificar que
`evaluate` coloca `answer` dentro daquele `Environment`.

Podemos usar a primeira forma de `define` para criar variáveis ou para vincular
nomes a funções anônimas, usando `(lambda …)` como o `value_exp`.

A segunda forma de `define` é um atalho para definir funções nomeadas.

[source, python]
----

    # (define (average a b) (/ (+ a b) 2))
    case ['define', [Symbol(name), *parms], *body] if body:
        env[name] = Procedure(parms, body, env)
----

Padrão&#x3a;::

    Lista começando com `'define'`, seguida de:
    * Uma lista começando com um `Symbol(name)`,
    seguida de zero ou mais itens agrupados em uma lista chamada `parms`.
    * Uma ou mais expressões agrupadas em `body`
    (a expressão guarda garante que `body` não esteja vazio)

Ação&#x3a;::
    * Cria uma nova instância de `Procedure` com os nomes dos parâmetros,
    o corpo como uma lista de expressões, e o ambiente atual.
    * Insere a `Procedure` em `env`, usando `name` como chave.

O doctest no <<test_case_defun>> define e coloca no `global_env`
uma função chamada `%`, que calcula uma porcentagem.

<<<

[[test_case_defun]]
.Definindo uma função chamada `%`, que calcula uma porcentagem
====
[source, python]
----
include::../code/18-with-match/lispy/py3.10/examples_test.py[tags=EVAL_DEFUN]
----
====

Após invocar `evaluate`, verificamos que `%` está vinculada a uma `Procedure` que
recebe dois argumentos numéricos e devolve uma porcentagem.

O padrão para o segundo `define` não obriga os itens em `parms` a serem todos
instâncias de `Symbol`. Eu teria que verificar isso antes de criar a
`Procedure`, mas não o fiz—para manter o código aqui tão fácil de acompanhar
quanto o de Norvig.



===== (set! …)

A forma `set!` muda o valor de uma variável previamente definida.footnote:[A
atribuição é um dos primeiros recursos ensinados em muitos tutoriais de
programação, mas `set!` só aparece na página 220 do mais conhecido livro de
Scheme, https://fpy.li/18-22[_Structure and Interpretation of Computer Programs_
(_A Estrutura e a Interpretação de Programas de Computador_), 2nd ed.,] de
Abelson et al. (MIT Press), também conhecido como SICP ou _Wizard Book_ (Livro
do Mago). Programas em estilo funcional podem nos levar muito longe sem as
mudanças de estado típicas da programação imperativa e da programação orientada
a objetos.]

[source, python]
----
    # (set! n (+ n 1))
    case ['set!', Symbol(name), value_exp]:
        env.change(name, evaluate(value_exp, env))
----

Padrão&#x3a;::
    Lista começando com `'set!'`, seguida de um `Symbol` e de uma expressão.

Ação&#x3a;::
    Atualiza o valor de `name` em `env` com o resultado da avaliação da expressão.

O método `Environment.change` atravessa os ambientes encadeados de local para global,
e atualiza a primeira ocorrência de `name` com o novo valor.
Se não estivéssemos implementando a palavra reservada `'set!'`,
esse interpretador poderia usar apenas o `ChainMap` de Python para implementar `env`,
sem precisar da nossa classe `Environment`.

[role="pagebreak-before less_space"]
.O `nonlocal` de Python e o `set!` do Scheme tratam da mesma questão
****

O((("nonlocal keyword")))((("keywords", "nonlocal keyword"))) uso da forma
`set!` tem relação com a instrução `nonlocal` em Python:
declarar `nonlocal x` permite a `x = 10` atualizar uma variável `x`
anteriormente definida fora do escopo local. Sem a declaração `nonlocal x`, `x =
10` vai sempre criar uma variável local em Python, como vimos na
https://fpy.li/cm[«Seção 9.7»] (vol.2).

De forma similar, `(set! x 10)` atualiza um `x` anteriormente definido que pode
estar fora do ambiente local da função. Por outro lado, a variável `x` em
`(define x 10)` será sempre uma variável local, criada ou atualizada no ambiente
local.

Ambos, `nonlocal` e `(set! …)`, são necessários para atualizar o estado do
programa mantido em variáveis dentro de uma clausura. O
Exemplo 13 do https://fpy.li/9[«Capítulo 9»] (vol.2)
demonstrou o uso de `nonlocal` para implementar uma função
que calcula uma média contínua, preservando os itens `count` e `total` em uma
clausura. Aqui está a mesma ideia, escrita no subconjunto de Scheme de _lis.py_:

[source, scheme]
----
(define (make-averager)
    (define count 0)
    (define total 0)
    (lambda (new-value)
        (set! count (+ count 1))
        (set! total (+ total new-value))
        (/ total count)
    )
)
(define avg (make-averager))  # <1>
(avg 10)  # <2>
(avg 11)  # <3>
(avg 15)  # <4>
----
<1> Cria uma nova clausura com a função interna definida por `lambda`
e as variáveis `count` e `total`, inicializadas com 0; vincula a clausura a `avg`.
<2> Devolve 10.0.
<3> Devolve 10.5.
<4> Devolve 12.0.

O código acima é um dos testes em https://fpy.li/18-18[_lispy/py3.10/examples_test.py_].

****

Agora chegamos a uma chamada de função.

[[function_call_sec]]
===== Chamada de função

[source, python]
----
    # (gcd (* 2 105) 84)
    case [func_exp, *args] if func_exp not in KEYWORDS:
        proc = evaluate(func_exp, env)
        values = [evaluate(arg, env) for arg in args]
        return proc(*values)
----

Padrão&#x3a;::
+
--
Lista com um ou mais itens.

A expressão guarda garante que `func_exp` não é um de
`['quote', 'if', 'define', 'lambda', 'set!']`—listados
logo antes de `evaluate` no <<ex_evaluate_match>>.

O padrão casa com  qualquer lista com uma ou mais expressões,
vinculando a primeira expressão a `func_exp` e
o restante a `args` como uma lista, que pode ser vazia.
--

Ação&#x3a;::
    * Avaliar `func_exp` para obter um `Procedure` que representa a função.
    * Avaliar cada item em `args` para criar uma lista de valores dos argumentos.
    * Invocar `proc` com os valores como argumentos separados, devolvendo o resultado.

Exemplo&#x3a;::
+
[source, python]
----
include::../code/18-with-match/lispy/py3.10/examples_test.py[tags=EVAL_CALL]
----

Esse doctest continua do <<test_case_defun>>:
ele assume que `global_env` contém uma função chamada `%`.
Os argumentos passados a `%` são expressões aritméticas,
para enfatizar que eles são avaliados antes da função ser chamada.

A expressão guarda nesse `case` é necessária porque `[func_exp, *args]`
casa com qualquer sujeito que seja uma sequência de um ou mais itens.
Entretanto, se `func_exp` é uma palavra reservada e o
sujeito não casou com nenhum dos `case` anteriores,
então temos um erro de sintaxe, como `(define x)`.


===== Capturar erros de sintaxe

Se o sujeito `exp` não casa com  nenhum dos `case` anteriores,
o `case` "coringa" gera um `SyntaxError`:

[source, python]
----
    case _:
        raise SyntaxError(lispstr(exp))
----

Aqui está um exemplo de um `(lambda …)` malformado, identificado como um `SyntaxError`:

[source, python]
----
include::../code/18-with-match/lispy/py3.10/examples_test.py[tags=EVAL_SYNTAX_ERROR]
----

Se o `case` para chamada de função não tivesse aquela expressão guarda
rejeitando palavras reservadas, a expressão `(lambda is not like this)` teria
sido tratada como uma chamada de função, que geraria um `KeyError`, pois
`'lambda'` faz parte do ambiente—assim como `lambda` em Python não é
o nome de uma função embutida.((("", startref="lispyeval18")))


==== Procedure: uma classe que implementa uma clausura


A((("decorators and closures", "closures in lis.py")))((("lis.py interpreter",
"Procedure class", id="lispyproced18"))) classe `Procedure` poderia muito bem se
chamar `Closure`, porque é isso que ela representa: uma definição de função
no contexto de um ambiente. A definição de função inclui o nome dos parâmetros e as
expressões que formam o corpo da função. O((("free variables"))) ambiente é
usado quando a função é chamada, para fornecer os valores das _variáveis
livres_: variáveis que aparecem no corpo da função, mas não são parâmetros,
variáveis locais ou variáveis globais. Vimos os conceitos de _clausura_ e de
_variáveis livres_ na https://fpy.li/c8[«Seção 9.6»] (vol.2).

Aprendemos como usar clausuras em Python, mas agora podemos mergulhar mais fundo
e ver como uma clausura é implementada em _lis.py_:

[source, python]
----
include::../code/18-with-match/lispy/py3.10/lis.py[tags=PROCEDURE]
----
<1> `+__init__+` é invocado quando uma função é definida pelas instruções `lambda` ou `define`.
<2> Salva os nomes dos parâmetros, as expressões no corpo e o ambiente, para uso posterior.
<3> `+__call__+` é invocado por `proc(*values)` na última linha da cláusula `case [func_exp, *args]`.
<4> Cria `local_env`, mapeando `self.parms` como nomes de variáveis locais e os `args` passados como valores.
<5> Cria um novo `env` combinado, colocando `local_env` primeiro e então
`self.env`—o ambiente salvo quando a função foi definida.
<6> Itera sobre cada expressão em `self.body`, avaliando-as no `env` combinado.
<7> Devolve o resultado da última expressão avaliada.

Há um par de funções simples após `evaluate` em https://fpy.li/18-24[_lis.py_]:
`run` lê um programa Scheme completo e o executa,
e `main` chama `run` ou `repl`, dependendo da linha de comando—parecido com o modo como Python faz.
Não vou descrever essas funções, pois não há nada novo ali.
Meus objetivos aqui eram compartilhar com vocês a beleza do pequeno interpretador de Norvig,
explicar melhor como as clausuras funcionam,
e mostrar como `match/case` foi uma ótima adição ao Python.

Para fechar essa seção estendida sobre casamento de padrões,
vamos formalizar o conceito de um padrão-OU (_OR-pattern_).((("", startref="lispyproced18")))

==== Usando padrões-OU

Uma((("lis.py interpreter", "OR-patterns")))((("OR-patterns")))((("&#x01C0; (pipe) operator")))((("pipe (&#x01C0;) operator")))
série de padrões separados por `|` formam um
padrão-OU (documentado em 
https://fpy.li/18-25[_OR-patterns_]):
ele casa se qualquer dos sub-padrões casar.
Este é um padrão-OU que já vimos na <<eval_atom_sec>>:

[source, python]
----
    case int(x) | float(x):
        return x
----

Todos os sub-padrões em um padrão-OU devem usar as mesmas variáveis.
Esta restrição é necessária para garantir que
as variáveis estejam disponíveis na expressão de guarda e no corpo do `case`,
independentemente de qual sub-padrão tenha casado.

[WARNING]
====
No contexto de uma cláusula `case`, o operador `|` tem um significado especial.
Ele não aciona o método especial `+__or__+`,
que manipula expressões como `a | b` em outros contextos,
onde ele é sobrecarregado para realizar operações como união de conjuntos ou
disjunção binária com inteiros (o "ou binário"), dependendo dos operandos.
====

Um padrão-OU não está limitado a aparecer no nível superior de um padrão. 
O símbolo `|` pode também ser usado em sub-padrões.
Por exemplo, se quiséssemos que o _lis.py_
aceitasse a letra grega λ (lambda)footnote:[O nome para λ
(U+03BB) no Unicode é GREEK SMALL LETTER LAMDA. Isso não é um erro ortográfico: o caractere
é chamado "lamda" sem o "b" no banco de dados do Unicode. De acordo com o artigo
https://fpy.li/18-26["Lambda"] da Wikipedia em inglês, o Unicode Consortium
adotou essa ortografia em função de "preferências expressas pela Autoridade Nacional
Grega."] além da palavra reservada `lambda`, poderíamos reescrever o padrão
assim:

[source, python]
----
    # (λ (a b) (/ (+ a b) 2) )
    case ['lambda' | 'λ', [*parms], *body] if body:
        return Procedure(parms, body, env)
----

Agora podemos passar para o terceiro e último assunto deste capítulo:
lugares incomuns onde a cláusula `else` pode aparecer no Python.((("",
startref="WMEBlispy18")))((("", startref="PMlispy18")))

=== Faça isso, então aquilo: blocos `else` além do `if`

Não((("with, match, and else blocks", "else clause", id="WMEBelse18")))((("else
blocks", id="else18"))) é segredo, mas é um recurso pouco conhecido em
Python: a cláusula `else` pode ser usada não apenas com instruções `if`, mas
também com as instruções `for`, `while`, e `try`.

A semântica para `for/else`, `while/else`, e `try/else` é semelhante, mas é
muito diferente do `if/else`. No início, a palavra `else` atrapalhou
meu entendimento destas cláusulas, mas acabei me acostumando.

Aqui estão as regras:

`for`:: O bloco `else` será executado somente se e quando o laço `for` rodar até
o iterável terminar (isto é, não rodará se o `for` for interrompido com um `break`).

`while`:: O bloco `else` será executado somente se e quando o laço `while`
terminar pela condição se tornar _falsa_ (novamente, não rodará se o `while` for
interrompido por um `break`)

`try`:: O bloco `else` será executado somente se nenhuma exceção for gerada no
bloco `try`. A https://fpy.li/9x[«documentação oficial»] também afirma: "Exceções
na cláusula `else` não são tratadas pela cláusula `except` precedente."

Em todos os casos, a cláusula `else` também será ignorada se uma exceção ou uma
instrução `return`, `break` ou `continue` fizer com que o fluxo de controle saia do bloco principal da instrução composta.
No caso do `try`, esta é a diferença importante entre `else` e `finally`:
o bloco `finally` será executado sempre, ocorrendo ou não uma exceção,
e até mesmo se o fluxo de execução sair do bloco `try` por uma instrução como `return`.

[NOTE]
====
Não tenho nada contra o funcionamento destas cláusulas `else`,
mas do ponto de vista do design da linguagem,
a palavra `else` foi uma escolha infeliz, porque
`else` implica em uma alternativa excludente,
como em "Execute esse laço, caso contrário faça aquilo."
Mas o significado do `else` em laços é o oposto: "Execute esse laço, então faça aquilo."
Isso sugere que `then` ("então") seria uma escolha melhor.
Também faria sentido no contexto de um `try`:
"Tente isso, então faça aquilo."
Entretanto, acrescentar uma nova palavra reservada é uma ruptura
séria em uma linguagem—uma decisão difícil.
Guido sempre foi econômico com palavras reservadas.
====

Usar `else` com essas instruções muitas vezes torna o código mais fácil de ler e
evita o transtorno de criar variáveis de controle ou codar instruções
`if` adicionais.

O uso de `else` em laços em geral segue o padrão desse trecho:

[source, python]
----
for item in my_list:
    if item.flavor == 'banana':
        break
else:
    raise ValueError('No banana flavor found!')
----

No caso de blocos `try/except`, o `else` pode parecer redundante à primeira
vista. Afinal, a `after_call()` no trecho a seguir só será invocada se a
`dangerous_call()` não gerar uma exceção, correto?

[source, python]
----
try:
    dangerous_call()
    after_call()
except OSError:
    log('OSError...')
----

Entretanto, não há um bom motivo para colocar a `after_call()` dentro do bloco `try`.
Por clareza e correção, o corpo de um bloco `try` deveria conter apenas
instruções que podem gerar as exceções esperadas. Assim fica melhor:

[source, python]
----
try:
    dangerous_call()
except OSError:
    log('OSError...')
else:
    after_call()
----

Agora fica explícito que o bloco `try` está de guarda contra possíveis erros na
`dangerous_call()`, e não em `after_call()`. Também fica explícito que
`after_call()` só será executada se nenhuma exceção for gerada no bloco `try`.

Em Python, `try/except` é bastante usado para controle de fluxo, não apenas
para tratamento de erro. Há inclusive uma sigla/slogan para isso, documentado
no https://fpy.li/9y[«glossário oficial»] do Python:

[quote]
____

EAFP:: Iniciais da expressão em inglês “easier to ask for forgiveness than
permission” que significa “é mais fácil pedir perdão que permissão”. Este estilo
de codificação comum em Python assume a existência de chaves ou atributos
válidos e captura exceções caso essa premissa se prove falsa. Este estilo limpo
e rápido se caracteriza pela presença de várias instruções `try` e `except`. A
técnica diverge do estilo LBYL, comum em outras linguagens como C, por exemplo.

____

O glossário então define LBYL:

[quote]
____

LBYL:: Iniciais da expressão em inglês “look before you leap”, que significa
algo como “olhe antes de pisar”. Este estilo de
codificação testa as pré-condições explicitamente antes de fazer chamadas ou
buscas. Este estilo contrasta com a abordagem EAFP e é caracterizado pela
presença de muitas instruções `if`. Em um ambiente multithread, a abordagem LBYL
pode arriscar a introdução de uma condição de corrida entre “o olhar” e “o
pisar”. Por exemplo, o código `if key in mapping: return mapping[key]` pode
falhar se outra thread remover `key` do `mapping` após o teste (olhar), mas antes
de acessar a chave (pisar). Este problema pode ser resolvido com bloqueios [travas] ou usando a
abordagem EAFP.

____

Dado o estilo EAFP, faz mais sentido conhecer e usar os blocos `else`
corretamente nas instruções `try/except`.

[NOTE]
====

Quando a instrução `match` foi proposta, algumas pessoas (inclusive eu)
acharam que ela também devia ter uma cláusula `else`. Afinal ficou
decidido que isso não era necessário, pois +
`case _:` tem o mesmo efeito.footnote:[Acompanhando a discussão na lista python-dev, achei que um
motivo para a rejeição do `else` foi a falta de consenso sobre como indentá-lo
dentro do `match`: o `else` deveria ser indentedo no mesmo nível do `match` ou
no mesmo nível do `case`?]

====

Agora vamos resumir o capítulo((("", startref="else18")))((("", startref="WMEBelse18"))).

=== Resumo do capítulo

Este((("with, match, and else blocks", "overview of"))) capítulo começou com
o significado da instrução `with` e os gerenciadores de contexto, indo
além do uso mais comum: fechar arquivos automaticamente. Implementamos
um gerenciador de contexto customizado, a classe `LookingGlass`, usando os
métodos `+__enter__/__exit__+`, e vimos como tratar exceções no método
`+__exit__+`. Uma ideia fundamental apontada por Raymond Hettinger, na palestra
de abertura da Pycon US 2013, é que `with` não serve apenas para gerenciamento
de recursos; ele é uma ferramenta para fatorar código comum de configuração e de
finalização, ou qualquer par de operações que precisem ser executadas antes e
após outro procedimento.footnote:[Veja o https://fpy.li/18-29[slide 21 em
_Python is Awesome_ (Python é Incrível)].]

Revisamos funções no módulo `contextlib` da biblioteca padrão. Uma delas, o
decorador `@contextmanager`, permite implementar um gerenciador de contexto
usando apenas um mero gerador com um `+yield+`—uma solução menos trabalhosa que
criar uma classe com pelo menos dois métodos. Reimplementamos a `LookingGlass`
como uma função geradora `looking_glass`, e discutimos como fazer tratamento de
exceções usando o `@contextmanager`.

<<<
Então estudamos o elegante interpretador Scheme de Peter Norvig, o _lis.py_,
escrito em Python idiomático e refatorado para usar `match/case` em `evaluate`—a
função central de qualquer interpretador. Entender o funcionamento de
`evaluate` exigiu revisar um pouco de Scheme, um parser para expressões-S, um
REPL simples e a construção de escopos aninhados através de `Environment`, uma
subclasse de `collection.ChainMap`. No fim, _lys.py_ foi um instrumento
para explorarmos mais que casamento de padrões. Ele mostra como diferentes partes
de um interpretador trabalham juntas, ilustrando conceitos fundamentais do
próprio Python: por que palavras reservadas são necessárias, como as regras de
escopo funcionam, e como clausuras são criadas e usadas.


[[further_reading_context_sec]]
=== Para saber mais

O https://fpy.li/9x[«Capítulo 8, Instruções Compostas»] na((("with, match, and
else blocks", "further reading on"))) _Referência da Linguagem Python_ diz
praticamente tudo que há para dizer sobre cláusulas `else` em instruções `if`,
`for`, `while` e `try`. Sobre o uso pythônico de `try/except`, com ou sem
`else`, Raymond Hettinger deu uma resposta brilhante para a pergunta
https://fpy.li/18-31[_Is it a good practice to use try-except-else in Python?_
(É uma boa prática usar try-except-else em Python?)] no StackOverflow. O
https://fpy.li/pynut3[_Python in aNutshell, 3rd ed._], de Martelli et.al., tem um capítulo sobre exceções
com uma excelente discussão sobre o estilo EAFP, atribuindo à pioneira da
computação Grace Hopper a frase "É mais fácil pedir perdão que pedir
permissão."

O capítulo 4 de _A Biblioteca Padrão de Python_, "Tipos Embutidos", tem uma
seção dedicada a https://fpy.li/9z[«Tipos de Gerenciador de Contexto»]. Os
métodos especiais `+__enter__/__exit__+` também estão documentados em _A
Referência da Linguagem Python_, em https://fpy.li/a4[«Gerenciadores de Contexto
da Instrução with»]. Os gerenciadores de contexto foram propostos na
https://fpy.li/pep343[_PEP 343—The "with" Statement_].

Raymond Hettinger apontou a instrução `with` como um "recurso maravilhoso da
linguagem" em sua https://fpy.li/18-29[«palestra de abertura»] da PyCon US 2013.
Ele também mostrou alguns usos interessantes de gerenciadores de contexto em sua
apresentação https://fpy.li/18-35[_Transforming Code into Beautiful, Idiomatic
Python+] (Transformando Código em Python Lindo e Idiomático), na mesma
conferência.

<<<
O post de Jeff Preshing em seu blog,
https://fpy.li/18-36[_The Python 'with' Statement by Example_]
(A Instrução 'with' de Python através de exemplos)
é interessante pelos exemplos de uso de gerenciadores de contexto com a
biblioteca gráfica `pycairo`.

A classe `contextlib.ExitStack` foi baseada em uma ideia original de Nikolaus
Rath, que escreveu um post curto explicando por que ela é útil:
https://fpy.li/18-37[_On the Beauty of Python's ExitStack_] 
(Sobre a Beleza do ExitStack de Python]. No texto, Rath argumenta que `ExitStack` é similar,
mas mais flexível que a instrução `defer` em Go—que acho uma das melhores ideias
naquela linguagem.

Beazley e Jones desenvolveram gerenciadores de contexto para propósitos muito
diferentes em seu livro, 
https://fpy.li/pycook3[_Python Cookbook, 3rd. ed._]. A _Recipe 8.3.
Making Objects Support the Context-Management Protocol_ (Fazendo
Objetos Suportarem o Protocolo Gerenciador de Contexto) implementa uma classe
`LazyConnection`, cujas instâncias são gerenciadores de contexto que abrem e
fecham conexões de rede automaticamente, em blocos `with`. A 
_Recipe 9.22. Defining Context Managers the Easy Way_
(O Jeito Fácil de Definir Gerenciadores de Contexto)
apresenta um gerenciador de contexto para código de
cronometragem, e outro para realizar mudanças transacionais em um objeto `list`:
dentro do bloco `with` é criada uma cópia de trabalho da instância de `list`, e
todas as mudanças são aplicadas àquela cópia de trabalho. Apenas quando o bloco
`with` termina sem uma exceção a cópia de trabalho substitui a original. Simples e
genial.

Peter Norvig descreve seu pequeno interpretador Scheme nos posts
https://fpy.li/18-38[_(How to Write a (Lisp) Interpreter (in Python))_]
(Como Escrever um Interpretador (Lisp) (em Python)) e
https://fpy.li/18-39[_(An ((Even Better) Lisp) Interpreter (in Python))_]
(Um Interpretador (Lisp (Ainda Melhor)) (em Python)).
O código-fonte de _lis.py_ e _lispy.py_ está no repositório
https://fpy.li/18-40[_norvig/pytudes_]. Meu repositório,
https://fpy.li/18-41[_fluentpython/lispy_], inclui a versão _mylis_ do _lis.py_,
atualizado para Python 3.10, com um REPL melhor, integração com a linha de
comando, exemplos, mais testes e referências para aprender mais sobre Scheme.
O melhor ambiente e dialeto de Scheme para explorar é o
https://fpy.li/18-42[_Racket_].


<<<
[[soapbox_with_match]]
.Ponto de vista
****


*Fatorando o pão*

Em ((("Soapbox sidebars", "with statements")))((("with, match, and else blocks",
"Soapbox discussion", id="WMEBsoap18"))) sua palestra de abertura na PyCon US
2013, https://fpy.li/18-1[_What Makes Python Awesome_] (O que torna Python
incrível), Raymond Hettinger diz que quando viu a proposta da instrução
`with`, pensou que era "um pouquinho misteriosa." Eu tive uma reação
similar, inicialmente. As PEPs são muitas vezes difíceis de ler, e a PEP 343 é típica nesse
sentido.

Mas aí--nos contou Hettinger--ele teve uma ideia: as sub-rotinas são a invenção
mais importante na história das linguagens de computador. Se você tem sequências
de operações, como A;B;C e P;B;Q, você pode fatorar B em uma sub-rotina. É como
fatorar o recheio de um sanduíche: usar atum com tipos de diferentes de pão. Mas
e se você quiser fatorar o pão, para fazer sanduíches com pão de trigo integral
usando recheios diferentes a cada vez? É isso que a instrução `with` oferece.
Ela é o complemento da sub-rotina. Hettinger continuou:

[quote]
____

A instrução `with` é algo muito importante.
Encorajo vocês a irem lá e olharem para a ponta desse iceberg,
e daí cavarem mais fundo.
Provavelmente é possível fazer coisas muito profundas com a instrução `with`.
Seus melhores usos ainda estão por ser descobertos.
Acredito que, se fizerem bom uso dela,
ela será copiada em outras linguagens,
e todas as linguagens futuras vão incluí-la.
Vocês podem fazer parte da descoberta de algo
quase tão profundo quanto a invenção da própria sub-rotina.

____

Hettinger admite que está forçando a "venda" da instrução `with`.
Mesmo assim, é um recurso bem útil.
Quando ele usou a analogia do sanduíche para explicar como `with` é o
complemento da sub-rotina, muitas possibilidades se abriram na minha mente.

Se você precisa convencer alguém que de Python é sensacional, assista à palestra de
abertura de Hettinger. A parte sobre gerenciadores de contexto fica entre 23:00
e 26:15. Mas a palestra inteira é excelente.

<<<
*Recursão eficiente com chamadas de cauda apropriadas*


As implementações((("Soapbox sidebars", "proper tail calls (PTC)",
id="SStail18")))((("proper tail calls (PTC)", id="PTC18")))((("tail call
optimization (TCO)", id="tco18"))) padrão de Scheme são obrigadas a oferecer
_chamadas de cauda apropriadas_ (PTC, sigla de _Proper Tail Calls_),
para tornar a iteração por recursão uma alternativa prática aos laços `while` e `for`
das linguagens imperativas. Alguns autores se referem às PTC como _otimização de
chamadas de cauda_ (TCO, sigla de _Tail Call Optimization_); para
outros, TCO é uma coisa diferente. Para mais detalhes, leia
https://fpy.li/a2[«Chamadas recursivas de cauda»] na Wikipedia em português e
https://fpy.li/18-44[«Tail call»], mais aprofundado, na Wikipedia em inglês, e
https://fpy.li/18-45[«Tail call optimization in ECMAScript 6»].

Uma _chamada de cauda_ é quando uma função devolve o resultado de uma chamada de
função, que pode ou não ser a ela mesma (a função que está devolvendo o
resultado). Os exemplos `gcd` no <<ex_gcd_scheme>> e no <<ex_gcd_python>> fazem
chamadas de cauda (recursivas) no desvio _falso_ do `if`.

Por outro lado, esta `factorial` não faz uma chamada de cauda:

[source, python]
----
def factorial(n):
    if n < 2:
       return 1
    return n * factorial(n - 1)
----

A chamada para `factorial` na última linha não é uma chamada de cauda, pois o
valor de `return` não é somente o resultado de uma chamada recursiva: o
resultado é multiplicado por `n` antes de ser devolvido.

Aqui está uma alternativa que usa uma chamada de cauda,
e é portanto recursiva de cauda (_tail recursive_):

[source, python]
----
def factorial_tc(n, product=1):
    if n < 1:
        return product
    return factorial_tc(n - 1, product * n)
----

<<<
Python não tem PTC então não há vantagem em escrever funções recursivas de
cauda. Neste caso, versão `factorial` é mais curta  e mais legível, na minha opinião,
do que a `factorial_tc`.
Para usos na vida real, não se esqueça de que Python tem o
`math.factorial`, escrito em C sem recursão. O ponto é que, mesmo em linguagens
que implementam PTC, isso não beneficia toda função recursiva, apenas aquelas
cuidadosamente escritas para fazer chamadas de cauda.

Se a linguagem implementa PTC, quando o interpretador vê uma chamada de
cauda, ele pula para dentro do corpo da função chamada sem criar um novo stack
frame, economizando memória. Há também linguagens compiladas que implementam
PTC, por vezes como uma otimização que pode ser ligada e desligada.

Não existe um consenso universal sobre a definição de TCO ou sobre o valor das
PTC em linguagens que não foram projetadas como linguagens funcionais desde o
início, como Python e JavaScript.
Em linguagens funcionais, PTC não é apenas uma otimização desejável. Se a linguagem não tem
outro mecanismo de iteração além da recursão, então PTC é necessário para viabilizar
o uso da linguagem na prática. O
https://fpy.li/18-46[_lis.py_] de Norvig não
implementa PTC, mas seu interpretador mais elaborado, o
https://fpy.li/18-16[_lispy.py_], implementa.

*Argumentos contra chamadas de cauda apropriadas em Python e JavaScript*

O CPython não implementa PTC, e provavelmente nunca o fará.
Guido van Rossum escreveu
https://fpy.li/18-48[_Final Words on Tail Calls_]
(Últimas Palavras sobre Chamadas de Cauda)
para explicar o motivo. Resumindo, aqui está uma passagem fundamental de seu post:

[quote]
____

Pessoalmente, acho que é um bom recurso para algumas linguagens, mas não acho
que se encaixe no Python: a eliminação dos registros do stack para algumas
chamadas mas não para outras certamente confundiria muitos usuários, que não
foram criados na religião das chamadas de cauda, mas podem ter aprendido sobre a
semântica das chamadas rastreando algumas chamadas em um depurador.

____

<<<
Em 2015, PTC foram incluídas no padrão ECMAScript 6 para JavaScript.
Em outubro de 2021 o interpretador do
https://fpy.li/18-49[«WebKit as implementa»].
O WebKit é usado pelo Safari.
Os interpretadores JS em todos os outros navegadores populares não têm PTC,
assim como o Node.js, que depende do interpretador V8 que o Google mantém para o Chrome.
Transpiladores e _polyfills_ (injetores de código) voltados para o JS,
como o TypeScript, o ClojureScript e o Babel, também não suportam PTC,
de acordo com uma https://fpy.li/18-50[«tabela de compatibilidade com ECMAScript 6»].

Já vi várias explicações para a rejeição das PTC por parte dos implementadores,
mas a mais comum é a mesma que Guido van Rossum mencionou:
PTC tornam a depuração mais difícil para todo mundo,
e beneficiam apenas uma minoria que prefere usar recursão para fazer iteração.
Para mais detalhes, veja
https://fpy.li/18-51[_What happened to proper tail calls in JavaScript?_]
(O que aconteceu com as chamadas de cauda apropriadas em JavaScript?) de Graham Marlow.

Há casos em que a recursão é a melhor solução, mesmo no Python sem PTC.
Em um https://fpy.li/18-52[post anterior]
sobre o assunto, Guido escreveu:

[quote]
____
[...] uma implementação típica de Python permite 1000 recursões,
o que é bastante para código não-recursivo e para código que usa recursão para percorrer,
por exemplo, uma árvore de parsing típica,
mas não o bastante para um laço escrito de forma recursiva sobre uma lista grande.
____

Concordo com Guido e com a maioria dos implementadores de JavaScript.

A falta de PTC é a maior restrição ao desenvolvimento de programas Python em um
estilo funcional—mais que a sintaxe limitada de `lambda`.

Se você estiver curioso em ver como PTC funciona em um interpretador com menos
recursos (e menos código) que o _lispy.py_ de Norvig, veja o
https://fpy.li/18-53[__mylis_2__]. O truque começa com o laço infinito em
`evaluate` e o código no `case` que faz chamadas de função: essa combinação faz o
interpretador pular para dentro do corpo da próxima `Procedure` sem invocar
`evaluate` recursivamente durante a chamada de cauda.

<<<
Estes pequenos
interpretadores demonstram o poder da abstração: apesar de Python não
implementar PTC, é possível e não muito difícil escrever um interpretador, em
Python, que implementa PTC. Aprendi a fazer isso lendo o código de Peter Norvig.
Obrigado por compartilhar, professor!((("", startref="tco18")))((("",
startref="PTC18")))((("", startref="SStail18")))

*A opinião de Norvig sobre `evaluate()` com casamento de padrões*

Mostrei((("Soapbox sidebars", "lis.py and evaluate function")))
o código da versão Python 3.10 de _lis.py_ para Peter Norvig.
Ele gostou do exemplo usando casamento de padrões, mas sugeriu uma solução diferente:
em vez de usar as guardas que escrevi,
ele teria exatamente um `case` por palavra reservada,
e teria testes dentro de cada `case`, para fornecer mensagens de `SyntaxError`
mais específicas—por exemplo, quando o corpo estiver vazio.

Se eu fizesse assim, a expressão guarda nesta linha seria desnecessária:

[source, python]
----
        case [func_exp, *args] if func_exp not in KEYWORDS:
----

A guarda seria redundante
pois todas as palavras reservadas já teriam sido tratadas antes do `case` para chamadas de função.

Provavelmente seguirei o conselho do professor Norvig quando
acrescentar funcionalidades ao
https://fpy.li/18-54[_mylis_].

Mas a forma como estruturei `evaluate` no <<ex_evaluate_match>>
tem algumas vantagens didáticas neste livro:

* o exemplo é paralelo à implementação original de Norvig 
com `if/elif/…` no 
Exemplo 11 do https://fpy.li/2[«Capítulo 2»] (vol.1);
* as cláusulas `case` demonstram mais recursos de casamento de padrões;
* o código ficou mais legível, na minha opinião.((("", startref="WMEBsoap18")))

****
