- 1. Introdução
- 2. Operadores
- 3. Variáveis
- 4. Funções e modificadores (mutators)
- 5. Laços (loops) e condicionais
- 6. I/O
- 7. Sub-rotinas
- 8. Programação Funcional
- 9. Classes & Objetos
- 10. Tratamento de Exceções
- 11. Expressões Regulares
- 12. Módulos em Raku
- 13. Unicode
- 14. Paralelismo, Concorrência e Assincronicidade
- 15. A Comunidade
Este documento tem como objetivo oferecer uma visão geral da linguagem de programação Raku. Para quem é novo em Raku esse material deve te dar o necessário para começar.
Algumas seções desse documento referenciam outras partes (mais completas e precisas) da documentação do Raku. Leia-as se precisar de mais informações sobre um assunto específico.
Ao longo desse documento você encontrará exemplos para a maior parte dos assuntos discutidos. Para melhor entendê-los, invista tempo reproduzindo todos os exemplos.
Esse trabalho é licenciado sob a Creative Commons Attribution-ShareAlike 4.0 International License. Para ver uma cópia dessa licença, visite
Para contribuir com esse documento acesse:
Todo feedback é bem-vindo:
Se você gostou desse trabalho, clique na estrela do repositório no Github.
-
Inglês: https://raku.guide
-
Alemão: https://raku.guide/de
-
Búlgaro: https://raku.guide/bg
-
Chinês: https://raku.guide/zh
-
Espanhol: https://raku.guide/es
-
Francês: https://raku.guide/fr
-
Holandês: https://raku.guide/nl
-
Indonésio: https://raku.guide/id
-
Italiano: https://raku.guide/it
-
Japonês: https://raku.guide/ja
-
Português: https://raku.guide/pt
-
Russo: https://raku.guide/ru
-
Turco: https://raku.guide/tr
-
Ucraniano: https://raku.guide/uk
Raku é uma linguagem de alto nível, uso geral e com tipagem gradual. Raku é multi paradigma. Ela suporta programação Procedural, Orientada a Objetos e Funcional.
-
TMTOWTDI (Pronuncia-se Tim Toady): Existe mais de uma maneira de se fazer as coisas (there is more than one way to do it).
-
Coisas fáceis devem continuar fáceis, coisas difíceis deveriam se tornar mais fáceis, e coisas impossíveis deveriam ser só mais difíceis.
-
Raku: É a especificação da linguagem com uma suite de testes. Implementações que passem na suite de testes da especificação são consideradas Raku.
-
Rakudo: É um compilador para Raku.
-
Rakudobrew: É um gerenciador de instalações para o Rakudo.
-
Zef: É um instalador de módulos do Raku.
-
Rakudo Star: É um pacote que inclui Rakudo, Zef, uma coleção de módulos Raku, e documentação.
Para instalar o Rakudo Star, execute os seguintes comandos no terminal:
mkdir ~/rakudo && cd $_
curl -LJO https://rakudo.org/latest/star/src
tar -xzf rakudo-star-*.tar.gz
mv rakudo-star-*/* .
rm -fr rakudo-star-*
./bin/rstar install
echo "export PATH=$(pwd)/bin/:$(pwd)/share/perl6/site/bin:$(pwd)/share/perl6/vendor/bin:$(pwd)/share/perl6/core/bin:\$PATH" >> ~/.bashrc
source ~/.bashrc
For other options, go to https://rakudo.org/star/source
Quatro opções estão disponíveis:
-
Siga os mesmos passos listados para instalar no Linux
-
Instale com o homebrew:
brew install rakudo-star
-
Instale com o MacPorts:
sudo port install rakudo
-
Baixe o instalador (arquivo com extensão .dmg) mais recente em http://rakudo.org/downloads/star/
-
Baixe a última versão do instalador (arquivo com extensão .msi) em http://rakudo.org/downloads/star/
Se o seu sistema for 32 bits, baixe o arquivo x86; se for 64 bits, baixe o arquivo x86_64. -
Após a instalação, certifique-se que
C:\rakudo\bin
está na sua variável PATH
-
Pegue a imagem oficial do Docker
docker pull rakudo-star
-
Depois execute o container com a imagem
docker run -it rakudo-star
Executar código Raku pode ser feito usando a REPL (Read-Eval-Print Loop).
Para isso, abra um terminal, digite raku
na janela do terminal
e aperte [Enter]. Isso vai fazer com que um prompt de >
apareça.
Em seguida, digite uma linha de código e aperte [Enter]. A REPL vai
exibir o valor daquela linha. Você pode então digitar outra linha,
ou digitar exit
e aperte [Enter] para sair da REPL.
Como alternativa, escreva seu código em um arquivo, salve e execute-o.
É recomendado que programas em Raku sejam gravados em arquivos com
a extensão .raku
. Execute o arquivo digitando raku nomedoarquivo.raku
na janela do terminal e apertando [Enter]. Ao contrário da REPL, isso
não vai automaticamente imprimir o resultado de cada linha: o código
precisa ter um comando say
para imprimir coisas.
A REPL é usada principalmente para experimentar algum código específico, normalmente uma única linha. Para programas com mais de uma linha é recomendado escrever em um arquivo e executá-lo.
Linhas individuais também podem ser executadas de modo não interativo
na linha de comando digitando raku -e 'seu código aqui'
e apertando
[Enter].
Tip
|
O pacote Rakudo Star inclui um editor de linhas que ajuda a obter o máximo da REPL. Se você instalou apenas o Rakudo em vez do Rakudo Star você provavelmente não tem os recursos de edição de linhas ativado (como usar as setas para cima e para baixo para ver o histórico, esquerda e direita para editar a linha, e auto completar com a tecla TAB). Considere executar o seguinte comando e estará tudo pronto:
|
Como vamos passar um bom tempo escrevendo e gravando programas Raku em arquivos, é bom termos um editor de textos decente que reconheça a sintaxe do Raku.
Eu particularmente uso e recomendo o Atom. É um editor de textos moderno e já vem com reconhecimento de sintaxe em Raku. Raku-fe é um reconhecedor de sintaxe alternativo para o Atom, derivado do pacote original mas com muitas correções e adições.
Versões recentes do Vim já vem com reconhecimento de sintaxe. Emacs e Padre precisam da instalação de pacotes adicionais.
Vamos começar com o ritual alô mundo
.
say 'hello world';
isso também pode ser escrito como:
'hello world'.say;
Raku tem forma livre: Você tem liberdade (na maioria dos casos) para usar qualquer quantidade de espaços em branco.
Declarações são normalmente uma linha lógica de código, e terminam com
um ponto-e-vírgula:
say "Hello" if True;
Expressões são um tipo especial de declaração que retorna um valor:
1+2
retorna 3
Expressões são feitas por Termos e Operadores.
Termos são:
-
Variáveis: Um valor que pode ser manipulado e modificado.
-
Literais: Um valor constante como um número ou string (sequência de caracteres).
Operadores são classificados em tipos:
Tipo |
Explicação |
Exemplo |
Prefixo (Prefix) |
Antes do termo. |
|
Infixo (Infix) |
Entre termos |
|
Sufixo (Postfix) |
Após o termo |
|
Circunfixos (Circumfix) |
Em torno do termo |
|
Pós circunfixos (Postcircumfix) |
Após um termo, em torno de outro |
|
Identificadores são os nomes dados aos termos quando você os define.
-
Precisam começar com um caractere do alfabeto ou um underscore (sublinhado).
-
Precisam conter dígitos (exceto o primeiro caractere).
-
Podem conter hífens ou apóstrofes (exceto os primeiros e últimos caracteres), contanto que haja um caractere alfabético à direita de cada hífen ou apóstrofe.
Válido |
Inválido |
|
|
|
|
|
|
|
|
|
|
-
Camel case:
variableNo1
-
Kebab case:
variable-no1
-
Snake case:
variable_no1
Você está livre para nomear seus identificadores como quiser, mas é uma boa prática adotar uma convenção de forma consistente.
Usar nomes significativos vai facilitar a sua vida (e a de outros) programando.
-
var1 = var2 * var3
é sintaticamente correto mas o propósito não é claro. -
salario-mensal = taxa-diaria * dias-uteis
seria uma forma melhor de nomear suas variáveis.
Um comentário é um pedaço de texto ignorado pelo compilador e usado como uma nota.
Comentários são divididos em 3 tipos:
-
Linha única:
# Isso é uma linha de comentário
-
Embutido:
say #`(Isso é um comentário embutido) "Alô Mundo."
-
Multi linhas:
=begin comment Esse é um comentário de várias linhas. Comentário 1 Comentário 2 =end comment
Strings precisam ser delimitadas por aspas duplas ou aspas simples.
Sempre use aspas duplas:
-
se sua string contém uma apóstrofe (aspas simples).
-
se sua string contém uma variável que precisa ser interpolada.
say 'Alô Mundo'; # Alô Mundo
say "Alô Mundo"; # Alô Mundo
say "Gota d'água"; # Gota d'água
my $name = 'Maria Silva';
say 'Alô $name'; # Alô $name
say "Alô $name"; # Alô Maria Silva
A tabela abaixo lista os operadores mais usados.
Operador | Tipo | Descrição | Exemplo | Resultado |
---|---|---|---|---|
|
|
Adição |
|
|
|
|
Subtração |
|
|
|
|
Multiplicação |
|
|
|
|
Potência |
|
|
|
|
Divisão |
|
|
|
|
Divisão inteira (arredonda para baixo) |
|
|
|
|
Módulo |
|
|
|
|
Divisibilidade |
|
|
|
|
|||
|
|
Máximo denominador comum (mdc) |
|
|
|
|
Menor múltiplo comum (mmc) |
|
|
|
|
Igualdade numérica |
|
|
|
|
Diferente numérico |
|
|
|
|
Menor que |
|
|
|
|
Maior que |
|
|
|
|
Menor ou igual a |
|
|
|
|
Maior ou igual a |
|
|
|
|
Igualdade de string |
|
|
|
|
Diferença de string |
|
|
|
|
Atribuição |
|
|
|
|
Concatenação de strings |
|
|
|
|
|||
|
|
Replicação de strings |
|
|
|
|
|||
|
|
Smart match (equivalência inteligente) |
|
|
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
Incremento |
|
|
|
Incremento |
|
|
|
|
|
Decremento |
|
|
|
Decremento |
|
|
|
|
|
Força o operando para um valor numérico |
|
|
|
|
|||
|
|
|||
|
|
Força o operando para um valor numérico e retorna sua negação |
|
|
|
|
|||
|
|
|||
|
|
Força o operando para um valor booleano |
|
|
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
Força o operando para um valor booleano e retorna sua negação |
|
|
|
|
Construtor de Sequências |
|
|
|
|
Construtor de Sequências |
|
|
|
|
Construtor de Sequências |
|
|
|
|
Construtor de Sequências |
|
|
|
|
Construtor de Sequências |
|
|
|
|
Construtor de Listas Preguiçosas (Lazy Lists) |
|
|
|
|
Achatamento (Flattening) |
|
|
|
|
Adicionar um R
antes de qualquer operador tem o efeito de inverter seus operandos.
Operação Normal | Resultado | Operador Reverso | Resultado |
---|---|---|---|
|
|
|
|
|
|
|
|
Operadores de redução trabalham com listas de valores.
Eles são formados colocando o operador entre colchetes []
Operação Normal | Resultado | Operador de Redução | Resultado |
---|---|---|---|
|
|
|
|
|
|
|
|
Note
|
Para a lista completa de operadores, incluindo sua precedência, visite https://docs.raku.org/language/operators |
Variáveis em Raku são classificadas em 3 categorias: Scalars (escalares), Arrays e Hashes.
Um sigil (símbolo) é um caractere usado como prefixo para categorizar variáveis.
-
$
é usado para scalars -
@
é usado para arrays -
%
é usado para hashes
Um scalar guarda um valor ou uma referência.
#String
my $nome = 'Maria Silva';
say $nome;
#Número
my $idade = 99;
say $idade;
Um grupo específico de operações pode ser realizada em um scalar, dependendo do valor que ele armazena.
my $nome = 'Maria Silva';
say $nome.uc;
say $nome.chars;
say $nome.flip;
MARIA SILVA
11
avliS airaM
Note
|
Para a lista completa de métodos aplicáveis a Strings, veja https://docs.raku.org/type/Str |
my $idade = 17;
say $idade.is-prime;
True
Note
|
Para a lista completa de métodos aplicáveis a Inteiros, visite https://docs.raku.org/type/Int |
my $idade = 2.3;
say $idade.numerator;
say $idade.denominator;
say $idade.nude;
23
10
(23 10)
Note
|
Para a lista completa de métodos aplicáveis a Números Racionais, visite http://doc.raku.org/type/Rat |
Arrays são listas contendo vários valores.
my @animais = 'camelo','lhama','coruja';
say @animais;
Muitas operações podem ser feitas em arrays como mostrado no exemplo abaixo:
Tip
|
O til ~ é usado para concatenar strings.
|
Script
my @animais = 'camelo','vicunha','lhama';
say "O zoológico contém " ~ @animals.elems ~ " animais";
say "Os animais são: " ~ @animais;
say "Vou adotar uma coruja para o zoo";
@animais.push("coruja");
say "Agora o zoo tem: " ~ @animals;
say "O primeiro animal adotado foi o " ~ @animais[0];
@animais.pop;
say "Infelizmente a coruja fugiu e ficamos com: " ~ @animais;
say "Vamos fechar o zoológico e manter apenas um animal";
say "Vamos soltar: " ~ @animais.splice(1,2) ~ " e ficar com o " ~ @animais;
Saída
O zoológico contém 3 animais
Os animais são: camelo vicunha lhama
Vou adotar uma coruja para o zoo
Agora o zoo tem: camelo vicunha lhama coruja
O primeiro animal adotado foi o camelo
Infelizmente a coruja fugiu e ficamos com: camelo vicunha lhama
Vamos fechar o zoológico e manter apenas um animal
Vamos soltar: vicunha lhama e ficar com o camelo
.elems
retorna o número de elementos em um array.
.push()
adiciona um elemento no final do array.
Podemos acessar um elemento específico do array especificando sua posição, como @animals[0]
.
.pop
remove o último elemento do array.
.splice(a,b)
remove os b
elementos que estão a partir da posição a
.
Um array básico é declarado assim:
my @array;
O array básico pode ter qualquer tamanho e por isso é chamado de auto-extensível.
Esses arrays aceitam qualquer quantidade de elementos sem restrição.
Por outro lado, também podemos criar arrays de tamanho fixo.
Esses arrays não podem ser acessados em posições acima do seu tamanho predefinido.
Para declarar um array com tamanho fixo, especifique o número máximo de elementos entre chaves imediatamente após o nome:
my @array[3];
Esse array será capaz de armazenar no máximo 3 valores, com índice de 0 a 2.
my @array[3];
@array[0] = "primeiro valor";
@array[1] = "segundo valor";
@array[2] = "terceiro valor";
Você não conseguirá adicionar um quarto valor nesse array:
my @array[3];
@array[0] = "primeiro valor";
@array[1] = "segundo valor";
@array[2] = "terceiro valor";
@array[3] = "quarto valor";
Index 3 for dimension 1 out of range (must be 0..2)
Os arrays que vimos até agora são unidimensionais.
Felizmente, podemos definir arrays multidimensionais em Raku.
my @tbl[3;2];
Esse array é bidimensional. A primeira dimensão pode ter até 3 valores e a segunda dimensão até 2 valores.
Pense nele como uma matriz 3x2
my @tbl[3;2];
@tbl[0;0] = 1;
@tbl[0;1] = "x";
@tbl[1;0] = 2;
@tbl[1;1] = "y";
@tbl[2;0] = 3;
@tbl[2;1] = "z";
say @tbl
[[1 x] [2 y] [3 z]]
[1 x]
[2 y]
[3 z]
Note
|
Para uma referência completa sobre Arrays, veja https://docs.raku.org/type/Array |
my %capitais = ('Inglaterra','Londres','Alemanha','Berlim');
say %capitais;
my %capitais = (Inglaterra => 'Londres', Alemanha => 'Berlim');
say %capitais;
Alguns métodos que podem ser chamados em hashes são:
Script
my %capitais = (Inglaterra => 'Londres', Alemanha => 'Berlim');
%capitais.push: (França => 'Paris');
say %capitais.kv;
say %capitais.keys;
say %capitais.values;
say "A capital da França é: " ~ %capitais<França>;
Saída
(França Paris Alemanha Berlim Inglaterra Londres)
(França Alemanha Inglaterra)
(Paris Berlim Londres)
A capital da França é: Paris
.push: (chave => 'Valor')
adiciona um novo par chave/valor.
.kv
retorna uma lista contendo todas as chaves e valores.
.keys
retorna uma lista contendo todas as chaves.
.values
retorna uma lista contendo todos os valores.
Podemos acessar um valor específico no hash especificando sua chave %hash<chave>
Note
|
Para uma referência completa sobre Hashes, veja https://docs.raku.org/type/Hash |
Nos exemplos anteriores, nós não especificamos o tipo dos valores que as variáveis devem conter.
Tip
|
.WHAT retorna o tipo do valor contido em uma variável.
|
my $var = 'Texto';
say $var;
say $var.WHAT;
$var = 123;
say $var;
say $var.WHAT;
Como você pode ver no exemplo acima, o tipo do valor em $var
foi primeiro (Str) e depois (Int).
Esse estilo de programação é chamado tipagem dinâmica. Dinâmico no sentido de que as variáveis podem conter valores de qualquer tipo.
Agora tente executar o seguinte exemplo:
Note o Int
antes do nome da variável.
my Int $var = 'Texto';
say $var;
say $var.WHAT;
Ele vai falhar e retornar a mensagem de erro: Type check failed in assignment to $var; expected Int but got Str
(Verificação de tipo falhou em atribuição a $var; esperava Int mas recebeu Str)
O que aconteceu foi que especificamos que aquela variável deve ser do tipo (Int). Quando tentamos atribuír uma (Str) nela, a operação falhou.
Esse estilo de programação é chamado tipagem estática. Estática no sentido de que o tipo das variáveis é definido antes das atribuições e não pode mudar.
Raku é classificado como uma linguagem com tipagem gradual; ela permite tanto tipagem estática quanto dinâmica.
my Int @array = 1,2,3;
say @array;
say @array.WHAT;
my Str @multilingual = "Alô","Hello","Salut","Hallo","您好","안녕하세요","こんにちは";
say @multilingual;
say @multilingual.WHAT;
my Str %capitais = (Inglaterra => 'Londres', Alemanha => 'Berlim');
say %capitais;
say %capitais.WHAT;
my Int %código-geográfico = (Inglaterra => 44, Alemanha => 49);
say %código-geográfico;
say %código-geográfico.WHAT;
Você provavelmente nunca usará os dois primeiros, mas eles foram listados para fins informacionais.
|
|
|
|
|
|
||
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Introspec’ão é o processo de obter informações sobre as propriedades de um objeto, como seu tipo.
Em um dos exemplos acima usamos .WHAT
para retornar o tipo de uma variável.
my Int $var;
say $var.WHAT; # (Int)
my $var2;
say $var2.WHAT; # (Any)
$var2 = 1;
say $var2.WHAT; # (Int)
$var2 = "Alô";
say $var2.WHAT; # (Str)
$var2 = True;
say $var2.WHAT; # (Bool)
$var2 = Nil;
say $var2.WHAT; # (Any)
O tipo de uma variável armazenando determinado valor é correlacionado ao valor em si.
O tipo de uma variável vazia fortemente declarada é o tipo com que foi declarada.
O tipo de uma variável vazia que não foi fortemente declarada é (Any)
Para limpar o valor de uma variável, atribua Nil
a ela.
Antes de usar uma variável pela primeira vez, ela precisa ser declarada.
Há muitas formas de se declarar variáveis em Raku, e my
é a que utilizamos até agora em todos os exemplos acima.
my $var=1;
O declarador my
dá à variável um escopo léxico.
Em outras palavras, a variável só será acessível no mesmo bloco em que foi declarada.
Um bloco em Raku é delimitado por { }
.
Se nenhum bloco é encontrado, a variável estará disponível em qualquer parte do programa Raku.
{
my Str $var = 'Texto';
say $var; # está acessível
}
say $var; # não está acessível, retorna erro
Como uma variável só pode ser acessada no bloco em que é definida, outra variável com o mesmo nome pode ser definida em outro bloco.
{
my Str $var = 'Texto';
say $var;
}
my Int $var = 123;
say $var;
Vimos nos exemplos anteriores como atribuir valores a variáveis.
Atribuições são feitas usando o operador =
.
my Int $var = 123;
say $var;
Podemos mudar o valor atribuido a uma variável:
my Int $var = 123;
say $var;
$var = 999;
say $var;
Saída
123
999
Por outro lado, não podemos mudar valores vinculados a uma variável.
Vinculação (ou "Binding") é feito usando o operador :=
.
my Int $var := 123;
say $var;
$var = 999;
say $var;
Saída
123
Cannot assign to an immutable value
my $a;
my $b;
$b := $a;
$a = 7;
say $b;
$b = 8;
say $a;
Saída
7
8
Vinculação de variáveis, como você já percebeu, é bidirecional.
$a := $b
e $b := $a
têm o mesmo efeito.
Note
|
Para mais informações sobre variáveis, veja https://docs.raku.org/language/variables |
É importante diferenciar funções de modificadores.
Funções não mudam o estado inicial do objeto onde foram chamadas.
Já modificadores mudam o estado de um objeto ao serem invocados.
Script
my @números = [7,2,4,9,11,3];
@números.push(99);
say @números; #1
say @números.sort; #2
say @números; #3
@números.=sort;
say @números; #4
Saída
[7 2 4 9 11 3 99] #1
(2 3 4 7 9 11 99) #2
[7 2 4 9 11 3 99] #3
[2 3 4 7 9 11 99] #4
.push
é um mutator, ele modifica o estado do array (#1)
.sort
é uma função, ela retorna uma lista ordenada mas não modifica o estado do array inicial:
-
(#2) mostra que retornou um array ordenado.
-
(#3) mostra que o array inicial não foi modificado.
Para forçar uma função a agir como um modificador, usamos .=
em vez de .
(#4) (Linha 9 do script)
Raku possui uma série de construções para loops e condicionais.
O código é executado somente se a condição foi atingida, ou em outras palavras se a expressão retornar True
(verdadeiro).
my $idade = 19;
if $idade > 18 {
say 'Bem-vinda'
}
Em Raku podemos inverter o código e a condição.
Mesmo se o código e a condição estiverem invertidos, a condição é sempre avaliada antes.
my $idade = 19;
say 'Bem-vinda' if $idade > 18;
Se a condição não foi atingida, podemos especificar blocos com alternativas usando:
-
else
-
elsif
# experimente executar o mesmo código para diferentes valores da variável
my $número-de-assentos = 9;
if $número-de-assentos <= 5 {
say 'Sou um sedã'
} elsif $número-de-assentos <= 7 {
say 'Sou um carro de 7 lugares'
} else {
say 'Sou uma van'
}
A negação de um if pode ser escrita usando unless
.
O código a seguir:
my $sapatos-limpos = False;
if not $sapatos-limpos {
say 'Limpe seus sapatos'
}
pode ser escrito como:
my $sapatos-limpos = False;
unless $sapatos-limpos {
say 'Limpe seus sapatos'
}
Negação em Raku é feita usando !
ou not
.
unless (condição)
é usado em vez de if not (condição)
.
unless
não pode ter bloco de else
.
with
funciona como um if
, mas verifica se a variável está definida.
my Int $var=1;
with $var {
say 'Alô'
}
Se você executar o código acima sem atribuir um valor para a variável, o bloco não será executado.
my Int $var;
with $var {
say 'Alô'
}
without
é a versão negada de with
. É como a relação do if
com o unless
.
Se a primeira condição de um with
não for atingida, uma alternativa pode ser especificada usando orwith
.
with
e orwith
podem ser comparadas a if
e elsif
.
O laço for
itera sobre vários valores.
my @array = [1,2,3];
for @array -> $item {
say $item * 100
}
Note que criamos uma variável de iteração $item
para realizar a operação *100
em cada item do array.
given
é o equivalente em Raku da função "switch" de outras linguagens.
my $var = 42;
given $var {
when 0..50 { say 'Menor ou igual a 50'}
when Int { say "é um Int" }
when 42 { say 42 }
default { say "oi?" }
}
Ao casar com uma condição, ele para de testar as outras condições.
Como alternativa, usar proceed
vai instruir o Raku a continuar tentando casar com outras condições mesmo após a execução da condição atual.
my $var = 42;
given $var {
when 0..50 { say 'Menor ou igual a 50';proceed}
when Int { say "é um Int";proceed}
when 42 { say 42 }
default { say "huh?" }
}
loop
é uma outra forma de fazer um laço do tipo for
.
De fato, loop
é como o for
é escrito em linguagens da família do C.
Raku pertence à família de linguagens como C.
loop (my $i = 0; $i < 5; $i++) {
say "O valor atual é $i"
}
Note
|
Para mais informações sobre laços e condicionais, veja https://docs.raku.org/language/control |
Em Raku, as duas interfaces mais comuns para Entrada/Saída (Input/Output ou simplesmente I/O) são o Terminal e Arquivos.
say
escreve na saída padrão, adicionando uma quebra de linha no final. Em outras palavras, a saída do código:
say 'Olá Senhora.';
say 'Olá Senhor.';
será escrita em 2 linhas separadas.
print
por outro lado é como o say
mas não adiciona a quebra de linha.
Experimente trocar say
por print
no programa acima e compare os dois resultados.
get
é usado para capturar a entrada do terminal.
my $nome;
say "Oi, qual o seu nome?";
$name = get;
say "Prezado(a) $name bem-vindo(a) ao Raku";
Quando o código acima for executado, o terminal vai ficar esperando para que você coloque seu nome e aperte a tecla [Enter]. Depois disso, ele vai te cumprimentar.
Duas sub-rotinas podem ser usadas para executar comandos externos:
-
run
Executa um comando externo sem envolver a shell -
shell
Executa o comando pela shell do sistema. Depende, portanto, da plataforma e da shell utilizada. Todos os metacaracteres da shell serão interpretados pela própria shell, incluindo pipes, redirecionamentos, substituições de variáveis de ambiente e assim por diante.
my $name = 'Neo';
run 'echo', "olá $name";
shell "ls";
shell "dir";
echo
e ls
são palavras chave comuns de shell em Linux.
echo
exibe texto no terminal (equivalente ao print
no Raku)
ls
lista todos os arquivos e diretórios no diretório atual
dir
é o equivalente a ls
no Windows.
slurp
é usado para ler dados de arquivos.
Crie um arquivo de texto com o seguinte conteúdo:
João 9
José 7
Joana 8
Maria 7
my $data = slurp "resultados.txt";
say $data;
Raku pode listar o conteúdo de um diretório sem precisar executar comandos da shell (usando ls
) como visto em um exemplo anterior.
say dir; # Lista arquivos e diretórios no diretório atual
say dir "/Documents"; # Lista arquivos e diretórios no diretório especificado
Você também pode criar e apagar diretórios.
mkdir "novapasta";
rmdir "novapasta";
mkdir
cria um novo diretório.
rmdir
remove um diretório vazio. Retorna erro se não estiver vazio.
Você também pode verificar se um determinado caminho existe, se é um arquivo ou um diretório:
No diretório onde você vai executar o script abaixo, crie um diretório vazio pasta123
e um arquivo vazio script123.raku
say "script123.raku".IO.e;
say "folder123".IO.e;
say "script123.raku".IO.d;
say "folder123".IO.d;
say "script123.raku".IO.f;
say "folder123".IO.f;
IO.e
verifica se o diretório/arquivo existe.
IO.f
verifica se o caminho é um arquivo.
IO.d
verifica se o caminho é um diretório.
Warning
|
Usuários de Windows podem usar / ou \\ para definir diretórios.C:\\rakudo\\bin C:/rakudo/bin |
Note
|
Para mais informação sobre I/O, veja https://docs.raku.org/type/IO |
Sub-rotinas (também chamadas de subs ou funções) são formas de agrupar um conjunto de funcionalidades.
A definição de uma sub-rotina começa com a palavra-chave sub
. Após sua definição, elas podem ser chamadas pelo nome.
Veja o exemplo abaixo:
sub saudação-alienígena {
say "Olá, terráqueos";
}
saudação-alienígena;
O exemplo anterior mostra uma sub-rotina que não recebe nenhum dado de entrada.
Muitas sub-rotinas pedem alguma entrada para funcionar. Essa entrada é fornecida via argumentos. O número e tipo de argumentos que essa sub-rotina aceita é chamada de assinatura da sub-rotina.
A sub-rotina abaixo aceita uma string como argumento.
sub diga-oi (Str $nome) {
say "Oi " ~ $nome ~ "!!!!"
}
diga-oi "Paulo";
diga-oi "Paula";
É possível definir várias sub-rotinas com o mesmo nome mas com assinaturas diferentes.
Quando a sub-rotina é chamada, o ambiente de execução vai decidir qual versão usar dependendo do número e do tipo dos argumentos fornecidos.
Esse tipo de subrotina é definido exatamente como uma sub normal, apenas trocando a palavra sub
por multi
.
multi cumprimento($nome) {
say "Bom dia $nome";
}
multi cumprimento($nome, $título) {
say "Bom dia $título $nome";
}
cumprimento "João";
cumprimento "Laura","Sra.";
Se uma sub-rotina é definida para aceitar um argumento, e fazemos a chamada sem fornecer o argumento, ela falha.
Como alternativa, o Raku nos dá a possibilidade de definir sub-rotinas com:
-
Argumentos opcionais
-
Argumentos padrão
Argumentos opcionais são definidos colocando ?
no final do nome do argumento.
sub diga-oi($nome?) {
with $nome { say "Oi " ~ $nome }
else { say "Oi humano" }
}
diga-oi;
diga-oi("Laura");
Se o usuário não fornece o argumento, ele pode ser definido com um valor padrão específico.
Isso é feito atribuindo um valor ao argumento durante a definição da sub-rotina.
sub diga-oi($nome="Miguel") {
say "Oi " ~ $nome;
}
diga-oi;
diga-oi("Laura");
Todas as sub-rotinas que vimos até agora fazem alguma coisa, elas exibem algum texto no terminal.
Embora isso seja perfeitamente normal, às vezes queremos que a sub-rotina retorne um valor que possamos usar mais tarde no fluxo do nosso programa.
Em circunstâncias normais, a última linha de código de uma sub-rotina é considerada seu valor de retorno.
sub ao-quadrado ($x) {
$x ** 2;
}
say "7 ao quadrado é " ~ ao-quadrado(7);
A medida que nosso código vai ficando maior, pode ser uma boa ideia especificar explicitamente o que queremos retornar.
Isso pode ser feito usando a palavra-chave return
.
sub ao-quadrado ($x) {
return $x ** 2;
}
say "7 ao quadrao é " ~ ao-quadrado(7);
Em um dos nossos exemplos anteriores, vimos como podemos aceitar somente argumentos de determinados tipos. O mesmo pode ser feito com valores de retorno.
Para restringir o valor de retorno para um tipo específico, podemos usar tanto o atributo returns
ou a notação de seta -->
na assinatura.
sub ao-quadrado ($x) returns Int {
return $x ** 2;
}
say "1.2 ao quadrado é " ~ ao-quadrado(1.2);
sub ao-quadrado ($x --> Int) {
return $x ** 2;
}
say "1.2 ao quadrado é " ~ ao-quadrado(1.2);
Se o valor retornado não casar com o tipo especificado, um erro será lançado.
Type check failed for return value; expected Int but got Rat (1.44)
Tip
|
Restrições de tipo podem não só controlar o tipo dos valores de retorno, como também se o valor está definido ou não. Nos exemplos anteriores, especificamos que o valor de retorno deve ser Dito isso, é considerada boa prática usar esse tipo de restrição. sub ao-quadrado ($x --> Int:D) {
return $x ** 2;
}
say "1.2 ao quadrado é " ~ ao-quadrado(1.2); |
Note
|
Para mais informações sobre sub-rotinas e funções, veja http://doc.raku.org/language/functions |
Nesse capítulo vamos olhar para algumas das funcionalidades que facilitam Programação Funcional.
Funções e sub-rotinas são cidadãs de primeira classe:
-
Elas podem ser passadas como argumento
-
Elas podem ser retornadas por outra função
-
Elas podem ser atribuídas a uma variável
Um bom exemplo para demonstrar esse conceito é a função map
.
map
é uma função de ordem superior (higher-order function), que recebe outra função como argumento.
my @array = <1 2 3 4 5>;
sub ao-quadrado($x) {
$x ** 2
}
say map(&ao-quadrado,@array);
(1 4 9 16 25)
Definimos a sub-rotina chamada ao-quadrado
, que vai elevar a 2 qualquer número fornecido como argumento.
Depois, usamos map
uma função de ordem superior, e passamos dois argumentos, uma sub-rotina e um array.
O resultado é uma lista com todos os elementos do array elevados ao quadrado.
Note que ao passar uma sub-rotina como argumento, precisamos prefixar seu nome com &
.
Todos os objetos no Raku são closures, o que significa que eles podem referenciar variáveis léxicas de escopos externos.
Uma função anônima também é chamada de uma lambda.
Uma função anônima não está associada a um identificador (ela não tem nome).
Vamos reescrever o exemplo do map
usando uma função anônima
my @array = <1 2 3 4 5>;
say map(-> $x {$x ** 2},@array);
Note que em vez de declarar a sub-rotina e passá-la como argumento ao map
, nós a definimos diretamente dentro do map
.
A sub-rotina anônima -> $x {$x ** 2}
não possui handle e não pode ser chamada.
No linguajar Raku chamamos essa notação de pointy block (bloco pontiagudo)
my $ao-quadrado = -> $x {
$x ** 2
}
say $ao-quadrado(9);
Em Raku, métodos podem ser encadeados, você não precisa mais passar o resultado de um método para outro como argumento.
Vamos imaginar que você recebey um array de valores. Pediram para que você retorne os valores únicos desse array, ordenados do maior para o menor.
Você pode tentar resolver esse problema escrevendo algo como isso:
my @array = <7 8 9 0 1 2 4 3 5 6 7 8 9>;
my @array-final = reverse(sort(unique(@array)));
say @array-final;
Primeiro chamamos a função unique
em @array
, então passamos o resultado como argumento para sort
e depois passamos o resultado da ordenação para reverse
.
Em contraste com o exemplo acima, o encadeamento de métodos é permitido em Raku.
O exemplo acima pode ser escrito como a seguir, tirando vantagem do encadeamento de métodos (method chaining):
my @array = <7 8 9 0 1 2 4 3 5 6 7 8 9>;
my @array-final = @array.unique.sort.reverse;
say @array-final;
É fácil perceber que encadeamento de métodos é mais agradável aos olhos.
O operador de alimentação, chamado de pipe em algumas linguagens de programação funcional, permite uma visualização ainda melhor do encadeamento de métodos.
my @array = <7 8 9 0 1 2 4 3 5 6 7 8 9>;
@array ==> unique()
==> sort()
==> reverse()
==> my @array-final;
say @array-final;
Comece com `@array` então retorne uma lista de elementos únicos
então ordene a lista
então inverta a lista
então guarde o resultado em @array-final
Como você pode ver o fluxo das chamadas é de cima para baixo.
my @array = <7 8 9 0 1 2 4 3 5 6 7 8 9>;
my @array-final-v2 <== reverse()
<== sort()
<== unique()
<== @array;
say @array-final-v2;
A alimentação para trás é como a alimentação para frente, mas escrita ao contrário.
O fluxo das chamadas é de baixo para cima.
O hiperoperador (hyper operator) >>.
chama um método em todos os elementos de uma lista e retorna uma lista com todos os resultados.
my @array = <0 1 2 3 4 5 6 7 8 9 10>;
sub é-par($var) { $var %% 2 };
say @array>>.is-prime;
say @array>>.&é-par;
Usando o hiperoperador podemos chamar métodos já definidos no Raku, como is-prime
que nos diz se um número é primo ou não.
Além disso podemos definir novas sub-rotinas e chamá-las usando o hiperoperador. Nesse caso precisamos prefixar um &
ao nome do método. Por exemplo, &é-par
Isso é muito prático pois nos poupa de ter que escrever loops for
para iterar sobre cada valor de um array.
Uma junction é uma sobreposição lógica de valores.
No exemplo abaixo 1|2|3
é uma junction.
my $var = 2;
if $var == 1|2|3 {
say "A variável é 1 ou 2 ou 3"
}
O uso de junctions normalmente ativa autothreading; a operação é executada para cada elemento da junction, e todos os resultados são combinados em uma nova junction e retornados.
Uma lista preguiçosa (lazy) é uma lista que é avaliada de forma preguiçosa.
Avaliação preguiçosa atrasa a avaliação de uma expressão até ela ser necessária, e evita repetir avaliações armazenando resultados em uma tabela de referência.
Os benefícios incluem:
-
Aumento no desempenho evitando cálculos desnecessários
-
Capacidade de construir estruturas de dados potencialmente infinitas
-
Capacidade de definir um fluxo de controle
Para construir uma lista preguiçosa usamos o operador infixo …
Uma lista preguiçosa tem elementos iniciais, um gerador e um final (endpoint).
my $lazy = (1 ... 10);
say $lazy;
O elemento inicial é 1 e o final é 10. Nenhum gerador foi definido então o gerador padrão é o sucessor (+1)
Em outras palavras essa lista preguiçosa pode retornar (se pedido) os seguintes elementos (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
my $lazy = (1 ... Inf);
say $lazy;
Essa lista pode retornar (se pedido) qualquer inteiro entre 1 e infinito, em outras palavras qualquer número inteiro.
my $lazy = (0,2 ... 10);
say $lazy;
Os elementos iniciais são 0 e 2 e o final é 10.
Nenhum gerador foi definido, mas usando os elementos iniciais, Raku vai deduzir que o gerador é (+2)
Essa lista preguiçosa pode retornar (se pedido) os seguintes elementos (0, 2, 4, 6, 8, 10)
my $lazy = (0, { $_ + 3 } ... 12);
say $lazy;
Nesse exemplo, definimos explicitamente um gerador entre { }
Essa lista preguiçosa pode retornar (se pedido) os seguintes elementos (0, 3, 6, 9, 12)
Warning
|
Ao usar um gerador explícito, o elemento final deve ter um valor que o gerador possa retornar. Como alternativa, você pode trocar Isso não vai parar o gerador
my $lazy = (0, { $_ + 3 } ... 10);
say $lazy; Isso vai parar o gerador
my $lazy = (0, { $_ + 3 } ...^ * > 10);
say $lazy; |
No capítulo anterior, aprendemos como Raku facilita Programação Funcional.
Neste capítulo veremos programação Orientada a Objetos em Raku.
Orientação a Objetos é um dos paradigmas mais utilizados hoje em dia.
Um objeto é um conjunto de variáveis e sub-rotinas agrupadas.
As variáveis são chamadas atributos e as sub-rotinas são chamadas métodos.
Atributos definem o estado de um objeto e métodos definem seu comportamento.
Uma classe define a estrutura de um grupo de objetos.
Para entender esse relacionamento considere o exemplo abaixo:
Há 4 pessoas em uma sala |
objetos ⇒ 4 pessoas |
Essas 4 pessoas são humanas |
classe ⇒ Humano |
Elas têm nomes, idades, sexos e nacionalidades diferentes |
atributos ⇒ nome, idade, sexo, nacionalidade |
No linguajar de orientação a objetos, dizemos que objetos são instâncias de uma classe.
Considere o script abaixo:
class Humano {
has $nome;
has $idade;
has $sexo;
has $nacionalidade;
}
my $joão = Humano.new(nome => 'João', idade => 23, sexo => 'M', nacionalidade => 'Brasileiro');
say $joão;
A palavra-chave class
é usada para definir uma classe.
A palavra-chave has
é usada para definir atributos de uma classe.
O método .new()
é chamado de construtor. Ele cria um objeto como uma instância da classe que o chamou.
O script acima, uma nova variável $joão
guarda uma referência para uma nova instância de "Humano" definida por Humano.new()
.
Os argumentos passados para o método .new()
são usados para dar valores aos atributos do novo objeto.
Podemos dar escopo léxico a uma classe usando my
:
my class Humano {
}
Encapsulamento é um conceito de orientação a objetos que agrupa um conjunto de dados e métodos.
Os dados (atributos) dentro de um objeto devem ser privados. Em outras palavras, acessíveis somente de dentro do objeto.
Para acessar os atributos de fora do objeto usamos métodos que chamamos de acessores (ou accessors, em inglês).
Os dois scripts abaixo fazem a mesma coisa.
my $var = 7;
say $var;
my $var = 7;
sub sayvar {
$var;
}
say sayvar;
O método sayvar
é um accessor. Ele nos permite acessar o valor de uma variável de forma indireta.
Encapsulamento é facilitado em Raku pelo uso dos twigils.
Twigils são sigils (símbolos) segundários. Eles vem entre o símbolo da variável e o nome do atributo.
Dois twigils são usados em classes:
-
!
é usado para declarar explicitamente que o atributo é privado. -
.
é usado para gerar automaticamente um acessor para o atributo.
Por padrão, todos os atributos são privados, mas é considerado boa prática sempre usar o twigil !
.
Considerando isso, deveríamos reescrever a classe acima de outro jeito:
class Humano {
has $!nome;
has $!idade;
has $!sexo;
has $!nacionalidade;
}
my $joão = Humano.new(nome => 'João', idade => 23, sexo => 'M', nacionalidade => 'Brasileiro');
say $john;
Adicione ao final desse script a declaração: say $joão.idade;
Ela vai retornar o seguinte erro: Method 'idade' not found for invocant of class 'Humano'
("Método 'idade' não encontrado para classe invocante 'Humano'")
A razão é que $!idade
é privado e só pode ser usado de dentro do objeto.
Tentar acessar atributos privados de fora do objeto vai retornar um erro.
Agora substitua has $!idade
por has $.idade
e veja o resultado de say $joão.idade;
Em Raku, todas as classes herdam o construtor padrão .new()
.
Ele pode ser usado para criar objetos chamando-o com alguns argumentos.
O construtor padrão só aceita argumentos por nome.
Considerando o exemplo acima, repare que todos os argumentos passados para .new()
são definidos pelo próprio nome:
-
nome => 'João'
-
idade => 23
E se não quiséssemos passar o nome de cada atributo ao criar um novo objeto?
Para isso precisamos criar um outro construtor que aceite argumentos por posição.
class Humano {
has $.nome;
has $.idade;
has $.sexo;
has $.nacionalidade;
#novo construtor que sobrescreve o construtor padrão.
method new ($nome,$idade,$sexo,$nacionalidade) {
self.bless(:$nome,:$idade,:$sexo,:$nacionalidade);
}
}
my $joão = Humano.new('João',23,'M','Brasileiro');
say $joão;
Métodos são as sub-rotinas de um objeto.
Como sub-rotinas normais, eles são a forma de agrupar um conjunto de funcionalidades, eles aceitam argumentos, possuem uma assinatura e podem ser definidos como multi.
Métodos são definidos usando a palavra-chave method
.
Em circunstâncias normais, métodos são necessários para fazer alguma ação com os atributos de um objeto.
Isso reforça o conceito de encapsulamento. Atributos de objetos só podem ser manipulados usando métodos.
O mundo externo só pode interagir com os métodos de um objeto, e não tem acesso direto aos seus atributos.
class Humano {
has $.nome;
has $.idade;
has $.sexo;
has $.nacionalidade;
has $.maioridade;
method avalia-maioridade {
if self.idade < 21 {
$!maioridade = 'Não'
} else {
$!maioridade = 'Sim'
}
}
}
my $joão = Humano.new(nome => 'João', idade => 23, sexo => 'M', nacionalidade => 'Brasileiro');
$joão.avalia-maioridade;
say $joão.maioridade;
Depois que os métodos foram definidos em uma classe, eles podem ser chamados em um objeto usando a notação de ponto:
objeto . método ou como no exemplo acima: $joão.avalia-maioridade
Dentro da definição de um método, se precisamos referenciar o próprio objeto para chamar outro método usamos a palavra-chave self
.
Dentro da definição de um método, se precisarmos referenciar um atributo usamos !
mesmo que ele tenha sido definido com .
O raciocínio é que o que o .
faz é declarar um atributo com !
e criar um accessor automaticamente.
No exemplo acima if self.idade < 21
e if $!idade < 21
têm o mesmo efeito, mas são tecnicamente diferentes:
-
self.idade
chama o método.idade
(accessor)
Pode ser escrito alternativamente como$.idade
-
$!idade
é uma chamada direta à variável
Métodos normais podem ser chamados em objetos do lado de fora da classe.
Métodos privados são métodos que só podem ser chamados de dentro da classe.
Um possível caso de uso seria um método que chama outro para uma ação específica.
O método que faz interface com o mundo externo é público enquanto o que é referenciado deve ficar privado.
Nós não queremos usuários chamando-o diretamente, então o declaramos como privado.
A declaração de um método privado exige o uso do twigil !
antes do seu nome.
Métodos privados são chamados com !
em vez de .
method !sou-privado {
# código entra aqui
}
method sou-público{
self!sou-privado;
#faça outras coisas
}
Atributos de Classe são atributos que pertencem à própria classe e não aos seus objetos.
Eles podem ser inicializados durante a definição.
Atributos de Classe são declarados com my
em vez de has
.
Eles são chamados na classe em si em vez de nos objetos.
class Humano {
has $.nome;
my $.contador = 0;
method new($nome) {
Humano.contador++;
self.bless(:$nome);
}
}
my $a = Humano.new('a');
my $b = Humano.new('b');
say Humano.contador;
Até agora todos os exemplos que vimos usaram accessors para obter informações dos atributos de objetos.
E se quiséssemos modificar o valor de um atributo?
Precisamos classificar o atributo como leitura/escrita (read/write) usando as palavras-chave is rw
class Humano {
has $.nome;
has $.idade is rw;
}
my $joão = Humano.new(nome => 'João', idade => 21);
say $joão.idade;
$joão.idade = 23;
say $joão.idade;
Por padrão, todos os atributos são declarados como somente leitura mas você pode fazer isso de forma explícita usando is readonly
Herança é outro conceito de programação orientada a objetos.
Ao definir classes, logo percebemos que alguns atributos/métodos são comuns a muitas classes.
Deveríamos duplicar esse código?
NÃO! Deveríamos usar herança
Vamos considerar que queremos definir duas classes, uma classe para seres Humanos e uma classe para Empregados.
Humanos tem 2 atributos: nome e idade.
Empregados tem 4 atributos: nome, idade, empresa e salário
Podemos ficar tentados a definir essas classes assim:
class Humano {
has $.nome;
has $.idade;
}
class Empregado {
has $.nome;
has $.idade;
has $.empresa;
has $.salário;
}
Embora tecnicamente correto, o código acima é considerado conceitualmente pobre.
Uma forma melhor de escrevê-lo é assim:
class Humano {
has $.nome;
has $.idade;
}
class Empregado is Humano {
has $.empresa;
has $.salário;
}
A palavra-chave is
define herança.
Em linguajar de orientação a objetos dizemos que a classe Empregado é filha (child) de Humano, e Humano é pai (parent) de Empregado.
Todas as classes filhas herdam os atributos e métodos da classe pai, então não precisamos redefini-los.
Classes herdam todos os atributos e métodos das classes pai.
Há casos em que precisamos que o método da classe filha se comporte de forma diferente do método herdado.
Para isso, redefinimos o método na classe filha.
Esse conceito é chamado overriding (sobrescrever).
No exemplo abaixo, o método apresente-se
é herdado pela classe Empregado.
class Humano {
has $.nome;
has $.idade;
method apresente-se {
say 'Olá, eu sou um humano e meu nome é ' ~ self.nome;
}
}
class Empregado is Humano {
has $.empresa;
has $.salário;
}
my $joão = Humano.new(nome =>'João', idade => 23,);
my $joana = Empregado.new(nome =>'Joana', idade => 25, empresa => 'Acme', salário => 4000);
$joão.apresente-se;
$joana.apresente-se;
Overriding funciona da seguinte maneira:
class Humano {
has $.nome;
has $.idade;
method apresente-se {
say 'Olá, eu sou um humano e meu nome é ' ~ self.nome;
}
}
class Empregado is Humano {
has $.empresa;
has $.salário;
method apresente-se {
say 'Olá, eu sou um empregado, meu nome é ' ~ self.nome ~ ' e eu trabalho na ' ~ self.empresa;
}
}
my $joão = Humano.new(nome =>'João', idade => 23,);
my $joana = Empregado.new(nome =>'Joana', idade => 25, empresa => 'Acme', salário => 4000);
$joão.apresente-se;
$joana.apresente-se;
Dependendo de que classe o objeto é, o método certo será chamado.
Herança múltipla é permitida em Raku. Uma classe pode herdar de várias outras classes.
class gráfico-de-barras {
has Int @.valores-das-barras;
method exibir {
say @.valores-das-barras;
}
}
class gráfico-de-linhas {
has Int @.valores-das-linhas;
method exibir {
say @.valores-das-linhas;
}
}
class gráfico-combinado is gráfico-de-barras is gráfico-de-linhas {
}
my $vendas-reais = gráfico-de-barras.new(valores-das-barras => [10,9,11,8,7,10]);
my $vendas-previstas = gráfico-de-linhas.new(valores-das-linhas => [9,8,10,7,6,9]);
my $real-vs-previsto = gráfico-combinado.new(valores-das-barras => [10,9,11,8,7,10],
valores-das-linhas => [9,8,10,7,6,9]);
say "Vendas reais:";
$vendas-reais.exibir;
say "Vendas previstas:";
$vendas-previstas.exibir;
say "Reais vs Previstas:";
$real-vs-previsto.exibir;
Saída
Vendas reais:
[10 9 11 8 7 10]
Vendas previstas:
[9 8 10 7 6 9]
Reais vs Previstas:
[10 9 11 8 7 10]
A classe gráfico-combinado
é capaz de guardar duas séries, uma para os valores reais em barra,
e outra para valores previstos em linha.
Por isso que o definimos como filho de gráfico-de-linhas
e gráfico-de-barras
.
Você deve ter reparado que chamar o método exibir
em gráfico-combinado
não gerou o resultado desejado.
Só uma das séries foi exibida.
Por que isso aconteceu?
gráfico-combinado
herdou de gráfico-de-linhas
e de gráfico-de-barras
, e ambos possuem um método chamado exibir
.
Quando chamamos esse método em gráfico-combinado
o Raku vai tentar resolver esse conflito chamando um dos métodos herdados.
Para que se comporte corretamente, devemos sobrescrever o método exibir
em gráfico-combinado
.
class gráfico-de-barras {
has Int @.valores-das-barras;
method exibir {
say @.valores-das-barras;
}
}
class gráfico-de-linhas {
has Int @.valores-das-linhas;
method exibir {
say @.valores-das-linhas;
}
}
class gráfico-combinado is gráfico-de-barras is gráfico-de-linhas {
method exibir {
say @.valores-das-barras;
say @.valores-das-linhas;
}
}
my $vendas-reais = gráfico-de-barras.new(valores-das-barras => [10,9,11,8,7,10]);
my $vendas-previstas = gráfico-de-linhas.new(valores-das-linhas => [9,8,10,7,6,9]);
my $real-vs-previsto = gráfico-combinado.new(valores-das-barras => [10,9,11,8,7,10],
valores-das-linhas => [9,8,10,7,6,9]);
say "Vendas reais:";
$vendas-reais.exibir;
say "Vendas previstas:";
$vendas-previstas.exibir;
say "Reais vs Previstas:";
$real-vs-previsto.exibir;
Saída
Vendas reais:
[10 9 11 8 7 10]
Vendas previstas:
[9 8 10 7 6 9]
Reais vs Previstas:
[10 9 11 8 7 10]
[9 8 10 7 6 9]
Roles (Papéis) são parecidos com classes no sentido de que são uma coleção de atributos e métodos.
Roles são declarados com a palavra-chave role
e classes que quiserem implementar o role (o papel) podem fazer isso usando a palavra-chave does
(faz).
role gráfico-de-barras {
has Int @.valores-das-barras;
method exibir {
say @.valores-das-barras;
}
}
role gráfico-de-linhas {
has Int @.valores-das-linhas;
method exibir {
say @.valores-das-linhas;
}
}
class gráfico-combinado does gráfico-de-barras does gráfico-de-linhas {
method exibir {
say @.valores-das-barras;
say @.valores-das-linhas;
}
}
my $vendas-reais = gráfico-de-barras.new(valores-das-barras => [10,9,11,8,7,10]);
my $vendas-previstas = gráfico-de-linhas.new(valores-das-linhas => [9,8,10,7,6,9]);
my $real-vs-previsto = gráfico-combinado.new(valores-das-barras => [10,9,11,8,7,10],
valores-das-linahs => [9,8,10,7,6,9]);
say "Vendas reais:";
$vendas-reais.exibir;
say "Vendas previstas:";
$vendas-previstas.exibir;
say "Real vs Previsto:";
$real-vs-previsto.exibir;
Execute o script acima e você verá que o resultado é o mesmo.
Agora você deve estar se perguntando; se roles se comportam igual a classes, qual o seu propósito?
Para responder essa pergunta modifique o primeiro script usado para demonstrar herança múltipla,
aquele em que esquecemos de sobrescrever o método exibir
.
role gráfico-de-barras {
has Int @.valores-das-barras;
method exibir {
say @.valores-das-barras;
}
}
role gráfico-de-linhas {
has Int @.valores-das-linhas;
method exibir {
say @.valores-das-linhas;
}
}
class gráfico-combinado does gráfico-de-barras does gráfico-de-linhas {
}
my $vendas-reais = gráfico-de-barras.new(valores-das-barras => [10,9,11,8,7,10]);
my $vendas-previstas = gráfico-de-linhas.new(valores-das-linhas => [9,8,10,7,6,9]);
my $real-vs-previsto = gráfico-combinado.new(valores-das-barras => [10,9,11,8,7,10],
valores-das-linhas => [9,8,10,7,6,9]);
say "Vendas reais:";
$vendas-reais.exibir;
say "Vendas previstas:";
$vendas-previstas.exibir;
say "Reais vs Previstas:";
$real-vs-previsto.exibir;
Saída
===SORRY!===
Method 'exibir' must be resolved by class gráfico-combinado because it exists in multiple roles (gráfico-de-linhas, gráfico-de-barras)
Se múltiplos roles são aplicados à mesma classe, e um conflito surge, um erro em tempo de compilação será gerado.
Essa é uma abordagem muito mais segura que herança múltipla onde conflitos não são considerados erros e são simplesmente resolvidos em tempo de execução (runtime).
Roles vão te avisar quando houver um conflito.
Introspecção é o processo de obter informação sobre as propriedades de um objeto como seu tipo, seus atributos ou seus métodos.
class Humano {
has $.nome;
has $.idade;
method apresente-se {
say 'Olá, eu sou um humano e meu nome é ' ~ self.nome;
}
}
class Empregado is Humano {
has $.empresa;
has $.salário;
method apresente-se {
say 'Olá, eu sou um empregado, meu nome é ' ~ self.nome ~ ' e eu trabalho na ' ~ self.empresa;
}
}
my $joão = Humano.new(nome =>'João', idade => 23,);
my $joana = Empregado.new(nome =>'Joana', idade => 25, empresa => 'Acme', salário => 4000);
say $joão.WHAT;
say $joana.WHAT;
say $joão.^attributes;
say $joana.^attributes;
say $joão.^methods;
say $joana.^methods;
say $joana.^parents;
if $joana ~~ Humano {say 'Joana é Humana'};
Introspecção é facilitada por:
-
.WHAT
retorna a classe da qual o objeto foi criado. -
.^attributes
retorna uma lista contendo todos os atributos do objeto. -
.^methods
retorna todos os métodos que podem ser chamados no objeto. -
.^parents
retorna todas as classes pai as quais um objeto pertence. -
~~
é chamado de operador smart-match (comparação inteligente). Ele avalia como True (verdadeiro) se o objeto foi criado da mesma classe que está sendo comparada, ou qualquer uma de suas heranças.
Note
|
Para mais informações sobre Programação Orientada a Objetos em Raku, veja: |
Exceções são um comportamento especial que acontece durante a execução quando algo dá errado.
Dizemos que as exceções são lançadas (thrown).
Considere o script abaixo que roda corretamente:
my Str $nome;
$nome = "Joana";
say "Olá " ~ $nome;
say "Como vai você hoje?"
Saída
Olá Joana
Como vai você hoje?
Agora considere esse script que lança uma exceção:
my Str $nome;
$nome = 123;
say "Olá " ~ $nome;
say "Como vai você hoje?"
Saída
Type check failed in assignment to $nome; expected Str but got Int
in block <unit> at exceptions.raku:2
Você deve ter percebido que sempre que um erro ocorre (nesse caso, atribuir um número a uma variável de string) o programa vai parar e outras linhas de código não serão avaliadas, mesmo que estejam corretas.
Tratamento de exceções é o processo de capturar uma exceção que foi lançada para que o script continue funcionando.
my Str $nome;
try {
$nome = 123;
say "Olá " ~ $nome;
CATCH {
default {
say "Pode dizer seu nome de novo? Não o encontramos nos registros.";
}
}
}
say "Como vai você hoje?";
Saída
Pode dizer seu nome de novo? Não o encontramos nos registros.
Como vai você hoje?
Tratamento de exceções é feito usando um bloco try-catch
.
try {
# coloque algum código aqui
# se algo der errado, o script vai entrar para o bloco CATCH abaixo
# se nada der errado o bloco CATCH será ignorado
CATCH {
default {
# o código aqui dentro só será executado se uma exceção for lançada
}
}
}
O bloco CATCH
pode ser definido da mesma forma que um bloco given
.
Isso significa que podemos capturar e tratar de forma diferente muitos tipos de exceção.
try {
# coloque algum código aqui
# se algo der errado, o script vai entrar para o bloco CATCH abaixo
# se nada der errado o bloco CATCH será ignorado
CATCH {
when X::AdHoc { # faça algo quando uma exceção do tipo X::AdHoc for lançada }
when X::IO { # faça algo quando exceções do tipo X::IO forem lançadas }
when X::OS { # faça algo se uma exceção do tipo X::OS for lançada }
default { # faça algo se uma exceção for lançada que não seja dos tipos acima }
}
}
Assim como capturar exceções, Raku também permite que você explicitamente lance exceções.
Dois tipos de exceções podem ser lançados:
-
exceções ad-hoc
-
exceções tipadas
my Int $age = 21;
die "Erro!";
my Int $age = 21;
X::AdHoc.new(payload => 'Erro!').throw;
Exceções ad-hoc são lançadas usando a sub-rotina die
seguido da mensagem de exceção.
Exceções tipadas são objetos, por isso o uso do construtor .new()
no exemplo acima.
Todas as exceções tipadas descendem da classe X
, abaixo seguem alguns exemplos:
X::AdHoc
é o tipo mais simples de exceção
X::IO
é para exceções relacionadas a erros de IO
X::OS
é para erros do sistema operacional (OS)
X::Str::Numeric
é relacionada a errors tentando converter strings em números
Note
|
Para uma lista completa de tipos de exceção e seus métodos associados visite https://docs.raku.org/type-exceptions.html |
Uma expressão regular, ou regex, é uma sequência de caracteres usada para identificação de padrões.
A forma mais fácil de entender regexes é pensar nelas como padrões.
if 'iluminação' ~~ m/ ilumina / {
say "inluminação contém a palavra ilumina";
}
Nesse exemplo, o operador de comparação inteligente ~~
é usado para verificar se a string (iluminação) contém a palavra (ilumina).
"Iluminação" casa com a regex m/ ilumina /
Uma expressão regular pode ser definida da seguinte maneira:
-
/ilumina/
-
m/ilumina/
-
rx/ilumina/
A menos que você peça explicitamente, espaços em branco são irrelevantes, m/ilumina/
e m/ ilumina /
são iguais.
Caracteres alfanuméricos e o underscore _
são escritos normalmente.
Todos os outros caracteres precisam ser escapados usando contrabarra ou colocados entre aspas.
if 'Temperatura: 13' ~~ m/ \: / {
say "A string fornecida contém dois-pontos :";
}
if 'Idade = 13' ~~ m/ '=' / {
say "A string fornecida contém o símbolo de igualdade =";
}
if '[email protected]' ~~ m/ "@" / {
say "Esse é um email válido porque contém o caractere @";
}
Caracteres podem ser classificados em categorias que podemos usar para casar.
Também podemos casar com o inverso daquela categoria (tudo menos ela):
Categoria |
Regex |
Inverso |
Regex |
Caractere de palavra (letra, dígito ou underscore) |
\w |
Qualquer caractere menos os de palavra |
\W |
Dígito |
\d |
Qualquer caractere menos dígitos |
\D |
Espaços |
\s |
Qualquer caractere menos espaços em branco |
\S |
Espaço horizontal |
\h |
Qualquer caractere menos espaços horizontais |
\H |
Espaço vertical |
\v |
Qualquer caractere menos espaços verticais |
\V |
Tab |
\t |
Qualquer caractere menos Tab |
\T |
Quebra de linha |
\n |
Qualquer caractere menos quebra de linha |
\N |
if "João123" ~~ / \d / {
say "Nome inválido, números não são permitidos";
} else {
say "Esse nome é válido"
}
if "João-Ninguém" ~~ / \s / {
say "Essa string tem espaços";
} else {
say "Essa string não tem espaços"
}
Casar contra categorias de caracteres como vimos na seção anterior é conveniente.
Dito isso, uma abordagem mais sistemática seria utilizarmos propriedades Unicode.
Propriedades Unicode são definidas entre <:
e >
if "João23" ~~ / <:N> / {
say "Contém um número";
} else {
say "Não contém um número"
}
if "João-Ninguém" ~~ / <:Lu> / {
say "Contém letras em maiúsculas";
} else {
say "Não contém letras em maiúsculas"
}
if "João-Ninguém" ~~ / <:Pd> / {
say "Contém um traço";
} else {
say "Não contém traços"
}
Curingas também podem ser usados em uma regex.
O ponto .
casa com qualquer caractere simples.
if 'abc' ~~ m/ a.c / {
say "Casou";
}
if 'a2c' ~~ m/ a.c / {
say "Casou";
}
if 'ac' ~~ m/ a.c / {
say "Casou";
} else {
say "Não Casou";
}
Quantificadores vem depois de um caractere e são usados para especificar quantas vezes o esperamos.
A interrogação ?
significa zero ou uma vez.
if 'ac' ~~ m/ a?c / {
say "Casou";
} else {
say "Não casou";
}
if 'c' ~~ m/ a?c / {
say "Casou";
} else {
say "Não Casou";
}
O asterisco *
significa zero ou mais vezes.
if 'az' ~~ m/ a*z / {
say "Casou";
} else {
say "Não Casou";
}
if 'aaz' ~~ m/ a*z / {
say "Casou";
} else {
say "Não Casou";
}
if 'aaaaaaaaaaz' ~~ m/ a*z / {
say "Casou";
} else {
say "Não Casou";
}
if 'z' ~~ m/ a*z / {
say "Casou";
} else {
say "Não Casou";
}
O +
significa pelo menos uma vez.
if 'az' ~~ m/ a+z / {
say "Casou";
} else {
say "Não Casou";
}
if 'aaz' ~~ m/ a+z / {
say "Casou";
} else {
say "Não Casou";
}
if 'aaaaaaaaaaz' ~~ m/ a+z / {
say "Casou";
} else {
say "Não Casou";
}
if 'z' ~~ m/ a+z / {
say "Casou";
} else {
say "Não Casou";
}
Sempre que casamos uma string com uma regex, o resultado é armazenado na variável especial $/
if 'Rakudo é um compilador de Raku.' ~~ m/:s Raku/ {
say "Casou com: " ~ $/;
say "A string que veio antes foi: " ~ $/.prematch;
say "A string que veio depois foi: " ~ $/.postmatch;
say "A string encontrada começa na posição: " ~ $/.from;
say "A string encontrada termina na posição: " ~ $/.to;
}
Casou com: Raku
A string que veio antes foi: Rakudo é um compilador de
A string que veio depois foi: .
A string encontrada começa na posição: 26
A string encontrada termina na posição: 32
$/
retorna um Objeto de Match (a string que casa com a regex)
Os seguintes métodos podem ser chamados no Objeto de Match:
.prematch
retorna a string que precede a sequência casada.
.postmatch
retorna a string que sucede a sequência casada.
.from
retorna a posição na string em que a sequência casada começa.
.to
retorna a posição na string em que a sequência casada termina.
Tip
|
Por padrão os espaços em branco numa definição de regex são irrelevantes. Se queremos casar uma expressão regular contendo espaços temos que dizer isso explicitamente. O :s na regex m/:s Raku/ força que espaços em branco sejam considerados e não descartados.Poderíamos ter escrito essa regex como m/ Perl\s6 / e usado \s que, como vimos antes, é um placeholder para espaços.Se uma regex contém mais de um espaço em branco, usar :s torna-se mais eficiente em vez de usar \s para cada caractere de espaço.
|
Vamos verificar se um email é válido ou não.
Para esse exemplo vamos assumir que um email válido é formado por:
primeiro nome [ponto] último nome [arroba] empresa [ponto] (com/org/net)
Warning
|
a regex usada nesse exemplo não é muito precisa para validação de emails. Seu único propósito é demonstrar a funcionalidade das regexes em Raku. Não use como está aqui em ambientes de produção. |
my $email = '[email protected]';
my $regex = / <:L>+\.<:L>+\@<:L+:N>+\.<:L>+ /;
if $email ~~ $regex {
say $/ ~ " é um email válido";
} else {
say "Esse não é um email válido";
}
[email protected] é um email válido
<:L>
casa com uma letra
<:L>` casa com uma ou mais letras +
`\.` casa com um caractere de [ponto] +
`\@` casa com um caractere de [arroba] +
`<:L:N>
casa com uma letra e um número
<:L+:N>+
casa com uma ou mais (letras e números)
Essa regex pode ser decomposta da seguinte forma:
-
primeiro nome
<:L>+
-
[ponto]
\.
-
último nome
<:L>+
-
[arroba]
\@
-
empresa
<:L+:N>+
-
[ponto]
\.
-
com/org/net
<:L>+
my $email = '[email protected]';
my regex muitas-letras { <:L>+ };
my regex ponto { \. };
my regex arroba { \@ };
my regex muitas-letras-e-numeros { <:L+:N>+ };
if $email ~~ / <muitas-letras> <ponto> <muitas-letras> <arroba> <muitas-letras-e-numeros> <ponto> <muitas-letras> / {
say $/ ~ " é um email válido";
} else {
say "Esse não é um email válido";
}
Uma regex rotulada é definida usando a seguinte sintaxe: my regex nome-da-regex { definição da regex }
Uma regex rotulada pode ser chamada usando a seguinte sintaxe: <nome-da-regex>
Note
|
Para mais informações sobre regexes, veja https://docs.raku.org/language/regexes |
Raku é uma linguagem de uso geral. Ela pode ser usada para resolver uma série de tarefas incluindo: manipulação de textos, gráficos, web, bancos de dados, protocolos de rede, etc.
Reusabilidade é um conceito muito importante onde programadores não precisam reinventar a roda cada vez que querem fazer uma nova tarefa.
Raku permite a criação e redistribuição de módulos. Cada módulo é um conjunto de funcionalidades que pode ser reutilizado após instalado.
Zef é uma ferramenta de gestão de módulos que vem com o Rakudo Star.
Para instalar um módulo específico, digite o comando abaixo no seu terminal:
zef install "nome do módulo"
Note
|
O diretório de módulos Raku pode ser encontrado em: https://modules.raku.org/ |
MD5 é uma função de hash criptográfico que produz um valor de 128 bits.
MD5 tem uma variedade de aplicações como o armazenamento de senhas em um banco de dados.
Quando um usuário se cadastra, suas credenciais não são armazenadas em texto puro e sim na forma de um hash.
O raciocínio por trás disso é que se o banco de dados for comprometido, quem fez o ataque não conseguirá descobrir as senhas de acesso.
Digamos que você precise de um script que gere um hash MD5 de uma senha antes de guardá-la no banco de dados.
Felizmente existe um módulo em Raku que já implementa o algoritmo MD5. Vamos instalá-lo:
zef install Digest::MD5
Agora execute o script abaixo:
use Digest::MD5;
my $senha = "senha123";
my $hash-da-senha = Digest::MD5.new.md5_hex($senha);
say $hash-da-senha;
Para executar a função md5_hex()
que cria os hashes MD5, precisamos carregar o módulo relacionado.
A palavra-chave use
carrega o módulo para ser usado no script.
Warning
|
O uso de hashing MD5 não é suficiente, porque é um método vulnerável a ataques de dicionário. Hashes de senhas devem ser "salgados" para mais segurança https://en.wikipedia.org/wiki/Salt_(cryptography). |
Unicode é o padrão para codificar e representar caracteres de texto que abrange o maior número de sistemas de escrita do mundo.
UTF-8 é uma codificação de caracteres capaz de codificar todos os caracteres possíveis, ou code points, em Unicode.
Caracteres são definidos por:
Grafema (grapheme): A representação visual.
Code point: Um número atribuído a esse caractere.
say "a";
say "\x0061";
say "\c[LATIN SMALL LETTER A]";
As 3 linhas acima mostram formas diferentes de se construir o mesmo caractere:
-
Escrevendo o caractere diretamente (grafema)
-
Usando
\x
e o code point -
Usando
\c
e o nome do code point
say "☺";
say "\x263a";
say "\c[WHITE SMILING FACE]";
say "á";
say "\x00e1";
say "\x0061\x0301";
say "\c[LATIN SMALL LETTER A WITH ACUTE]";
A letra á
pode ser escrita:
-
usando seu code point único
\x00e1
-
ou como uma combinação dos code points de
a
e de acento agudo\x0061\x0301
say "á".NFC;
say "á".NFD;
say "á".uniname;
Saída
NFC:0x<00e1>
NFD:0x<0061 0301>
LATIN SMALL LETTER A WITH ACUTE
NFC
retorna o code point único.
NFD
decompõe o caractere e retorna o code point de cada parte.
uniname
retorna o nome do code point.
my $Δ = 1;
$Δ++;
say $Δ;
my $var = 2 + ⅒;
say $var;
Em circunstâncias normais, todas as tarefas de um programa executam em sequência.
Isso pode não ser um problema, a menos que o que esteja tentando fazer consuma muito tempo.
Raku possui recursos que permitem que você execute tarefas em paralelo.
Nesse ponto, é importante notar que paralelismo pode significar uma dessas duas coisas:
-
Paralelismo de Tarefas: Duas (ou mais) expressões independentes executando em paralelo.
-
Paralelismo de Dados: Uma única expressão iterando sobre uma lista de elementos em paralelo.
Vamos começar pelo segundo.
my @array = (0..50000); # Populando o array
my @result = @array.map({ is-prime $_ }); # chame is-prime para cada elemento do array
say now - INIT now; # Exibe o tempo que o script levou pra rodar
Estamos apenas fazendo uma operação, @array.map({ is-prime $_ })
A sub-rotina is-prime
está sendo chamada para cada elemento do array em sequência:
is-prime @array[0]
depois is-prime @array[1]
depois is-prime @array[2]
etc.
is-prime
em múltiplos elementos do array ao mesmo tempo:my @array = (0..50000); # Populando o array
my @result = @array.race.map({ is-prime $_ }); # chame is-prime para cada elemento do array
say now - INIT now; # exiba o tempo que o script levou pra rodar
Note o uso de race
na expressão.
Esse método vai ativar a iteração em paralelo dos elementos do array.
Após executar ambos os exemplos (com e sem race
), compare o tempo que levou para cada um dos scripts executar.
Tip
|
race
my @array = (1..1000);
my @result = @array.race.map( {$_ + 1} );
.say for @result; hyper
my @array = (1..1000);
my @result = @array.hyper.map( {$_ + 1} );
.say for @result; Se você rodar os dois exemplos, verá que um está ordenado e o outro não. |
my @array1 = (0..49999);
my @array2 = (2..50001);
my @result1 = @array1.map( {is-prime($_ + 1)} );
my @result2 = @array2.map( {is-prime($_ - 1)} );
say @result1 eqv @result2;
say now - INIT now;
-
Nós definimos 2 arrays
-
aplicamos uma operação diferente em cada array e guardamos os resultados
-
então verificamos se os dois resultados são iguais
O script espera até @array1.map( {is-prime($_ + 1)} )
terminar
e só então começa @array2.map( {is-prime($_ - 1)} )
Só que as duas operações são aplicadas a arrays diferentes e não dependem uma da outra.
my @array1 = (0..49999);
my @array2 = (2..50001);
my $promise1 = start @array1.map( {is-prime($_ + 1)} ).eager;
my $promise2 = start @array2.map( {is-prime($_ - 1)} ).eager;
my @result1 = await $promise1;
my @result2 = await $promise2;
say @result1 eqv @result2;
say now - INIT now;
O método start
avalia o código e retorna um objeto do tipo promessa ou abreviadamente, uma promise (promessa).
Se o código executar com sucesso, a promessa será mantida.
Se o código lançar uma exceção, a promessa será quebrada.
O método await
espera por uma promessa.
Se ela for mantida o método obterá os valores retornados.
Se ela for quebrada o método obterá a exceção lançada.
Observe o tempo que o script levou para executar.
Warning
|
Paralelismo sempre adiciona um custo (overhead) de threading. Se esse overhead não for compensado pelo ganho em velocidade computacional, o script pode parecer mais lento. É por isso que usar race , hyper , start and await para scripts muito simples pode de fato deixá-los mais lentos.
|
Note
|
Para mais informações sobre Concorrência e Programação Assíncrona, veja https://docs.raku.org/language/concurrency |
-
Canal de IRC #raku. Muita discussão acontece no canal de IRC. Esse deve ser seu lugar de destino para qualquer questão: https://raku.org/community/irc
-
Agregador de blogs pl6anet. Fique em dia lendo blog posts com foco em Raku.
-
/r/raku. Assine a subreddit do Raku.