- Raku se parece más a un laboratorio de funciones de lenguaje que a un diseño único y pulido, así que permite ver en un solo lugar experimentos que otros lenguajes podrían adoptar
- Junctions propaga operaciones sobre conjuntos de valores posibles y luego los pliega con cuantificadores booleanos como
all,anyyone, para expresar condiciones complejas de forma breve - El
*de Whatevers se usa para rangos infinitos, funciones anónimas e indexación basada en la longitud de listas, creando un estilo de código conciso pero poco familiar - Las expresiones regulares de Raku renuncian a la compatibilidad con PCRE, pero ofrecen gramáticas componibles, separadores de valores, lookahead legible e ignorar espacios en blanco por defecto, entre otras comodidades
- Si se suman Hyperoperators, Pair Syntax, Slangs, RakuAST y Rakudo Star, Raku aparece como un lenguaje que experimenta ampliamente, desde la sintaxis hasta la forma de distribución
Por qué Raku parece un laboratorio de funciones de lenguaje
- Después de usar Raku de verdad durante más de un año, da la impresión de ser un lenguaje más cercano a un “laboratorio para funciones de lenguaje”
- Sus cinco modelos de concurrencia y sus múltiples maneras de hacer una misma tarea muestran un carácter experimental, de “ver qué pasa”
- Una razón de que muchas funciones interactúen de forma extraña y tengan tantos casos límite es que ejecutar 100 experimentos es mucho más fácil que pulirlos para que todos funcionen bien juntos
- Las funciones tratadas aquí son experimentos al estilo Raku que probablemente algún día podrían verse también en otros lenguajes de programación
Junctions: superposición de valores posibles y cuantificadores booleanos
- Junctions representa una superposición de valores posibles, y cuando se aplica una operación a un junction, esa misma operación se aplica a todos los valores internos
2|10se convierte enany(2, 10)2&10 + 3se convierte enall(5, 13)(1&2) + (10^20)se combina comoall(one(11, 21), one(12, 22))
- El uso principal de Junctions es expresar fórmulas booleanas como
allyany- no hay forma de desestructurar un junction
- para usarlo realmente, primero hay que plegarlo a un booleano
sofuerza la conversión de un junction a booleanoso (1&2) + (10^20) < 15daTrueso (1&2) + (10^20) > 0daFalse
- En Raku,
list.allno es un método que reciba argumentos, sino un método sin argumentos que convierte una lista en junction<1 2 3>.allse convierte enall(1, 2, 3)- al aplicar
is-prime($x), se aplica a cada valor y queda algo comoall(False, True, True)
- Combinando Junctions también se puede construir un comportamiento como
unique- “todos los elementos son iguales exactamente a un solo elemento” puede expresarse como
{.all == .one} <1 2 3 7>es verdadero, y<1 2 3 7 2>es falso
- “todos los elementos son iguales exactamente a un solo elemento” puede expresarse como
Whatevers: rangos infinitos y funciones anónimas con *
- El
*de Whatevers cumple varios papeles en Raku como símbolo whatever - En rangos o secuencias,
*significa que no hay límite1..*equivale a1..Inf(2,4,8...*)[17]es262144
- Una expresión que contiene
*se eleva a función anónima- a este comportamiento se le llama whatever-priming
- el resultado es
WhateverCodey, salvo por el tipo, no se distingue de otras funciones (* + 10)(2)es12(^10).map(* % 2)crea el patrón0 1 0 1 ...
- Si dentro de la expresión hay dos
*, cada uno se convierte en un argumento posicional distinto(2, 30, 4, 50).map(* + *)devuelve(32, 54)- esto puede ser útil para definiciones complicadas como Fibonacci, aunque no está claro que sea mejor que hacer que cada
*represente el mismo valor
- Muchos métodos de Raku pueden recibir funciones, así que el priming se usa ampliamente
l[*-1]parece un índice negativo, pero en realidad[]pasa la longitud de la lista a una función y consulta el índice calculado- si la lista tiene longitud 10,
l[*-1]esl[10-1], o sea el último elemento l.head(2)toma los dos primeros elementos, yl.head(*-2)significa todo excepto los dos últimos
- Si se pasa otra función a
[], también se pueden implementar fácilmente estructuras como un ring buffer@x[95 % *]--calcula la posición usando la longitud de la lista
Expresiones regulares: gramáticas componibles en lugar de compatibilidad con PCRE
- Raku usa una nueva sintaxis de regex distinta de las expresiones regulares POSIX o de PCRE
- las regex POSIX tienen una estructura cargada de barras invertidas y signos de puntuación
- PCRE es retrocompatible con POSIX y heredó esa estructura aún más compleja de barras invertidas y puntuación
- Perl 6 rompió la retrocompatibilidad e introdujo una nueva sintaxis de regex
- La mejora más notable es la componibilidad
- en muchos lenguajes las regex se combinan concatenando cadenas de regex
- en Raku se puede insertar otra regex con algo como
/< foo >+/, haciendo coincidir la regexfoouna o más veces sin que se filtre hacia la regex superior
- Esto permite dividir regex complejas en partes más pequeñas y legibles
- ese enfoque es la base de que Raku soporte gramáticas de parsing como función integrada del lenguaje
- Al romper la retrocompatibilidad también se añadieron varias funciones de conveniencia
\\d+ % ','coincide con1,1,2y1,1,4, pero no con1,ni con12- lookahead y non-capturing group no usan combinaciones crípticas de símbolos
r1 && r2solo coincide con cadenas que satisfacen tantor1comor2:permite detener el backtracking- los espacios en blanco se ignoran por defecto y deben activarse explícitamente en el patrón de coincidencia
- La notación regex de Raku, aparte de su capacidad de procesamiento real, puede verse como un conjunto de funciones que bien podrían aparecer también en otros lenguajes
Hyperoperators: mapeo generalizado que baja hasta listas anidadas
- La forma básica de Hyperoperators,
l>>.method, se parece amap, pero desciende recursivamente dentro de sublistas[1, [2, 3], 4]>>.succda[2 [3 4] 5]
- En Raku, una llamada de función de la forma
f(list, *args)puede transformarse a la forma de métodolist.&f(* args)- por eso
>>.funciona como un operador de mapeo generalizado - junto con Whatever, puede usarse como
[1, [2, 3], 4]>>.&(*+1)
- por eso
- El hyperoperator binario
l1 << op >> l2aplicaopelemento por elemento a dos listas- la lista más corta se repite hasta que termina la más larga
[1, 2, 3, 4, 5] <<+>> [10, 20]da[11 22 13 24 15]- las formas
<< op<<o>>op>>solo repiten hasta que se termina la lista izquierda o derecha, respectivamente
- También funciona con valores únicos y listas anidadas
[1, 2, 3, 4, 5] <<+>> 10da[11 12 13 14 15][1, [2, 3], 4, 5] <<+>> [10, 20]da[11 [22 23] 14 25]
- El hyperoperator entre dos hashes también tiene un comportamiento separado para aplicar
opsobre unión, intersección y diferencia de hashes - El nombre Hyperoperator viene de que todas las operaciones se evalúan en paralelo
- incluso los loops explícitos pueden paralelizarse con el prefijo
hyper
- incluso los loops explícitos pueden paralelizarse con el prefijo
Pair Syntax: la unidad básica de los hashes y una forma concisa de pasar flags
- Pair es la unidad básica con la que se construyen los hashes en Raku
- un hash es una colección de pares
key => value - pair es el tipo básico, y hash es una colección de pairs
- un hash es una colección de pares
- Raku tiene muchas formas de colon syntax para escribir un pair de forma corta
my $x = 3; :$xsignificax => 3:a<$x>significaa => "$x":a($x)significaa => 3:3asignificaa => 3
- La abreviación más importante es
:keyy:!key- se mapean respectivamente a
key => Trueykey => False - se usan como forma concisa de añadir flags a un método
- se mapean respectivamente a
- El método
matchrecibe varios colon pairs como flags- usa formas como
:continue(:$c),:pos(:$p),:global(:$g),:overlap(:$ov) - en la firma,
:f(:$foo)define el parámetro$fooy además crea el alias:f - quien llama puede activar el mismo flag con
:fo con:foo
- usa formas como
- Los argumentos colon pair son flexibles en orden y posición
"abab".match(/../, :g)hace coincidencia global"abab".match(/../, :g, :ov)hace coincidencia con superposición- también se puede cambiar el orden y juntarlos como
"abab".match(:g:ov, /../) - incluso pueden ponerse argumentos con palabra clave después de la llamada, como
"abab".match(:g, /../):ov:2nd
- Esta sintaxis permite indicar opciones de método mucho más corto que con
match(global=True, overlap=True)
Slangs, RakuAST y Rakudo Star
- Slangs es una función para modificar la gramática de Raku
- puede usarse para cambiar la sintaxis de loops, modificar identificadores, añadir actors o incluso secuencias de ADN
- aparecen como ejemplos Slang::Otherwise, Slang::Piersing, OO::Actors y BioInfo
- RakuAST es una función para parsear expresiones de Raku como AST dentro del propio Raku
Q/my $x; $x++/.ASTmuestra la declaración de variable y el operador de incremento postfix comoRakuAST::StatementList- hay un ejemplo de salida de código Raku con notación de otro lenguaje usando
Q/my $x; put $x/.AST.DEPARSE("NL")
- Raku cuenta con una edición instalable llamada Rakudo Star
- esta distribución viene con ciertos módulos de terceros preinstalados
- se parece a un punto intermedio entre la carga de mantener una gran biblioteca estándar y la carga para el usuario de tener que encontrar por su cuenta los paquetes adecuados dentro del ecosistema
1 comentarios
Opiniones de Hacker News
Hace tiempo implementé de forma independiente para JavaScript algo parecido a la función de expresiones regulares componibles de la que se habló aquí, y aun después de varios años sigue siendo una de las bibliotecas que más a menudo llevo a otros proyectos.
Se acerca un poco más a la sensación de tener parsers de primera clase dentro del lenguaje, y como ejemplo hay una implementación que usa esto para parsear tipos de medios con expresiones regulares: https://runkit.com/tolmasky/media-type-parsing-with-template...
En npm,
templated-regular-expression; GitHub: https://github.com/tolmasky/templated-regular-expressionClaro que los lenguajes de programación deberían tener parsers de verdad y no se deberían usar expresiones regulares como parser, pero si de todos modos vas a usar regex, es realmente bueno poder dividirlas en piezas pequeñas.
Las expresiones regulares de Raku, combinadas con gramáticas, son mucho más potentes y, si están bien escritas, más fáciles de entender que cualquier “parser de verdad”. Para parsear Raku con un parser convencional, tendrías que poder agregar y quitar elementos del parser durante el parseo; el parser de Raku maneja esto subclasificando la gramática actual para agregar o quitar elementos dentro del alcance léxico actual, y al terminar el alcance vuelve a la gramática anterior.
En Raku, las expresiones regulares son otra gramática para escribir código; solo difieren un poco de la gramática y el comportamiento básicos, y pueden tener parámetros y variables. Si la gramática de las regex no encaja con una tarea, puedes insertar gramática normal de Raku para hacer lo necesario y luego volver a la gramática de regex.
Por ejemplo, también es posible hacer coincidir uno o más
Aseguidos por el mismo número deBy el mismo número deC; en la mayoría de las gramáticas de expresiones regulares, para hacer coincidir el mismo número deAyBse necesita recursión intermedia, pero si haces eso es difícil tratar también lasCde la misma manera y además se vuelve mucho más difícil de leer. El enfoque de Raku no necesita backtracking ni recursión, así que puede ejecutarse bastante rápido.Si lo envuelves en una gramática obtienes un árbol de parseo completo; también es posible hacerlo sin gramática, pero usar una lo hace más fácil. Como ejemplo de parser real, suelo recomendar JSON::TINY::Grammar: https://github.com/moritz/json/blob/master/lib/JSON/Tiny/Gra...
Honestamente, gran parte del diseño de los “parsers de verdad” parece un subproducto de la RAM limitada de las primeras computadoras. La etapa separada de tokenización existía para reducir la RAM ocupada por el código fuente y dejar memoria para el análisis semántico y la compilación posteriores, y no creo que simplifique tanto esas etapas siguientes. El módulo JSON::Tiny de arriba construye estructuras de datos nativas de Raku mediante una clase de acciones mientras la gramática parsea, así que en cierto sentido parsea y compila al mismo tiempo.
Uso Raku en producción. Para procesamiento de texto es el mejor lenguaje, y la experiencia de crear parsers es excelente.
Me sorprende que no sea un lenguaje de primer nivel para crear pipelines de texto con LLM.
Guau. Me dan ganas de salirme de la industria antes de que llegue el día en que tenga que mantener una base de código en Raku.
Al ver libros introductorios de Raku, muchas partes se sienten como Python con sintaxis de la familia C. Tiene una sintaxis centrada en llaves, pero la facilidad de uso, las estructuras de datos integradas y las capacidades de orientación a objetos son todas de alto nivel.
Si conoces otros lenguajes de scripting de alto nivel, creo que podrías leer con bastante facilidad el código Raku de scripts similares. No creo que la mayoría use en el código cotidiano las funciones realmente raras; Raku es más flexible, pero el código normal no suele verse críptico.
Eso se ve también en funciones de Raku como poder definir argumentos con nombre en forma abreviada, por ejemplo
:global(:$g), o la capacidad de llamar a una coincidencia global de regex comomatch(/foo/, :g)asumiendo un valorTrue. Perl tiene muchas cosas así, todas pensadas para escribir más rápido y de forma más divertida, pero son menos legibles para principiantes.Creo que ahora se convertirá en una brecha generacional. Es posible que toda una generación de programadores mayores sienta que ha quedado desplazada en un entorno de trabajo que para la generación actual es normal.
Algunas funciones me resultan medio familiares. Hyper suena como una versión más improvisada de algo del lado de recursion-schemes, y el
*que se muestra aquí se parece en cierta medida al_de Scala.Me gusta usar
_en lambdas, así que ojalá todos los lenguajes adoptaran algo parecido.> (2, 30, 4, 50).map(* + *) returns (32, 45)¿no debería serreturns (32, 54)? El segundo término sería 4+50.Tal vez sea resultado de una traducción inicial desde la forma en que en algunos países dicen “cuatro y cincuenta”, como vierenvijftig, en lugar de fifty-four en inglés.
(32 54). Parece que al autor se le fue el dedo.Entonces, ¿Perl ahora es la droga de entrada hacia los lenguajes de la familia APL?
Por ejemplo, se puede usar el metaoperador de reducción
[ ]junto con el+infijo para “sumar todo”:[+] 1, 2, 3Raku sigue teniendo un gran problema de velocidad. Parsear archivos de log con expresiones regulares es una especialidad de Perl, pero incluso el Raku más reciente, sin contar el tiempo de arranque, tarda 6.5 veces más que Python 3.13
Que
(2,4,8...*)[17]dé262144me pareció realmente sorprendenteSi dejamos fuera
Whatever, lo novedoso desde Perl6/Raku probablemente esté más por el lado de la evaluación diferida(1,3,9...*)[4,5]da(81 243), y(1,3,9...*)[(1..3)]da(3 9 27).También se puede anidar, así que son posibles expresiones como
(1,2,4...*)[(1,2,4...*)[1,2,3]]Es una selección de funciones interesantes de Raku.
Creo que las funciones más importantes de Raku son la interfaz de línea de comandos y las gramáticas (grammar).
El soporte para interfaz de línea de comandos es una función común; por ejemplo, existen implementaciones y casos de adopción de
docopt. Pero en Raku esta función viene integrada y es cómoda de usar.En cambio, las gramáticas son una función poco común porque el lenguaje de programación ofrece las gramáticas como componentes de primera clase, y permite crearlas y combinarlas mediante programación orientada a objetos
He seguido este proyecto durante años y, aunque me parece interesante, me da mucha lástima que Perl 6 se haya perdido tanto en este viaje al otro lado del espejo