Aqui vai um problema de programação simples, porém interessante, originalmente proposto por Mattox Beckman. Depois de ver a abordagem de Tejas Dinkar para este problema usando Haskell, decidi tentar com Clojure.
Você é Hércules, prestes a lutar contra a temida Hydra. A Hydra tem 9 cabeças. Quando uma cabeça é cortada, ela gera 8 cabeças novas. Quando uma dessas 8 cabeças é cortada, cada uma gera 7 cabeças novas. Cortar uma dessas gera 6 cabeças novas, e assim por diante até que a cabeça mais fraca da hidra não gerará mais nenhuma cabeça nova.
Nosso trabalho é descobrir quantos cortes Hércules precisa dar para matar todas as cabeças da Hydra. E não, não é n!.
Podemos começar definindo uma função que retorna uma Hydra de n cabeças.
|
|
Para facilitar a comparação entre ambas as soluções, a estrutura de dados que estou usando aqui é a mesma usada por Dinkar: uma lista. Nesta lista, cada número representa uma cabeça viva e seu nível de força.
Agora, de acordo com a descrição do problema, quando Hércules corta uma cabeça de nível 3, a Hydra cresce duas cabeças de nível 2.
|
|
Aqui está uma possível implementação para tal função.
|
|
Este código deve fazer sentido mesmo se você não estiver familiarizado com Clojure.
O que acontece se Hércules tentar cortar a cabeça de uma Hydra sem cabeças?
A maioria das linguagens de programação funcional que conheço são construídas sobre um forte princípio chamado propriedade de fechamento.
Em geral, uma operação para combinar objetos de dados satisfaz a propriedade de fechamento se os resultados de combinar coisas com essa operação podem eles mesmos ser combinados usando a mesma operação. Fechamento é a chave para o poder em qualquer meio de combinação porque permite criar estruturas hierárquicas – estruturas compostas de partes, que elas mesmas são compostas de partes, e assim por diante.
– Gerald Jay Sussman, Hal Abelson
Para ilustrar este conceito com código, vamos considerar a função cons do Clojure.
|
|
Isso significa que cons segue o princípio de fechamento. Mas e nossa função chop-head?
O princípio se mantém?
|
|
Aparentemente não. Para corrigir isso, precisamos garantir que dec não seja chamado com
nil, já que não é possível decrementar um valor nulo.
|
|
E agora?
|
|
Matando a Hydra
Para que Hércules mate a Hydra, ele precisa repetidamente cortar as cabeças da Hydra enquanto ela ainda as tiver.
|
|
A função (iterate f x) retorna uma sequência lazy (infinita) de x, (f x),
(f (f x)), etc, dado que f é uma função livre de efeitos colaterais.
|
|
Como chop-head respeita o princípio de fechamento sempre retornando uma lista,
podemos usá-la em iterate até obtermos uma lista vazia, o que significa que a Hydra
está MORTA.
Vamos testá-la em uma Hydra bebê de 3 cabeças.
|
|
Quantos cortes são necessários para matar a Hydra original de 9 cabeças?
|
|
Outra pergunta interessante: qual é o número máximo de cabeças que Hércules enfrentou de uma só vez?
|
|
Ah. Lindo.