Modelando algoritmos complexos com enum
Ricardo da Rocha
Posted on December 24, 2022
Em Rust, os enumerados são tipos muito ricos. Neste vídeo eu dei uma pequena introdução aos enumerados em Rust. É bem comum você encontrar na internet exemplos e tutoriais sobre enumerados para modelar problemas simples. Agora eu vou dar exemplos um pouco mais avançados, mas que não são muito difíceis. Na prática, vamos aplicar a técnica de composição/decomposição usando várias camadas de enumerados.
Para ilustrar o poder dos enumerados, vamos modelar um jogo de xadrez, o que pode ser considerado um problema bastante complexo. Na verdade, nós vamos modelar apenas uma parte bem modesta do jogo, porém repleta de desafios, principalmente se você pensar numa abordagem clássica de algoritmos.
Modelando um jogo de xadrez básico
No xadrez temos seis tipos de peças, mas antes vamos começar com um objetivo bem básico: simular as cores das peças.
enum Cor {
Brancas,
Pretas }
E agora nós podemos declarar as peças
#[derive(Debug, Copy, Clone, PartialEq)]
enum TipoPeca {
Peao,
Cavalo,
Bispo,
Torre,
Dama,
Rei }
De cara eu já anotei uma série de macros para adicionar alguns recursos básicos para o nosso tipo, como por exemplo algumas otimizações da memória. Mas não se preocupe com isso por enquanto.
Um terceiro tipo poderia encapsular os dois primeiros. Esta é uma ótima forma de esconder a complexidade.
#[derive(Copy, Clone)]
struct Peca {
cor: Cor,
peca: TipoPeca
}
+ #[derive(Copy, Clone)]
= enum Cor {
= Brancas,
= Pretas }
E eu posso criar qualquer peça de xadrez assim:
use Cor::*;
use TipoPeca ::*;
let peca1 = Peca {
cor: Brancas,
peca: Cavalo,
}
Representando um tabuleiro
Um tabuleiro é uma matriz de 8X8
o que resulta em 64 casas. Uma casa pode estar ocupada por qualquer peça, mas uma casa também pode estar vazia. É aqui que começa a ficar interessante:
#[derive(Copy, Clone)]
enum Casa{
Ocupada(Peca),
Vazia
}
Veja que, neste caso, estamos abstraindo a seguinte condição lógica
Se a casa está ocupada, então ela possui uma Peça
Senão, a casa está vazia
Isso é muito poderoso e pode ser explorado de uma forma muito particular.
use Casa::*;
var casa: Casa;
...
match casa {
Ocupada(_peca) => print!("♟"),
Vazia => print!("🔲"),
}
Um ponto interessante é que eu posso adicionar fields para um enumerado. Desta forma eu posso identificar qual peça está ocupando aquela casa. No exemplo acima eu criei um único field do tipo Peca
, mas veja que poderiam ser adicionados vários fields dentro da tupla. Também seria possível adicionar fields nomeados, usando a sintaxe de estruturas.
enum Exemplo {
ocupada{peca: Peca, cor: Cor},
vazia(Cor,)
}
Adicionando comportamento
Uma das tendências da programação Rust é separar os algoritmos em duas camadas: A camada de dados e a camada de comportamento. Esta técnicas é, por vezes, chamada de programação orientada a dados. Quando criamos enumerados enum e estruturas struct em geral nós estamos modelando a camada de dados, porque estas estruturas que irão armazenar as variáveis do sistema. E então temos as traits, que permitem criar métodos abstratos, e as cláusulas impl que permitem implementar métodos para as nossas estruturas. Neste artigo nós não vamos abordar traits, porém veremos que também é possível adicionar rotinas de comportamento para enumerados.
impl TipoPeca {
fn display(&self) -> char {
match self {
Peao => '🟤',
Cavalo => '🏇',
Bispo => '🧙',
Torre => '🚩',
Dama => '🌸',
Rei => '👑'
}
}
Se os enumerados simulam uma máquina de estados, os métodos também podem ser compreendidos como funções que alterem o estado da variável.
impl TipoPeca {
fn promover(&self) {
match self {
TipoPeca::Peao => self = TipoPecao::Dama,
outra => panic!("🛑 Esta peça {} não pode ser promovida ", outra.display);
}
}
}
A aplicação final ficaria mais ou menos assim:
use Cor::*;
use TipoPeca ::*;
use Casa::*;
let mut tabuleiro = [Vazia;64];
let torre = Peca{
cor: Brancas,
peca: Torre};
let bispo = Peca{
cor: Brancas,
peca: Bispo};
...
tabuleiro[0] = Ocupada(torre);
tabuleiro[1] = Ocupada(bispo);
for casa in tabuleiro.iter() {
match casa {
Ocupada(p) => print!("{}", p.peca.display),
Vazia => print!("🔲"),
}
}
Aqui está o código ↗ para quem quiser brincar.
Bom jogo! 🦀
Sugestões de leitura
Você pode apoiar o meu livro de Rust no Catarse
Se você gostar deste artigo, é bem provável que também goste desta incrível abordagem que Joshua Cooper sobre a criação de apis e modelagem de documentos JSON com enumerados.
Posted on December 24, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.