1 puntos por GN⁺ 2024-11-14 | 1 comentarios | Compartir por WhatsApp
  • 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, any y one, 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|10 se convierte en any(2, 10)
    • 2&10 + 3 se convierte en all(5, 13)
    • (1&2) + (10^20) se combina como all(one(11, 21), one(12, 22))
  • El uso principal de Junctions es expresar fórmulas booleanas como all y any
    • no hay forma de desestructurar un junction
    • para usarlo realmente, primero hay que plegarlo a un booleano
  • so fuerza la conversión de un junction a booleano
    • so (1&2) + (10^20) < 15 da True
    • so (1&2) + (10^20) > 0 da False
  • En Raku, list.all no es un método que reciba argumentos, sino un método sin argumentos que convierte una lista en junction
    • <1 2 3>.all se convierte en all(1, 2, 3)
    • al aplicar is-prime($x), se aplica a cada valor y queda algo como all(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

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ímite
    • 1..* equivale a 1..Inf
    • (2,4,8...*)[17] es 262144
  • Una expresión que contiene * se eleva a función anónima
    • a este comportamiento se le llama whatever-priming
    • el resultado es WhateverCode y, salvo por el tipo, no se distingue de otras funciones
    • (* + 10)(2) es 12
    • (^10).map(* % 2) crea el patrón 0 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] es l[10-1], o sea el último elemento
    • l.head(2) toma los dos primeros elementos, y l.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 regex foo una 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
  • Al romper la retrocompatibilidad también se añadieron varias funciones de conveniencia
    • \\d+ % ',' coincide con 1, 1,2 y 1,1,4, pero no con 1, ni con 12
    • lookahead y non-capturing group no usan combinaciones crípticas de símbolos
    • r1 && r2 solo coincide con cadenas que satisfacen tanto r1 como r2
    • : 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 a map, pero desciende recursivamente dentro de sublistas
    • [1, [2, 3], 4]>>.succ da [2 [3 4] 5]
  • En Raku, una llamada de función de la forma f(list, *args) puede transformarse a la forma de método list.&f(* args)
    • por eso >>. funciona como un operador de mapeo generalizado
    • junto con Whatever, puede usarse como [1, [2, 3], 4]>>.&(*+1)
  • El hyperoperator binario l1 << op >> l2 aplica op elemento 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] <<+>> 10 da [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 op sobre 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

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
  • Raku tiene muchas formas de colon syntax para escribir un pair de forma corta
    • my $x = 3; :$x significa x => 3
    • :a<$x> significa a => "$x"
    • :a($x) significa a => 3
    • :3a significa a => 3
  • La abreviación más importante es :key y :!key
    • se mapean respectivamente a key => True y key => False
    • se usan como forma concisa de añadir flags a un método
  • El método match recibe 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 $foo y además crea el alias :f
    • quien llama puede activar el mismo flag con :f o con :foo
  • 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
  • RakuAST es una función para parsear expresiones de Raku como AST dentro del propio Raku
    • Q/my $x; $x++/.AST muestra la declaración de variable y el operador de incremento postfix como RakuAST::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

 
GN⁺ 2024-11-14
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-expression
    Claro 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.

    • Un “parser de verdad” no es lo bastante potente para parsear Raku.
      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 A seguidos por el mismo número de B y el mismo número de C; en la mayoría de las gramáticas de expresiones regulares, para hacer coincidir el mismo número de A y B se necesita recursión intermedia, pero si haces eso es difícil tratar también las C de 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.

    • Llegué tarde, pero me pregunto si conoces algún buen ejemplo de cómo llamar una API HTTPS con Raku y hacer polling hasta que devuelva cierto valor.
    • Me interesa saber si usas algún paquete de LLM de Raku y, si es así, cuál.
  • 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.

    • Curiosamente, después de leer esa publicación del blog, me dieron ganas de renunciar a mi trabajo e ir a buscar al equipo de Raku para trabajar con ellos. Quizá todavía soy demasiado ingenuo :)
    • Si nunca has visto código Raku típico, es entendible que el artículo provoque esa reacción.
      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.
    • Es parecido a Perl. Nadie quiere mantenerlo, pero usarlo es realmente divertido. Tiene muchísima expresividad.
      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 como match(/foo/, :g) asumiendo un valor True. 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.
    • Es raro que la gente diga cosas parecidas sobre mantener bases de código escritas con asistencia de IA.
      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.
    • No creo que Raku sea un lenguaje pensado para la “industria”.
  • 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 ser returns (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.

    • Lo comprobé en rakudo y en realidad devuelve (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?

    • Sí. Además, el artículo ni siquiera cubrió los metaoperadores.
      Por ejemplo, se puede usar el metaoperador de reducción [ ] junto con el + infijo para “sumar todo”: [+] 1, 2, 3
  • Raku 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

    • Habría que incluir el ejemplo. En mi experiencia, hay cosas en las que Raku es más rápido y otras en las que es más lento, así que decir simplemente “Raku tarda 6.5 veces más que Python 3.13” no significa mucho si no se ve en qué es lento
  • Que (2,4,8...*)[17]262144 me pareció realmente sorprendente

    • A mí lo primero que se me vino a la cabeza fue la palabra “horrible”, y luego pensé en “resultado horrible” y en el problema de la parada, pero cada quien tiene sus gustos
    • La detección del incremento existe desde hace mucho en Perl6, así que no es una función nueva.
      Si dejamos fuera Whatever, lo novedoso desde Perl6/Raku probablemente esté más por el lado de la evaluación diferida
    • Es bastante consistente.
      (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

    • Me pregunto qué estará haciendo Patrick Michaud estos días y cómo le irá