September 13, 2006

→ the title:: Tutorial: dois pesos, duas medidas :: → keywords:, , , , , @ 1:26 pm

Cipitá é uma cidade do interior, dessas de novela. Cipitá fica a oeste de tudo; não há lei a oeste de Trilopinha, não há Deus a oeste de Rabidinga, e ao redor de Cipitá não existe nem água encanada.

A autoridade máxima de Cipitá é um juiz preconceituoso. Em Cipitá, ou você é cipitense, ou é um forasteiro.


data Pessoa a b = Forasteiro a | Cipitense b

O juiz preconceituoso de Cipitá tem critérios completamente diferentes para Forsteiros e Cipitenses; para um dado veredito a pronunciar seu raciocínio é o seguinte:


veredito sentença_ruim sentença_boa (Forasteiro a) = sentença_ruim a
veredito sentença_ruim sentença_boa (Cipitense a) = sentença_boa a


Por exemplo, em um caso de roubo de galinhas, manda a lei consuetudinária de Cipitá que os cipitenses sejam condenados a um mês de reclusão, mas os forasteiros ficam presos três anos!


roubou_galinhas = veredito (const "3 anos") (const "1 mês")

Sejam, por exemplo, duas pessoas envolvidas com um roubo de galinhas.


vladimir = Forasteiro "Vladimir Trilopinhense"
mutema = Cipitense "Mutema Bebum"

O juiz vai decidir o que fazer com ambas pessoas, usando a avançada tecnologia de programação funcional disponível hoje em dia com o Haskell:


> roubou_galinhas mutema
1 mês
> roubou_galinhas vladimir
3 anos

Em Cipitá, existem dois pesos e duas medidas.

Vamos entender como funcionam as sentenças e porque usamos a palavra const. Nós definimos em veredito que cada sentença é uma alteração sobre a situação presente da pessoa em questão. Traduzindo pra Haskell, a declaração de tipo de veredito é

veredito ::     (a -> j) ->
                (b -> t) ->
                Pessoa a b ->
                j

Nessa declaração, a e b são as situações iniciais de um Forasteiro e de um Cipitense, respectivamente. (a->j) é uma função que transforma essa situação inicial num veredito. Nós definimos Pessoa a b anteriormente. O resultado final da operação toda é um veredito. A declaração de tipo de roubou_galinhas é, como esperado,

roubou_galinhas ::    Pessoa a b ->
                      String

Uma simplificação possível teria sido evitar esse caminho indireto de “mudança de situação” e definir penas constantes:


pena sentença_ruim sentença_boa (Forasteiro a) = sentença_ruim
pena sentença_ruim sentença_boa (Cipitense a) = sentença_boa

Esta simplificação permitira uma definição mais concisa de roubou_galinhas:


roubou_galinhas' = pena "3 anos" "1 mês"

Este ganho putativo em concisão traz em troca uma perda em flexibilidade. Por exemplo, o povo pode ter duas reações a uma pessoa:


viva p = "Viva "++p++", bom companheiro!"
xô p = "Fora "++p++", homem mal-cheiroso e indesejável!"

Assim, o veredito do preconceituoso povo cipitense quanto a uma pessoa pode ser expresso como


gritos = veredito xô viva

Os gritos do povo são os previstos:


> gritos vladimir
"Fora Vladimir Trilopinhense, homem mal-cheiroso e indesejável!"
> gritos mutema
"Viva Mutema Bebum, bom companheiro!"

Um juiz da capital tenderá a julgar de maneira diferente, no entanto, e expedir uma liminar anulando os efeitos do veredito do juiz de Cipitá, com a mesma sentença para o Forasteiro e o Cipitense. Assim, ele envia o seguinte programa para o juiz de Cipitá, que deve executá-lo em obediência à hierarquia de tribunais.


liminar sentença_justa = veredito sentença_justa sentença_justa

Na capital, o juiz se ocupa primariamente de analisar alterações nas penas de presos já no sistema prisional. O sistema dele tem dispositivos parecidos com o de Cipitá:


data Preso a b = MauComportamento a | BomComportamento b
infracaoSimples ajusteSevero ajusteLeve (MauComportamento a) = ajusteSevero a
infracaoSimples ajusteSevero ajusteLeve (BomComportamento a) = ajusteLeve a

À maneira de Cipitá, este sistema pode ser usado para julgar os presos de acordo com seu tipo. Sejam, por exemplo, dois presos e os meses que falta para que saiam:


arrependido_evangelico = BomComportamento 24
rapper_irritante = MauComportamento 24

Seja também uma infração leve, como uma briga no refeitório:


brigou = infracaoSimples (+3) (+1)

Aqui, (+3) e (+1) são funções que adicionam, respectivamente, três meses e um mês à pena do preso em questão. Para fixação, vemos como fica a pena dos presos depois da briga.


> brigou arrependido_evangelico
25
> brigou rapper_irritante
27

Evidentemente, a declaração de tipo de infracaoSimples e brigou são, simplesmente


infracaoSimples :: (a->j) -> (b->j) -> Preso a b -> j
brigou :: Preso Integer Integer -> Integer

Existem, no entanto, infrações mais severas. Uma fuga da prisão certamente elimina o status de BomComportamento. Assim, uma infração severa deve não só aumentar a pena do preso, mas alterar seu tipo. Em Haskell,


infracaoSevera para_o_mau para_o_bom (BomComportamento a) = MauComportamento (para_o_bom a)
infracaoSevera para_o_mau para_o_bom (MauComportamento a) = MauComportamento (para_o_mau a)

Assim, temos, num caso hipotético de fuga,


fugiu = infracaoSevera (+72) (+36)

A interação do juiz com o programa, ao decidir a nova situação dos presos será parecida com o seguinte:


> fugiu rapper_irritante
MauComportamento 96
> fugiu arrependido_evangelico
MauComportamento 60

A declaração de fugiu é:


fugiu :: Preso Integer Integer -> Preso Integer b

Vemos que esta função tem a interessante propriedade de ter como saída o mesmo tipo da entrada. Assim, podemos ver o que acontece se os nossos presos fugirem duas vezes fazendo simplesmente


> (fugiu . fugiu) arrependido_evangelico
MauComportamento 132
> (fugiu . fugiu) rapper_irritante
MauComportamento 168

O sistema simples da cidade de Cipitá está implementado no módulo Data.Either do compilador GHC de Haskell. Pessoa é o tipo de dados Either, Forasteiro e Cipitense são os construtores de dados Left e Right e veredito é a função either.

O sistema em que o “lado” (ou estado) em que um indivíduo está pode mudar em uma função não existe como módulo por default nos compiladores atuais de Haskell, mas vimos no exemplo acima como é simples implementar tipos de dados com propriedades interessantes.

Em termos mais abstratos, definimos as seguintes extensões:

module EitherExts where
import Data.Either

either', trigger, trigger_, switch ::  (a -> b) -> (a -> b) -> Either a a -> Either b b

either' f g (Left x) = Left (f x)
either' f g (Right x) = Right (g x)

trigger f g (Left x) = Left (f x)
trigger f g (Right x) = Left (g x)

trigger_ f g (Left x) = Right (f x)
trigger_ f g (Right x) = Right (g x)

switch f g (Left x) = Right (f x)
switch f g (Right x) = Left (g x)

sure :: (a->b) -> Either a a -> b
sure f = either f f

sure' :: (a->b) -> Either a a -> Either b b
sure' f = either' f f

Este módulo contém as extensões ao tipo Data.Either necessárias para lidar com mudanças de estado em sentido genérico (por exemplo, comida crua e cozida, amigo e inimigo, sólido e fluido, tempo chuvoso ou seco, etc.), sem maiores referências ao sistema prisional.

A prisão pode querer manter registros mais completos sobre o preso. Assim, podemos querer definir um novo módulo específico para o sistema prisional:

module Prisão where
import Data.Either
import EitherExts

type Pena = Int
data Ficha = Ficha {    nome :: String,
                        pena :: Either Pena Pena }

Isto cria pra nós funções de acesso da forma nome :: Ficha -> String.
Definimos agora, ainda no módulo Prisão as categorias de ajuste de pena existentes no código penal:

infracaoLeve f g ficha = ficha { pena = either' f g (pena ficha) }
infracaoSevera f g ficha = ficha { pena = trigger f g (pena ficha) }
-- etc., etc.

Estas funções são bastante similares, e podemos abstraí-las em uma função de ajuste de pena mais geral:

ajustaPena :: (a -> b -> Either Pena Pena -> Either Pena Pena)
              -> a -> b -> Ficha -> Ficha
ajustaPena modo f g ficha = ficha { pena = modo f g (pena ficha) }

Apesar da declaração complicada de tipo, esta função simplesmente aplica uma das funções de mudança de tipo (como trigger e switch) à ficha do preso. Assim, as infrações e outras situações disciplinares podem ser escritas como:

infracaoLeve = ajustaPena either'
infracaoSevera = ajustaPena trigger
boaAção = ajustaPena trigger_
surrealismo = ajustaPena switch

O tipo destas funções (que aliás, não precisa ser declarada; nenhuma função precisa de uma declaração explícita de tipo, mas elas ajudam a manter as idéias em ordem) é

infracaoLeve, infracaoSevera, boaAção, surrealismo ::   (Pena->Pena) ->
                                                        (Pena->Pena) ->
                                                        Ficha ->
                                                        Ficha

Finalmente, definimos como açúcar sintático a seguinte funçãozinha, que é apenas a composição de funções em ordem invertida:

(&) :: (a->b) -> (b->c) -> a -> c
f & g = g . f

Podemos agora escrever nosso código penal da seguinte forma:

fugiu = infracaoSevera (*2) (+36)
brigou = infracaoLeve (+5) (+1)
plantouFlores = boaAcao (-1) (-5)
salvouAVidaDoDiretor = boaAcao (-5) (flip div 2)
cipitensesTomaramBrasilia = surrealismo (*2) (*2)

A história do nosso preso pode ser agora escrita. Por exemplo, podemos ter os presos:

everaldo = Ficha { nome = "Everaldo Normal da Cunha", pena = 100 }
biza = Ficha { nome = "Ferdinando  Bizarro de Sá", pena = 50 }

Exemplos do que o diretor pode fazer com o seu programa:

*Prisão> nome everaldo
"Everaldo Normal da Cunha"
*Prisão> pena biza
Left 50
*Prisão> fugiu everaldo
Ficha {nome = "Everaldo Normal da Cunha", pena = Left 136}
*Prisão> fugiu biza
Ficha {nome = "Ferdinando  Bizarro de Sá", pena = Left 100}
*Prisão> (brigou & fugiu) everaldo
Ficha {nome = "Everaldo Normal da Cunha", pena = Left 137}
*Prisão> (fugiu & brigou) everaldo
Ficha {nome = "Everaldo Normal da Cunha", pena = Left 141}
*Prisão> (plantouFlores & brigou) biza
Ficha {nome = "Ferdinando  Bizarro de Sá", pena = Right 50}
*Prisão> (brigou & plantouFlores) biza
Ficha {nome = "Ferdinando  Bizarro de Sá", pena = Right 54}
*Prisão> (plantouFlores & fugiu) biza
Ficha {nome = "Ferdinando  Bizarro de Sá", pena = Left 85}
*Prisão> (fugiu & plantouFlores) biza
Ficha {nome = "Ferdinando  Bizarro de Sá", pena = Right 99}

Nota-se, por exemplo, que fugir e depois plantar flores deixa o prisioneiro no estado de “bom comportamento”, mas plantar flores e depois fugir o deixa em estado de “mau comportamento”, muito embora esta pena seja numericamente menor que a outra, devido à estrutura do código penal. Como cereja no bolo, definimos uma função de relatório:

relatorio :: Ficha -> String
relatorio ficha = "O preso " ++ nome ficha ++
                                " tem ainda " ++ show (sure id (pena ficha))
                                ++ " meses de pena para cumprir"

Os relatórios podem ser arbitrariamente longos e incluir qualquer outra informação que se tenha convencionado incluir no tipo de dados Ficha. No limite, o computador pode emitir o documento legal inteiro, eliminando milhares de assim-chamados “técnicos judiciários” que custam fortunas à Viúva e à Nação!

*Prisão> relatorio everaldo
"O preso Everaldo Normal da Cunha tem ainda 100 meses de pena para cumprir"
*Prisão> relatorio ((fugiu & plantouFlores) biza)
"O preso Ferdinando  Bizarro de Sá tem ainda 99 meses de pena para cumprir"

Vimos neste tutorial como tipos abstratos de dados permitem incorporar estrutura adicional aos tipos de dados pré-existentes no computador (como os números inteiros Int) e como esta estrutura pode ir sendo propagada desde um nível muito abstrato (os módulos Data.Either e EitherExts) até uma biblioteca de combinadores (funções sobre funções retornando funções) que permite definir uma linguagem específica de domínio (aqui, uma linguagem de histórico prisional) a ser usada pelo legislador (que define uma pena para fugas, por exemplo, como fugiu = infracaoSevera (*2) (+36)) e pelo administrador (que diz simplesmente fugiu everaldo).

Bibliotecas de combinadores mais sofisticadas existem para domínios de aplicação como a música e os derivativos financeiros. Mas mesmo sistemas bem mais simples, como o descrito acima, permitem codificar o conhecimento de especialistas em seus domínios (legislação penal, por exemplo) em uma forma na qual os computadores conseguem pensar.

Powered by WordPress