Recursos incomuns de Raku
(buttondown.com)- Raku se parece mais com um laboratório de recursos de linguagem, onde várias ideias são testadas diretamente, do que com um projeto único e altamente refinado; por isso, dá para ver em um só lugar experimentos que outras linguagens talvez venham a adotar
- Junctions propagam operações sobre um conjunto de valores possíveis e depois as reduzem com quantificadores booleanos como
all,anyeone, permitindo expressar condições complexas de forma curta - O
*de Whatevers é usado em intervalos infinitos, funções anônimas e indexação baseada no tamanho da lista, criando um estilo de código conciso, mas incomum - As regexes de Raku abandonam a compatibilidade com PCRE em troca de gramáticas componíveis e recursos práticos como separadores de valores, lookahead mais legível e ignorar espaços em branco por padrão
- Considerando Hyperoperators, Pair Syntax, Slangs, RakuAST e até o Rakudo Star, Raku é uma linguagem que experimenta amplamente, da gramática à forma de distribuição
Por que Raku parece um laboratório de recursos de linguagem
- Depois de usar Raku de fato por mais de um ano, a linguagem parece mais próxima de um “laboratório para recursos de linguagem”
- Os cinco modelos de concorrência e as várias maneiras de fazer uma mesma coisa revelam um caráter experimental, voltado a “ver o que acontece”
- O motivo de muitos recursos interagirem de forma estranha e terem muitos casos de borda também é que executar 100 experimentos é muito mais fácil do que refiná-los para funcionarem bem em conjunto
- Os recursos tratados aqui são experimentos ao estilo de Raku que talvez um dia apareçam em outras linguagens de programação
Junctions: sobreposição de valores possíveis e quantificadores booleanos
- Junctions representam uma sobreposição de valores possíveis, e quando uma operação é aplicada a uma junction, a mesma operação é aplicada a todos os valores internos
2|10viraany(2, 10)2&10 + 3viraall(5, 13)(1&2) + (10^20)é combinado comoall(one(11, 21), one(12, 22))
- O principal uso de Junctions é expressar fórmulas booleanas como
alleany- não há como desmontar estruturalmente uma junction
- para usá-la na prática, primeiro é preciso reduzi-la a um booleano
soforça a conversão de uma junction para booleanoso (1&2) + (10^20) < 15éTrueso (1&2) + (10^20) > 0éFalse
- Em Raku,
list.allnão é um método que recebe argumentos, mas sim um método sem argumentos que transforma a lista em junction<1 2 3>.allviraall(1, 2, 3)- ao aplicar
is-prime($x), isso é aplicado a cada valor, resultando em algo comoall(False, True, True)
- Compondo Junctions, também dá para criar comportamentos como
unique- “todo elemento é igual a exatamente um elemento” pode ser expresso como
{.all == .one} <1 2 3 7>é verdadeiro, e<1 2 3 7 2>é falso
- “todo elemento é igual a exatamente um elemento” pode ser expresso como
Whatevers: intervalos infinitos e funções anônimas com *
- O
*de Whatevers é um símbolo whatever que assume vários papéis em Raku - Em intervalos ou sequências,
*significa que não há limite1..*é1..Inf(2,4,8...*)[17]é262144
- Expressões que contêm
*são promovidas a funções anônimas- esse comportamento é chamado de whatever-priming
- o resultado é
WhateverCodee, tirando o tipo, não se distingue de outras funções (* + 10)(2)é12(^10).map(* % 2)cria o padrão0 1 0 1 ...
- Se houver dois
*na expressão, cada um vira um argumento posicional separado(2, 30, 4, 50).map(* + *)retorna(32, 54)- isso é útil em definições complicadas de Fibonacci, mas não é certo que seja melhor do que um comportamento em que cada
*represente o mesmo valor
- Muitos métodos de Raku podem receber funções, então o priming é amplamente usado
l[*-1]parece um índice negativo, mas na prática[]passa o tamanho da lista para a função e consulta o índice calculado- se a lista tem tamanho 10,
l[*-1]viral[10-1], ou seja, o último elemento l.head(2)são os dois primeiros elementos, el.head(*-2)significa tudo, exceto os dois últimos
- Passando outras funções para
[], também dá para implementar estruturas como buffer circular de forma simples@x[95 % *]--calcula a posição usando o tamanho da lista
Regexes: gramáticas componíveis em vez de compatibilidade com PCRE
- Raku usa uma nova sintaxe de regex, diferente de regex POSIX e de PCRE
- regex POSIX têm uma estrutura cheia de barras invertidas e pontuação
- PCRE é retrocompatível com POSIX e herdou essa estrutura ainda mais complexa de barras invertidas e pontuação
- Perl 6 rompeu a retrocompatibilidade e introduziu uma nova sintaxe de regex
- A melhoria mais marcante é a componibilidade
- em muitas linguagens, regexes são combinadas concatenando strings de regex
- em Raku, é possível inserir outra regex, como em
/< foo >+/, para casar a regexfoouma ou mais vezes sem que ela vaze para a regex superior
- Isso permite dividir regexes complexas em partes menores e mais legíveis
- essa abordagem é a base para Raku oferecer gramáticas de parsing como recurso nativo da linguagem
- Ao quebrar a retrocompatibilidade, vários recursos de conveniência também foram adicionados
\d+ % ','casa1,1,2e1,1,4, mas não casa1,nem12- lookahead e grupo non-capturing não usam combinações enigmáticas de símbolos
r1 && r2casa apenas strings que satisfazem tantor1quantor2:pode interromper o backtracking- espaços em branco são ignorados por padrão e precisam ser ativados explicitamente no padrão de match
- Independentemente da capacidade real de processamento, a notação de regex de Raku pode ser vista como um conjunto de recursos que também pode aparecer em outras linguagens
Hyperoperators: mapeamento generalizado que desce até listas aninhadas
- A forma básica de Hyperoperators,
l>>.method, é parecida commap, mas desce recursivamente até as sublistas[1, [2, 3], 4]>>.succvira[2 [3 4] 5]
- Em Raku, uma chamada de função no formato
f(list, *args)pode ser convertida para a forma de métodolist.&f(* args)- por isso,
>>.funciona como um operador de mapeamento generalizado - junto com Whatever, pode ser usado como
[1, [2, 3], 4]>>.&(*+1)
- por isso,
- O hyperoperator binário
l1 << op >> l2aplicaopelemento por elemento às duas listas- a lista menor se repete até a maior terminar
[1, 2, 3, 4, 5] <<+>> [10, 20]resulta em[11 22 13 24 15]- as formas
<< op<<ou>>op>>repetem apenas até a lista da esquerda ou da direita terminar
- Também funciona com valor único e com listas aninhadas
[1, 2, 3, 4, 5] <<+>> 10vira[11 12 13 14 15][1, [2, 3], 4, 5] <<+>> [10, 20]vira[11 [22 23] 14 25]
- Hyperoperators entre dois hashes também têm um comportamento separado, aplicando
opà união, interseção ou diferença entre hashes - O nome Hyperoperator vem do fato de todas as operações serem avaliadas em paralelo
- até loops explícitos podem ser paralelizados com o prefixo
hyper
- até loops explícitos podem ser paralelizados com o prefixo
Pair Syntax: unidade básica do hash e passagem concisa de flags
- Pair é a unidade básica que compõe os hashes em Raku
- um hash é uma coleção de pares
key => value - pair é o tipo básico, e hash é uma coleção de pairs
- um hash é uma coleção de pares
- Raku tem várias formas de sintaxe com dois-pontos para escrever pair de forma curta
my $x = 3; :$xéx => 3:a<$x>éa => "$x":a($x)éa => 3:3aéa => 3
- O atalho mais importante é
:keye:!key- eles correspondem a
key => Trueekey => False, respectivamente - são usados como uma forma concisa de passar flags a métodos
- eles correspondem a
- O método
matchrecebe vários colon pairs como flags- usa formas como
:continue(:$c),:pos(:$p),:global(:$g),:overlap(:$ov) - na assinatura,
:f(:$foo)define o parâmetro$fooe também cria o alias:f - quem chama pode definir a mesma flag com
:fou:foo
- usa formas como
- Argumentos colon pair têm ordem e posição flexíveis
"abab".match(/../, :g)faz match global"abab".match(/../, :g, :ov)faz match com sobreposição- também é possível inverter a ordem e agrupar, como em
"abab".match(:g:ov, /../) - argumentos nomeados podem até vir depois da chamada, como em
"abab".match(:g, /../):ov:2nd
- Essa sintaxe permite definir opções de método de forma bem mais curta do que
match(global=True, overlap=True)
Slangs, RakuAST, Rakudo Star
- Slangs são um recurso para alterar a gramática de Raku
- podem ser usados para modificar a sintaxe de loops, mudar identificadores, adicionar actors ou incluir sequências de DNA
- os exemplos citados são Slang::Otherwise, Slang::Piersing, OO::Actors e BioInfo
- RakuAST é um recurso que faz parsing de expressões Raku para AST dentro do próprio Raku
Q/my $x; $x++/.ASTmostra uma declaração de variável e um incremento postfix na forma deRakuAST::StatementList- há também um exemplo que gera código Raku em uma notação parecida com outra linguagem, como
Q/my $x; put $x/.AST.DEPARSE("NL")
- Raku tem uma distribuição instalável chamada Rakudo Star
- essa distribuição fornece alguns módulos de terceiros já pré-instalados
- ela é próxima de um meio-termo entre o custo de manter uma grande biblioteca padrão e o custo de fazer o usuário procurar sozinho os pacotes adequados no ecossistema
1 comentários
Opiniões no Hacker News
Há algum tempo implementei de forma independente, para JavaScript, algo parecido com o recurso de regexes composicionais mencionado aqui, e mesmo anos depois ela ainda é uma das bibliotecas que mais levo para outros projetos.
Ela deixa a sensação um pouco mais próxima de ter parsers de primeira classe dentro da linguagem, e há um exemplo usando isso para implementar o parsing de tipos de mídia com regexes: https://runkit.com/tolmasky/media-type-parsing-with-template...
No npm:
templated-regular-expression; GitHub: https://github.com/tolmasky/templated-regular-expressionClaro que linguagens de programação deveriam ter parsers de verdade, e regexes não deveriam ser usadas como parsers, mas, se você vai usar regex de qualquer forma, poder dividi-la em pequenos pedaços é realmente ótimo.
As regexes de Raku são muito mais poderosas quando combinadas com gramáticas e, quando bem escritas, são mais fáceis de entender do que qualquer “parser de verdade”. Para fazer parse de Raku com um parser comum, seria preciso conseguir adicionar e remover itens do parser durante o parsing; o parser de Raku lida com isso subclassificando a gramática atual para adicionar e remover itens dentro do escopo léxico atual e, quando o escopo termina, voltando à gramática anterior.
Em Raku, regexes são outra gramática para escrever código; elas só têm uma gramática e um comportamento padrão um pouco diferentes, e podem ter parâmetros e variáveis. Se a gramática de regex não for adequada para uma tarefa, dá para inserir gramática Raku normal, fazer o que for necessário e depois voltar para a gramática de regex.
Por exemplo, é possível casar uma ou mais ocorrências de
Aseguidas pelo mesmo número deBe pelo mesmo número deC; na maioria das gramáticas de regex, para casar o mesmo número deAeB, você precisaria de recursão no meio, mas assim fica difícil tratar tambémCdo mesmo modo, e a leitura fica muito mais complicada. A abordagem do Raku não precisa de backtracking nem recursão, então pode rodar bem rápido.Se você agrupar isso em uma gramática, obtém uma árvore de parse completa; também é possível fazer sem gramática, mas usar gramática é mais fácil. Como exemplo de parser real, costumo recomendar JSON::TINY::Grammar: https://github.com/moritz/json/blob/master/lib/JSON/Tiny/Gra...
Para ser sincero, boa parte do design dos “parsers de verdade” parece um subproduto da RAM limitada dos computadores antigos. A etapa separada de tokenização servia para reduzir a RAM ocupada pelo código-fonte e deixar memória para a análise semântica e a compilação posteriores, e não acho que ela simplifique tanto assim essas etapas seguintes. O módulo JSON::Tiny acima cria estruturas de dados nativas de Raku por meio de uma classe de ações enquanto a gramática faz o parsing, então é como se fizesse parsing e compilação ao mesmo tempo.
Uso Raku em produção. Para processamento de texto, é a melhor linguagem, e a experiência de criar parsers é excelente.
É surpreendente que ela não seja a linguagem de ponta para criar pipelines de texto com LLMs.
Uau. Dá vontade de sair da indústria antes que chegue o dia em que eu tenha que manter uma base de código em Raku.
Ao olhar livros introdutórios de Raku, muitas partes parecem Python com sintaxe da família C. É uma sintaxe centrada em chaves, mas a facilidade de uso, as estruturas de dados embutidas e os recursos de orientação a objetos são todos de alto nível.
Se você conhece outras linguagens de script de alto nível, acho que o código Raku de scripts semelhantes seria bem fácil de ler. Não acho que a maioria das pessoas use, no código do dia a dia, os recursos realmente incomuns, e o Raku é mais flexível, mas em geral o código não parece obscuro.
Isso aparece em recursos do Raku como a possibilidade de definir argumentos nomeados em forma abreviada, por exemplo
:global(:$g), ou de chamar um match global de regex comomatch(/foo/, :g), assumindo um valorTrue. Perl também tem muitas coisas assim, todas para escrever de forma mais rápida e divertida, mas são menos legíveis para iniciantes.Acho que isso vai virar uma diferença geracional. Uma geração inteira de programadores mais velhos pode acabar se sentindo deslocada em um ambiente de trabalho que, para a geração atual, é normal.
Alguns recursos são meio familiares. Hyper soa como uma versão mais improvisada de algo da área de recursion-schemes, e o
*mostrado aqui é um pouco parecido com o_do Scala.Gosto de usar
_em lambdas, então queria que todas as linguagens adotassem algo parecido.> (2, 30, 4, 50).map(* + *) returns (32, 45)não deveria serreturns (32, 54)? O segundo termo seria 4+50.Talvez seja resultado de uma tradução palavra por palavra vinda do modo como alguns países dizem algo como “quatro e cinquenta”, em vez de “cinquenta e quatro” em inglês.
(32 54). Parece que o autor apenas cometeu um deslize.Então agora Perl é uma droga de entrada para linguagens da família APL?
Por exemplo, dá para usar o metaoperador de redução
[ ]junto com o+infixo para “somar tudo”:[+] 1, 2, 3Raku ainda tem velocidade como um grande problema. Fazer parsing de arquivos de log com expressões regulares é um ponto forte do Perl, mas mesmo o Raku mais recente, excluindo o tempo de inicialização, leva 6,5 vezes mais tempo que o Python 3.13
Foi realmente surpreendente que
(2,4,8...*)[17]resulte em262144Excluindo o
Whatever, a novidade após Perl 6/Raku provavelmente está mais no lado da avaliação preguiçosa(1,3,9...*)[4,5]vira(81 243), e(1,3,9...*)[(1..3)]vira(3 9 27)Também dá para aninhar, permitindo expressões como
(1,2,4...*)[(1,2,4...*)[1,2,3]]O texto acabou selecionando e abordando alguns recursos interessantes do Raku
Acho que os recursos mais importantes do Raku são a interface de linha de comando e as gramáticas (grammar)
Suporte a interface de linha de comando é um recurso comum; por exemplo, há implementações de
docopte casos de adoção. Mas em Raku esse recurso é embutido e fácil de usarJá gramáticas são um recurso raro no sentido de que a linguagem de programação oferece gramáticas como componentes de primeira classe, permitindo criar e combinar gramáticas com programação orientada a objetos
Acompanhei este projeto por alguns anos e ele é interessante, mas é realmente uma pena que o Perl 6 pareça ter se perdido demais nessa viagem pelo país do espelho