Depois de semanas de expetativa, tu e os teus amigos reúnem-se para a vossa primeira partida de Dungeons & Dragons (D&D). Como esta é a primeira sessão do jogo, cada jogador tem de criar uma personagem para jogar. Os atributos da personagem são determinados pelo lançamento de dados de 6 faces, mas onde estão os dados? Com um sobressalto, percebes que os teus amigos estão à espera que tu arranjes os dados; afinal de contas, foi tua ideia jogar D&D! Entras em pânico ao perceberes que te esqueceste de trazer os dados, o que significaria não haver jogo de D&D. Como tens alguns conhecimentos básicos de programação, arranjas rapidamente uma solução: vais escrever um programa para simular lançamentos de dados.
Num jogo de Dungeons & Dragons, cada jogador começa por gerar uma personagem com a qual possa jogar. Essa personagem tem, entre outras coisas, seis atributos: strength, dexterity, constitution, intelligence, wisdom e charisma. Estes seis atributos têm valores determinados aleatoriamente. Fazes isto lançando quatro dados de 6 faces e registando a soma dos três dados mais altos. Fazes isto seis vezes, uma vez para cada atributo.
Os hitpoints iniciais da tua personagem são 10 + o modificador de constitution da tua personagem. Encontras o modificador de constitution da tua personagem subtraindo 10 à constitution da tua personagem, dividindo por 2 e arredondando para baixo.
Escreve um gerador aleatório de personagens que siga as regras acima.
Por exemplo, os seis lançamentos de quatro dados podem ser assim:
Como a constitution é 3, o modificador de constitution é -4 e os hitpoints são 6.
A maioria das linguagens de programação tem geradores (pseudo)aleatórios, mas poucas linguagens de programação são feitas para lançar dados. Uma dessas linguagens é Troll.
Nas linguagens funcionais, a maioria das funções é pura. Isto significa que devolvem sempre o mesmo resultado para o mesmo conjunto de argumentos e não fazem mais nada. Por outras palavras, as funções puras são determinísticas.
No entanto, por definição, um valor aleatório é imprevisível e não determinístico. Mesmo a pseudorrandomicidade, ou seja, devolver uma sequência de números que parecem aleatórios, não é assim tão fácil de conseguir num contexto puro. Isto acontece porque um gerador de números pseudorrandómicos (PRNG) precisa de manter um estado interno para devolver o número seguinte da sequência.
O Lean oferece duas abordagens possíveis para este problema:
Usar uma mónade que permita alterar o estado interno de um PRNG.
Existe uma função já disponível, IO.rand, que faz exatamente isso.
Usar funções puras que recebem um gerador como argumento e devolvem não só o valor pseudorrandómico produzido, mas também um gerador atualizado, "preparado" para o número seguinte da sequência.
Este exercício usa a segunda abordagem. É passado um gerador a todas as funções que devem produzir um valor pseudorrandómico, e espera-se que a função devolva tanto o valor como um gerador atualizado.
Repara que um dado gerador é determinístico, ou seja, produz sempre o mesmo valor. Para gerar o valor pseudorrandómico seguinte de uma sequência, é necessário usar o gerador atualizado.
Neste exercício, a aleatoriedade é verificada através de um teste do qui-quadrado com um nível de significância de p < 0.0001.
Isto significa que, se uma pontuação de capacidade for gerada de acordo com as instruções, há menos de 0,01% de hipóteses de uma implementação correta falhar o teste por puro azar.
Repara que, de acordo com as instruções, uma pontuação de capacidade é a soma dos três maiores resultados de quatro lançamentos de um d6 não enviesado (dado de seis faces).
Inscreve-te no Exercism para aprenderes e dominares Lean com 100 exercícios, e mentoria humana real, tudo grátis.
Exploramos como gerar lançamentos de dados (aparentemente) aleatórios. Vais aprender sobre sementes, números pseudoaleatórios e algumas armadilhas comuns. Por fim, vemos porque é que os testes baseados em propriedades são excelentes para testar funções que devolvem valores aleatórios.