Este documento es una guía rápida del lenguaje de programación Raku.
Para los novatos en Raku sería un punto de partida y puesta en marcha.

Algunas partes de este documento hacen referencia a otras partes (más completas y precisas) de la Documentación de Raku. Consulta la documentación si necesitas más información sobre algo concreto.

A lo largo de este documento encontrarás ejemplos de los temas más comentados. Es conveniente que pruebes todos los ejemplos para entenderlos bien.

Licencia

Este trabajo está bajo la licencia Creative Commons Attribution-ShareAlike 4.0 International License. Puedes encontrar una copia de esta licencia en

Colaboración

Puedes colaborar en este documento en:

Sugerencias y comentarios

Si te gusta este trabajo clica en Star del repositorio de Github.

1. Introducción

1.1. Qué es Raku

Raku es un lenguaje de alto nivel, de propósito general y de tipado gradual. Raku es multiparadigma y soporta programación Procedimental, Orientada a Objetos y Funcional.

El lema de Raku:
  • TMTOWTDI (Pronunciado como Tim Toady): There is more than one way to do it (Hay más de una forma para hacer algo).

1.2. Jerga

  • Raku: Es una especificación con un banco de pruebas. Las implementaciones que pasan el banco de pruebas de la especificación se consideran Raku.

  • Rakudo: Es un compilador para Raku.

  • Zef: Es un instalador de módulos de Raku.

  • Rakudo Star: Es un paquete que incluye Rakudo, Zef, una colección de módulos de Raku y documentación.

1.3. Instalación de Raku

Linux

Instalación de Rakudo Star desde la línea de comandos:

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

Tienes más información en https://rakudo.org/star/source

macOS

Tienes cuatro opciones:

  • Sigue los mismos pasos de la instalación para Linux

  • Realizar la instalación con homebrew: brew install rakudo-star

  • Realizar la instalación con MacPorts: sudo port install rakudo

  • Descargar el último instalador (archivo con extensión .dmg) desde https://rakudo.org/latest/star/macos

Windows
  1. Para arquitectura 64-bit: descarga el instalador más reciente (.msi) desde https://rakudo.org/latest/star/win

  2. Finalizada la instalación, comprueba que C:\rakudo\bin figura en el PATH del sistema.

Docker
  1. Consigue la imagen oficial de Docker docker pull rakudo-star

  2. Y ejecuta un contenedor con la imagen docker run -it rakudo-star

1.4. Ejecutando código en Raku

Puedes ejecutar código Raku mediante REPL (Read-Eval-Print Loop). Para ello, abre un terminal, introduce perl6 y pulsa [Enter]. Aparecerá el prompt >. Ahora introduce una línea de código, pulsa [Enter] y aparecerá una línea nueva con el resultado. Puedes introducir otra línea o exit y pulsar [Enter] para salir al sistema.

También puedes escribir tu código en un archivo de texto, guardarlo y ejecutarlo. Es recomendable que los scripts de Raku tengan la extensión .raku. Ejecuta el archivo de esta forma: perl6 nombre-archivo.raku desde un terminal y pulsa [Enter]. La ejecución suele mostrar el resultado de sentencias como say para visualizar por la salida estándar contenidos de texto con un salto de línea al final .

REPL normalmente se utiliza para probar trozos pequeños de código, como una línea. En el caso de programas con más de una línea de código es recomendable guardarlos en un archivo y ejecutarlos como hemos visto.

También puedes ejecutar una línea de código de forma "in-line" mediante el parámetro -e de la siguiente forma: perl6 -e 'línea de código' y pulsando [Enter].

Rakudo Star incorpora un editor de líneas con más funcionalidades para REPL.

Si instalaste Rakudo en lugar de Rakudo Star es probable que no tengas estas funcionalidades (historial con flechas verticales, edición de la línea con flechas horizontales, autocompletar con TAB, etc.). Para instalar estas funcionalidades utiliza estos comandos:

  • zef install Linenoise debe funcionar en Windows, Linux y macOS

  • zef install Readline si tienes Linux y prefieres la librería Readline

1.5. Editores

Como casi siempre vamos a guardar nuestros programas de Raku en archivos, necesitamos un editor de textos decente que reconozca la sintaxis de Raku.

Yo recomiendo y utilizo Atom. Es un editor de textos moderno que reconoce y visualiza bien la sintaxis de Raku. Perl 6 FE es un paquete de Atom con una visualización alternativa de la sintaxis de Raku, deriva del paquete original, tiene muchas correcciones y más funcionalidades.

En la comunidad también se utiliza Vim, Emacs o Padre.

Las últimas versiones de Vim permiten la visualización de la sintaxis mediante plug-ins; Emacs y Padre necesitan instalar paquetes adicionales.

1.6. ¡Hola Mundo!

Comenzamos con El ritual hola mundo.

say 'hola mundo';

que también puede escribirse como:

'hola mundo'.say;

1.7. Sintaxis general

Raku tiene forma libre: generalmente los espacios en blanco carecen de significado salvo en ciertos casos.

Una Sentencia normalmente es una línea lógica de código que finaliza en punto y coma: say "Hola" if True;

Las Expresiones son sentencias especiales que devuelven un valor: 1+2 devuelve 3

Las expresiones están formadas por Términos y Operadores.

Los Términos pueden ser:

  • Variables: Un valor que puede manipularse y ser cambiado.

  • Literales: Un valor constante como un número o un texto.

Los Operadores se clasifican en estos tipos:

Tipo

Significado

Ejemplo

Prefijo

Antes del término

++1

Infijo

Entre términos

1+2

Sufijo

Después del término

1++

Circumfijo

Al principio y al final del término

(1)

Precircumfijo

Después del término, al principio y al final de otro

Array[1]

1.7.1. Identificadores

Los identificadores son los nombres que se le dan a los términos cuando los defines.

Reglas:
  • Deben comenzar con un carácter alfabético o un guión bajo.

  • Pueden contener dígitos excepto en el primer carácter.

  • Pueden contener guión medio o apóstrofe seguido de un carácter alfabético, no al final.

Válido

No válido

var1

1var

var-uno

var-1

var’uno

var'1

var1_

var1'

_var

-var

Convención de nombres:
  • Notación Camello: variableNum1

  • Notación Kebab: variable-num1

  • Notación Serpiente: variable_num1

Puedes nombrar tus identificadores como quieras, pero es recomendable utilizar una convención consistente.

Utiliza nombres significativos para hacerlo más fácil, a tí y a los demás.

  • var1 = var2 * var3 es correcto pero no tiene un propósito evidente.

  • mes-salario = dia-frecuencia * dias-trabajo es una buena forma de nombrar las variables.

1.7.2. Comentarios

Un comentario es un texto, sirve como anotación y el compilador no lo tiene en cuenta.

Hay 3 tipos de comentarios:

  • De una línea:

    # Esto es un comentario de una línea
  • Incrustado:

    say #`(Esto es un comentario incrustado) "Hola Mundo."
  • De varias líneas:

    =begin comentario
    Esto es un comentario de varias líneas.
    Comentario 1
    Comentario 2
    =end comentario

1.7.3. Comillas

El texto tiene que ir entre comillas dobles o simples.

Utiliza siempre comillas dobles:

  • si el texto contiene un apóstrofe.

  • si el texto necesita visualizar el texto de una variable (interpolación de variable).

say 'Hola Mundo';   # Hola Mundo
say "Hola Mundo";   # Hola Mundo
say "Ven pa'ca cordera";    # Ven pa'ca cordera
my $nombre = 'Juan De Dios';
say 'Hola $nombre';   # Hola $nombre
say "Hola $nombre";   # Hola Juan De Dios

2. Operadores

2.1. Operadores comunes

La siguiente tabla muestra los operadores más utilizados.

Operador Tipo Descripción Ejemplo Resultado

+

Infijo

Suma

1 + 2

3

-

Infijo

Resta

3 - 1

2

*

Infijo

Multiplicación

3 * 2

6

**

Infijo

Potencia

3 ** 2

9

/

Infijo

División

3 / 2

1.5

div

Infijo

División Entera (redondeo inferior)

3 div 2

1

%

Infijo

Resto

7 % 4

3

%%

Infijo

Divisible

6 %% 4

False

6 %% 3

True

gcd

Infijo

Máximo común divisor

6 gcd 9

3

lcm

Infijo

Mínimo común múltiplo

6 lcm 9

18

==

Infijo

Igual numérico

9 == 7

False

!=

Infijo

No igual numérico

9 != 7

True

<

Infijo

Menor que

9 < 7

False

>

Infijo

Mayor que

9 > 7

True

<=

Infijo

Menor o igual

7 <= 7

True

>=

Infijo

Mayor o igual

9 >= 7

True

eq

Infijo

Texto igual

"Juan" eq "Juan"

True

ne

Infijo

Texto no igual

"Juan" ne "Juana"

True

=

Infijo

Asignación

my $var = 7

Asigna el valor 7 a la variable $var

~

Infijo

Texto concatenado

9 ~ 7

97

"Buenos " ~ "días"

Buenos días

x

Infijo

Texto replicado

13 x 3

131313

"Hola " x 3

Hola Hola Hola

~~

Infijo

Expresión regular

2 ~~ 2

True

2 ~~ Int

True

"Raku" ~~ "Raku"

True

"Raku" ~~ Str

True

"iluminación" ~~ /ilumina/

「ilumina」

++

Prefijo

Incremento

my $var = 2; ++$var;

Incrementa la variable por 1 y devuelve 3 como resultado

Sufijo

Incremento

my $var = 2; $var++;

Devuelve la variable 2 y después la incrementa

--

Prefijo

Decremento

my $var = 2; --$var;

Decrementa la variable en 1 y devuelve 1 como resultado

Sufijo

Decremento

my $var = 2; $var--;

Devuelve la variable 2 y después la decrementa

+

Prefijo

Fuerza el operando a un valor numérico

+"3"

3

+True

1

+False

0

-

Prefijo

Fuerza el operando a un valor numérico y devuelve la negación

-"3"

-3

-True

-1

-False

0

?

Prefijo

Fuerza el operando a un valor booleano

?0

False

?9.8

True

?"Hola"

True

?""

False

my $var; ?$var;

False

my $var = 7; ?$var;

True

!

Prefijo

Fuerza el operador a un valor booleano y devuelve la negación

!4

False

..

Infijo

Constructor de Rangos

0..5

Crea un rango cuyo intervalo es [0, 5] [1]

..^

Infijo

Constructor de Rangos

0..^5

Crea un rango cuyo intervalo es [0, 5) [1]

^..

Infijo

Constructor de Rangos

0^..5

Crea un rango cuyo intervalo es (0, 5] [1]

^..^

Infijo

Constructor de Rangos

0^..^5

Crea un rango cuyo intervalo es (0, 5) [1]

^

Prefijo

Constructor de Rangos

^5

Igual que 0..^5 Crea un rango cuyo intervalo es [0, 5) [1]

…​

Infijo

Constructor de listas perezosas

0…​9999

devuelve los elementos si son solicitados

|

Prefijo

Aplanamiento

|(0..5)

(0 1 2 3 4 5)

|(0^..^5)

(1 2 3 4)

2.2. Intercambio de Operandos

Al agregar R delante de cualquier operador hace que se intercambien sus operandos.

Operación original Resultado Intercambio de operandos Resultado

2 / 3

0.666667

2 R/ 3

1.5

2 - 1

1

2 R- 1

-1

2.3. Reducción de Operadores

La reducción de operadores se utiliza en listas de valores. Se forman encerrando el operador entre corchetes []

Operación original Resultado Reducción de Operadores Resultado

1 + 2 + 3 + 4 + 5

15

[+] 1,2,3,4,5

15

1 * 2 * 3 * 4 * 5

120

[*] 1,2,3,4,5

120

En https://docs.raku.org/language/operators tienes una lista completa de los operadores, incluyendo su precedencia.

3. Variables

Las variables en Raku se reparten en tres categorías: Escalares, Arrays y Hashes.

Un sigilo (Signo en Latín) es un carácter utilizado como prefijo para categorizar variables.

  • $ para escalares

  • @ para arrays

  • % para hashes

3.1. Escalares

Un escalar contiene un valor o referencia.

# Texto
my $nombre = 'Juan De Dios';
say $nombre;

# Entero
my $edad = 99;
say $edad;

Dependiendo del valor que aloje, se pueden realizar un conjunto específico de operaciones en un escalar.

Texto
my $nombre = 'Juan De Dios';
say $nombre.uc;
say $nombre.chars;
say $nombre.flip;
JUAN DE DIOS
12
soiD eD nauJ
Consulta https://docs.raku.org/type/Str para ver la lista completa de métodos de texto.
Enteros
my $edad = 17;
say $edad.is-prime;
True
Consulta https://docs.raku.org/type/Int para ver la lista completa de métodos de enteros.
Números Racionales
my $edad = 2.3;
say $edad.numerator;
say $edad.denominator;
say $edad.nude;
23
10
(23 10)
Consulta https://docs.raku.org/type/Rat para ver la lista completa de métodos de números racionales.

3.2. Arrays

Los Arrays son listas que contienen varios valores.

my @animales = 'camello','llama','búho';
say @animales;

Se pueden realizar muchas operaciones con arrays, como las de este ejemplo:

La tilde ~ se utiliza para concatenar texto.
Script
my @animales = 'camello','vicuña','llama';
say "El zoo tiene " ~ @animales.elems ~ " animales";
say "Los animales son: " ~ @animales;
say "He conseguido un búho para el zoo";
@animales.push("búho");
say "Los animales del zoo ahora son: " ~ @animales;
say "El primer animal del zoo es: " ~ @animales[0];
@animales.pop;
say "Desafortunadamente el búho se escapó y los animales que quedan son: " ~ @animales;
say "Vamos a dejar solo una animal en el zoo";
say "Dejamos ir a: " ~ @animales.splice(1,2) ~ " y dejamos en el zoo al " ~ @animales;
Salida
El zoo tiene 3 animales
Los animales son: camello vicuña llama
He conseguido un búho para el zoo
Los animales del zoo ahora son: camello vicuña llama búho
El primer animal del zoo es: camello
Desafortunadamente el búho se escapó y los animales que quedan son: camello vicuña llama
Vamos a dejar solo una animal en el zoo
Dejamos ir a: vicuña llama y dejamos en el zoo al camello
Explicación

.elems devuelve el número de elementos de un array.
.push() añade uno o más elementos a un array.
Podemos acceder a un elemento concreto del array indicando su posición @animales[0].
.pop elimina el último elemento del array y lo devuelve.
.splice(a,b) elimina b elementos que comienzan en la posición a.

3.2.1. Arrays de tamaño fijo

Un array básico se declara así:

my @array;

El array básico puede tener un número indefinido de valores y por eso se denomina auto-extendible.
Un array puede tener cualquier número de valores sin restricciones.

En contraste, también podemos crear arrays de tamaño fijo.
En estos arrays se define un tamaño fijo y no puede crecer más allá de este tamaño.

Para declarar un array de tamaño fijo, especifica el número máximo de elementos entre corchetes justo después de su nombre:

my @array[3];

Este array tendrá un máximo de 3 valores, indexados desde 0 a 2.

my @array[3];
@array[0] = "primer valor";
@array[1] = "segundo valor";
@array[2] = "tercer valor";

No puedes agregar un cuarto valor a este array:

my @array[3];
@array[0] = "primer valor";
@array[1] = "segundo valor";
@array[2] = "tercer valor";
@array[3] = "cuarto valor";
Index 3 for dimension 1 out of range (must be 0..2)

3.2.2. Arrays multidimensionales

Los arrays que hemos visto hasta ahora son de una dimensión.
Con Raku, podemos definir arrays de varias dimensiones.

my @tbl[3;2];

Este array es de dos dimensiones. La primera dimensión puede tener un máximo de 3 valores y la segunda dimensión un máximo de 2 valores.

Imagínalo como una tabla de 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]]
Representación visual del array:
[1 x]
[2 y]
[3 z]
Consulta https://docs.raku.org/type/Array para tener la referencia completa sobre Arrays.

3.3. Hashes

Un Hash es una colección de pares Clave/Valor.
my %capitales = 'UK','Londres','Alemania','Berlín';
say %capitales;
Otra forma de insertar valores en un hash:
my %capitales = UK => 'Londres', Alemania => 'Berlín';
say %capitales;

Algunos de los métodos aplicables a los hashes son:

Script
my %capitales = UK => 'Londres', Alemania => 'Berlín';
%capitales.push: (Francia => 'París');
say %capitales.kv;
say %capitales.keys;
say %capitales.values;
say "La capital de Francia es: " ~ %capitales<Francia>;
Salida
(Alemania Berlín Francia París UK Londres)
(Alemania Francia UK)
(Berlín París Londres)
La capital de Francia es: París
Explicación

.push: (Clave => 'Valor') agrega un nuevo par clave/valor.
.kv devuelve una lista con todas las claves y valores.
.keys devuelve una lista con todas las claves.
.values devuelve una lista con todos los valores.
Podemos acceder a un valor concreto del hash indicando su clave %hash<clave>

Consulta https://docs.raku.org/type/Hash para una referencia completa sobre hashes.

3.4. Tipos

En los ejemplos anteriores no hemos especificado el tipo de valor que debería contener cada variable.

.WHAT devuelve el tipo del valor que contiene la variable.
my $var = 'Texto';
say $var;
say $var.WHAT;

$var = 123;
say $var;
say $var.WHAT;

Como puedes ver en el ejemplo anterior, el tipo de valor en $var primero fue texto (Str) y después entero (Int).

Este estilo de programación se caracteriza por ser de tipado dinámico. Dinámico en el sentido de que las variables pueden contener valores de Cualquier tipo.

Ahora intenta ejecutar el siguiente ejemplo:
Fíjate en el Int indicado antes de la variable.

my Int $var = 'Texto';
say $var;
say $var.WHAT;

Este ejemplo devuelve un error indicando: Type check failed in assignment to $var; expected Int but got Str

Lo que ocurre es que hemos especificado como entero (Int) el tipo de la variable y falla al intentar asignar en ella un texto (Str).

Este estilo de programación se caracteriza por ser de tipado estático. Estático en el sentido de que la variable se define con un tipo determinado antes de asignarla y este tipo no puede cambiarse después.

Raku es un lenguaje de tipado gradual; lo que permite tipado estático y dinámico.

Los arrays y hashes también pueden tener tipado estático:
my Int @array = 1,2,3;
say @array;
say @array.WHAT;

my Str @multilengua = "Hello","Salut","Hallo","您好","안녕하세요","こんにちは";
say @multilengua;
say @multilengua.WHAT;

my Str %capitales = UK => 'London', Alemania => 'Berlín';
say %capitales;
say %capitales.WHAT;

my Int %código-país = UK => 44, Alemania => 49;
say %código-país;
say %código-país.WHAT;
A continuación tienes una lista con los tipos más comunes:

Es posible que nunca utilices los dos primeros, pero aparecen en la siguiente lista para que sepas que existen.

Tipo

Descripción

Ejemplo

Resultado

Mu

La raíz de la jerarquía de tipos de Raku

Any

Clase base por defecto para nuevas clases y para la mayoría de las clases nativas

Cool

Valor que puede tratarse como texto o número indistintamente

my Cool $var = 31; say $var.flip; say $var * 2;

13 62

Str

Texto o cadena de carácteres

my Str $var = "NEON"; say $var.flip;

NOEN

Int

Entero (independientemente de la precisión)

7 + 7

14

Rat

Número racional (precisión limitada)

0.1 + 0.2

0.3

Bool

Booleano

!True

False

3.5. Introspección

Introspección es el proceso para adquirir información sobre las propiedades de un objeto, como por ejemplo su tipo.
En uno de los ejemplos anteriores utilizamos .WHAT para conocer el tipo de una variable.

my Int $var;
say $var.WHAT;    # (Int)
my $var2;
say $var2.WHAT;   # (Any)
$var2 = 1;
say $var2.WHAT;   # (Int)
$var2 = "Hola";
say $var2.WHAT;   # (Str)
$var2 = True;
say $var2.WHAT;   # (Bool)
$var2 = Nil;
say $var2.WHAT;   # (Any)

El tipo de una variable que contiene un valor se corresponde con su valor.
El tipo de una variable declarada de forma estática y sin valor es el tipo con el que se ha declarado.
El tipo de una variable vacía que no ha sido declarada de forma estática es (Any).
Asigna Nil a una variable para eliminar su valor.

3.6. Alcance

Es necesario declarar una variable antes de utilizarla.

Raku dispone de varias formas de declaración, y de momento estamos utilizando my.

my $var=1;

La forma de declaración my proporciona a la variable un alcance léxico. Dicho de otro modo, la variable solo es accesible desde el mismo bloque donde es declarada.

En Raku un bloque está delimitado por { }.

En caso de no existir bloque, la variable estará disponible en el script entero.

{
  my Str $var = 'Texto';
  say $var; # accesible
}
say $var; # no accesible, devuelve un error

Como la variable solo es accesible dentro del bloque donde está definida, el mismo nombre de variable puede utilizarse en cualquier otro bloque.

{
  my Str $var = 'Texto';
  say $var;
}
my Int $var = 123;
say $var;

3.7. Asignación vs. Vinculación

En los ejemplos anteriores hemos visto cómo asignar valores a variables.
La asignación se realiza mediante el operador =.

my Int $var = 123;
say $var;

Y podemos cambiar el valor asignado a la variable:

Asignación
my Int $var = 123;
say $var;
$var = 999;
say $var;
Salida
123
999

Por otro lado, no podemos cambiar el valor vinculado de una variable.

La vinculación se realiza mediante el operador :=.

Vinculación
my Int $var := 123;
say $var;
$var = 999;
say $var;
Salida
123
Cannot assign to an immutable value
Las variables también pueden vincularse a otras variables:
my $a;
my $b;
$b := $a;
$a = 7;
say $b;
$b = 8;
say $a;
Salida
7
8

Como has visto, la vinculación de variables es bidireccional.
$a := $b y $b := $a tienen el mismo efecto.

En https://docs.raku.org/language/variables tienes más información sobre variables.

4. Funciones y mutadores

Es importante diferenciar entre funciones y mutadores.

Las funciones no cambian el estado del objeto donde se aplica.

Los mutadores modifican el estado del objeto.

Script
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
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
Salida
[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
Explicación

.push es un mutador porque cambia el estado del array (#1)

.sort es una función porque devuelve un array ordenado pero no cambia el estado inicial del array:

  • (#2) muestra la devolución de un array ordenado.

  • (#3) muestra que el estado inicial del array no ha cambiado.

Puedes hacer que una función se comporte como un mutador utilizando .= en lugar de . (#4) (línea 9 del script)

5. Bucles y condiciones

Raku tiene muchos constructores de bucles y condiciones.

5.1. if

El código se ejecuta solo si se cumple una condición, por ej., una expresión se evalúa como True.

my $edad = 19;

if $edad > 18 {
  say 'Bienvenido'
}

En Raku podemos invertir el código y la condición, y aún así la condición siempre se evalúa primero.

my $edad = 19;

say 'Bienvenido' if $edad > 18;

Si la condición no se cumple, podemos especificar bloques alternativos de ejecución utilizando:

  • else

  • elsif

# ejecuta el mismo código para distintos valores de la variable
my $número-de-asientos = 9;

if $número-de-asientos <= 5 {
  say 'Soy un sedan'
} elsif $número-de-asientos <= 7 {
  say 'Tengo 6 o 7 asientos'
} else {
  say 'Soy un microbus'
}

5.2. unless

La negación de if es unless.

El siguiente código:

my $limpiar-zapatos = False;

if not $limpiar-zapatos {
  say 'Limpia tus zapatos'
}

puede escribirse como:

my $limpiar-zapatos = False;

unless $limpiar-zapatos {
  say 'Limpia tus zapatos'
}

La negación en Raku se realiza con ! o con not.

unless (condición) se utiliza en lugar de if not (condición).

unless no puede utilizar la claúsula else.

5.3. with

with es como if pero solo comprueba si la variable está definida.

my Int $var=1;

with $var {
  say 'Hola'
}

No ocurre nada si ejecutas el código sin asignar un valor a la variable.

my Int $var;

with $var {
  say 'Hola'
}

without es la negación de with y es parecido a unless.

Si la primera condición with no se cumple, puedes indicar una alternativa mediante orwith.
with y orwith son parecidos a if y elsif.

5.4. for

for itera sobre una serie de valores.

my @array = 1,2,3;

for @array -> $array-item {
  say $array-item * 100
}

Observa que en la iteración hemos creado la variable $array-item para realizar después la operación *100 en cada elemento del array.

5.5. given

En Raku given viene a ser switch en otros lenguajes, pero mucho más potente.

my $var = 42;

given $var {
    when 0..50 { say 'Menos o igual a 50'}
    when Int { say "es un Entero" }
    when 42  { say 42 }
    default  { say "¿ejem?" }
}

Cuando se produce la coincidencia no se evalúan las demás.

Como opción, proceed continúa la evaluación aunque se produzca la coincidencia.

my $var = 42;

given $var {
    when 0..50 { say 'Menos o igual a 50';proceed}
    when Int { say "es un Entero";proceed}
    when 42  { say 42 }
    default  { say "¿ejem?" }
}

5.6. loop

loop es otra forma de escribir un for.

Actualmente loop viene a ser el for utilizado en la familia de lenguajes de C.

Raku pertenece a la familia de lenguajes de C.

loop (my $i = 0; $i < 5; $i++) {
  say "El número actual es $i"
}
En https://docs.raku.org/language/control tienes más información sobre bucles y condiciones

6. I/O

En Raku, las dos interfaces más utilizadas de Entrada/Salida son el Terminal y los Ficheros.

6.1. E/S básica mediante el Terminal

6.1.1. say

say escribe en la salida estándar agregando al final una línea nueva. En otras palabras, el siguiente código:

say 'Hola Mamá.';
say 'Hola Señor.';

escribirá dos líneas separadas.

6.1.2. print

Por otro lado print es como say pero no agrega la línea nueva.

Prueba a utilizar say en lugar de print y compara ambos resultados.

6.1.3. get

Para capturar la entrada desde el terminal utiliza get.

my $nombre;

say "¡Hola!, ¿cual es tu nombre?";
$nombre = get;

say "¿Que tal $nombre?, bienvenido a Raku";

Este código hace que el terminal espere la introducción de tu nombre. Hazlo y después pulsa [Enter]. Como resultado te dará la bienvenida.

6.1.4. prompt

prompt es una combinación entre print y get.

El ejemplo anterior puede escribirse de esta otra forma:

my $nombre = prompt "¡Hola!, ¿cual es tu nombre? ";

say "¿Que tal $nombre?, bienvenido a Raku";

6.2. Ejecutando Comandos de la Shell

Podemos utilizar dos subrutinas para ejecutar comandos de la shell:

  • run Ejecuta un comando externo sin la intervención de la shell.

  • shell Ejecuta un comando desde la shell del sistema y dependerá de la plataforma y la shell. Todos los caracteres especiales los interpreta la shell, como pueden ser las tuberías, redirecciones, sustitución de variables de entorno, etc.

Ejecuta el siguiente script en Linux/macOS
my $nombre = 'Neo';
run 'echo', "Hola $nombre";
shell "ls";
Ejecuta lo siguiente en Windows
shell "dir";

echo y ls son palabras clave típicas de la shell de Linux:
echo visualiza texto en el terminal (es el equivalente a say en Raku)
ls muestra un listado de todos los archivos y carpetas del directorio actual

dir en Windows es el equivalente de ls en Linux.

6.3. E/S de Archivos

6.3.1. slurp

slurp lee datos de un archivo.

Crea un archivo de texto con el siguiente contenido:

datos.txt
Juan 9
Juanito 7
Juana 8
Juanita 7
my $datos = slurp "datos.txt";
say $datos;

6.3.2. spurt

spurt escribe datos en un archivo.

my $datos-nuevos = "Nuevas puntuaciones:
Pablo 10
Pablin 9
Paulo 11";

spurt "datos-nuevos.txt", $datos-nuevos;

El código anterior crea un nuevo archivo llamado datos-nuevos.txt conteniendo las nuevas puntuaciones.

6.4. Manipulando archivos y carpetas

Raku puede mostrar el contenido de una carpeta sin recurrir a los comandos de la shell (utilizando ls por ejemplo).

say dir;               # Muestra archivos y carpetas de la carpeta actual
say dir "/Documentos"; # Muestra archivos y carpetas de la carpeta indicada

Además, puedes crear y eliminar carpetas.

mkdir "carpeta-nueva";
rmdir "carpeta-nueva";

mkdir crea una carpeta nueva.
rmdir elimina una carpeta vacía y devuelve un error sino está vacía.

También puedes comprobar si una ruta existe y si es un archivo o una carpeta:

Crea una carpeta vacía llamada carpeta123, un archivo vacío llamado script123.raku y el siguiente script:

say "script123.raku".IO.e;
say "carpeta123".IO.e;

say "script123.raku".IO.d;
say "carpeta123".IO.d;

say "script123.raku".IO.f;
say "carpeta123".IO.f;

Ejecuta el script.

IO.e comprueba si existe la carpeta/archivo.
IO.f comprueba si la ruta es un archivo.
IO.d comprueba si la ruta es una carpeta.

en Windows puedes utilizar / o \\ para separar carpetas anidadas
C:\\rakudo\\bin
C:/rakudo/bin
En https://docs.raku.org/type/IO tienes más información sobre E/S.

7. Subrutinas

7.1. Definición

Las Subrutinas (también denominadas subs o funciones) son una forma de empaquetar y reutilizar funcionalidades.

La definición de una subrutina comienza con la palabra clave sub. Una vez definida puede invocarse mediante su nombre.

Fíjate en el siguiente ejemplo:

sub saludo-alien {
  say "Hola terrícolas";
}

saludo-alien;

El ejemplo anterior es una subrutina sin entrada de datos.

7.2. Signatura

Las subrutinas pueden requerir una entrada, y ésta se proporciona mediante argumentos. Una subrutina puede definir ninguno o varios parámetros. La signatura de una subrutina es el número y el tipo de los parámetros que puede definir.

La siguiente subrutina acepta un argumento de tipo string.

sub di-hola (Str $nombre) {
    say "¡¡Hola " ~ $nombre ~ "!!"
}
di-hola "Pablo";
di-hola "Paula";

7.3. Sobrecarga

Es posible definir varias subrutinas con el mismo nombre pero con signaturas diferentes. Cuando se llama a la subrutina, se decidirá qué versión utilizar en tiempo de ejecución dependiendo del número y tipo de argumentos proporcionados. Este tipo de subrutinas se definen de la misma forma que una subrutina normal pero utilizando la palabra clave multi en lugar de sub.

multi saludo($nombre) {
    say "Buenos días $nombre";
}
multi saludo($nombre, $título) {
    say "Buenos días $título $nombre";
}

saludo "Juanito";
saludo "Laura","Srta.";

7.4. Parámetros por defecto y opcionales

Tendremos un error si se define una subrutina para aceptar un argumento y éste no es proporcionado.

Con Raku podemos definir subrutinas con:

  • Parámetros opcionales

  • Parámetros por defecto

Un parámetro opcional se define añadiendo ? al nombre del parámetro.

sub di-hola($nombre?) {
  with $nombre { say "Hola " ~ $nombre }
  else { say "Hola humano" }
}
di-hola;
di-hola("Laura");

Si no es necesario proporcionar un argumento, puede definirse uno por defecto asignándole un valor al parámetro en la definición de la subrutina.

sub di-hola($nombre="Mateo") {
  say "Hola " ~ $nombre;
}
di-hola;
di-hola("Laura");

7.5. Valores de retorno

Hemos visto que todas las subrutinas hasta ahora siempre hacen algo: mostrar resultados en la pantalla del terminal.

Sin embargo y a veces, utilizamos una subrutina para que nos devuelva algún valor que podamos utilizar después en el flujo del programa.

Si una función se ejecuta hasta el final de su bloque, el valor de retorno vendrá determinado por la última declaración o expresión.

Retorno implícito
sub cuadrado ($x) {
  $x ** 2;
}
say "7 al cuadrado es igual a " ~ cuadrado(7);

Para dejarlo más claro sería una buena idea especificar de forma explícita qué es lo que queremos devolver. Esto se realiza mediante la palabra clave return.

sub cuadrado ($x) {
  return $x ** 2;
}
say "7 al cuadrado es igual a " ~ cuadrado(7);

7.5.1. Restricción de valores de retorno

En uno de los ejemplos anteriores vimos cómo restringir el tipo del argumento aceptado. Lo mismo podemos hacer con los valores de retorno.

Para restringir el valor de retorno a un tipo determinado utilizamos la notación de flecha --> en la signatura.

Utilizando el tipo de retorno
sub cuadrado ($x --> Int) {
  return $x ** 2;
}
say "1.2 al cuadrado es igual a " ~ cuadrado(1.2);

Si el tipo del valor devuelto no coincide con el indicado, tendremos un error.

Type check failed for return value; expected Int but got Rat (1.44)

La restricción del tipo del valor de retorno tambień puede controlar si este valor está definido o no.

En los ejemplos anteriores especificamos que el valor de retorno debería ser de tipo Int.

Además, podemos indicar que el valor de retorno esté obligatoriamente definido o no utilizando las siguientes signaturas:
-→ Int:D y -→ Int:U

Como hemos visto es una buena costumbre utilizar estas restricciones.
A continuación puedes ver la versión modificada del ejemplo anterior utilizando :D para que el tipo devuelto Int esté definido obligatoriamente.

sub cuadrado ($x --> Int:D) {
  return $x ** 2;
}
say "1.2 al cuadrado es igual a " ~ cuadrado(1.2);
En https://docs.raku.org/language/functions encontrarás más información sobre subrutinas y funciones.

8. Programación Funcional

En este apartado veremos algunas características que facilitan la Programación Funcional.

8.1. Las Funciones son de primera clase

Las funciones/subrutinas son de primera clase:

  • Pueden pasarse como argumentos

  • Pueden ser devueltas desde otras funciones

  • Pueden asignarse a variables

Un gran ejemplo es la función map.
map es una función de orden superior que puede aceptar otra función como argumento.

Script
my @array = <1 2 3 4 5>;
sub cuadrado($x) {
  $x ** 2
}
say map(&cuadrado,@array);
Salida
(1 4 9 16 25)
Explicación

Hemos definido la subrutina cuadrado que toma un argumento y lo multiplica por sí mismo.
Después utilizamos map, una función de orden superior, que toma dos argumentos: la subrutina cuadrado y un array.
El resultado es una lista de los cuadrados de cada elemento del array.

Ten en cuenta que cuando pasamos una subrutina como argumento, es necesario utilizar el prefijo & en el nombre.

8.2. Funciones anónimas

Una función anónima también se denomina lambda.
Una función anónima no está vinculada a un identificador (no tiene nombre).

Escribamos de nuevo el ejemplo de map pero utilizando una función anónima

my @array = <1 2 3 4 5>;
say map(-> $x {$x ** 2},@array);

Observa que en lugar de declarar la subrutina cuadrado y pasarla a map como argumento, la definimos dentro de la función anónima como -> $x {$x ** 2}.

En la jerga de Raku nos referimos a esta notación como punto de entrada al bloque

Un punto de entrada al bloque también puede utilizarse para asignar funciones a variables:
my $cuadrado = -> $x {
  $x ** 2
}
say $cuadrado(9);

8.3. Encadenamiento

En Raku los métodos pueden encadenarse, de forma que el resultado de un método pasa como argumento a otro método.

Un ejemplo: dado un array, necesitamos los valores únicos de ese array ordenados de mayor a menor.

A continuación una solución sin encadenamiento:

my @array = <7 8 9 0 1 2 4 3 5 6 7 8 9>;
my @final-array = reverse(sort(unique(@array)));
say @final-array;

Aquí llamamos unique sobre @array, pasamos el resultado como argumento a sort y pasamos el resultado a reverse.

En contraste, con métodos encadenados, el ejemplo anterior puede escribirse así:

my @array = <7 8 9 0 1 2 4 3 5 6 7 8 9>;
my @final-array = @array.unique.sort.reverse;
say @final-array;

Como ves, el encadenamiento de métodos es más visual.

8.4. Operador de Alimentación

El operador de alimentación, llamado tubería en algunos lenguajes de programación funcional, ejemplifica el encadenamiento de métodos.

Alimentación hacia adelante
my @array = <7 8 9 0 1 2 4 3 5 6 7 8 9>;
@array ==> unique()
       ==> sort()
       ==> reverse()
       ==> my @final-array;
say @final-array;
Explicación
Comienza con `@array` y devuelve una lista de elementos únicos
                    después los ordena
                    después invierte el orden
                    después guarda el resultado en @final-array

Fíjate en el flujo de las llamadas a los métodos: se produce de arriba hacia abajo, esto es, desde el primero hasta el último paso.

Alimentación hacia atrás
my @array = <7 8 9 0 1 2 4 3 5 6 7 8 9>;
my @final-array-v2 <== reverse()
                   <== sort()
                   <== unique()
                   <== @array;
say @final-array-v2;
Explicación

La alimentación hacia atrás es parecida a la anterior pero al revés.
El flujo de las llamadas a los métodos es de abajo hacia arriba, esto es, desde el paso final hacia el primero.

8.5. Hiperoperador

El hiperoperador >>. puede aplicar un método a todos los elementos de una lista y devolver una lista con los resultados.

my @array = <0 1 2 3 4 5 6 7 8 9 10>;
sub es-par($var) { $var %% 2 };

say @array>>.is-prime;
say @array>>.&es-par;

Mediante el hiperoperador podemos utilizar todos los métodos ya definidos en Raku, por ej. is-prime que devuelve si un número es primo o no.
Además, podemos definir funciones nuevas y utilizarlas mediante el hiperoperador agregando el prefijo & en el nombre del método, por ej. &es-par.

El uso del hiperoperador es muy práctico pues evita escribir un bucle for para iterar sobre cada valor.

Raku garantiza que el orden de los resultados sea el mismo que el de la lista original. Sin embargo, Raku no garantiza la llamada a los métodos en el orden de la lista o en el mismo hilo. Por tanto, ten cuidado con los métodos que tengan efectos secundarios, como say o print.

8.6. Ensamblajes

Un ensamblaje es una superposición lógica de valores.

En el siguiente ejemplo 1|2|3 es un ensamblaje.

my $var = 2;
if $var == 1|2|3 {
  say "La variable es 1 o 2 o 3"
}

El uso de ensamblajes normalmente produce autothreading para cada elemento del ensamblaje y todos los resultados se combinan y se devuelven en un nuevo ensamblaje.

8.7. Listas perezosas

Una lista perezosa es una lista que se evalúa perezosamente.
La evaluación perezosa demora la evaluación de una expresión hasta que es requerida, guardando mientras los resultados en una tabla de búsqueda para así evitar repetir la evaluación.

Entre los beneficios tenemos:

  • Incremento del rendimiento evitando cálculos innecesarios

  • La habilidad de construir estructuras de datos potencialmente infinitas

  • La habilidad de definir controles de flujo

Podemos definir una lista perezosa utilizando el operador infijo …​
Una lista perezosa tiene elemento(s) inicial(es), un generador y un punto final.

Lista perezonsa simple
my $listaperezosa = (1 ... 10);
say $listaperezosa;

El elemento inicial es 1 y el punto final es 10. Como no hemos definido un generador, por defecto es el sucesor (+1)
Dicho de otra forma, esta lista perezosa puede devolver (si es requerida) los siguientes elementos (1, 2, 3, 4, 5, 6, 7, 8, 9, 10)

Lista perezosa infinita
my $listaperezosa = (1 ... Inf);
say $listaperezosa;

Esta lista puede devolver (si es requerida) cualquier entero entre 1 e infinito, en otras palabras cualquier número entero.

Lista perezosa con generador deductivo
my $listaperezosa = (0,2 ... 10);
say $listaperezosa;

Los elementos iniciales son 0 y 2, y el punto final es 10. Aunque no hay un generador definido, Raku utiliza los elementos iniciales para deducir que el generador es (+2)
Esta lista puede devolver (si es requerida) los siguientes elementos (0, 2, 4, 6, 8, 10)

Lista perezosa con generador definido
my $listaperezosa = (0, { $_ + 3 } ... 12);
say $listaperezosa;

En este ejemplo hemos definido de forma explícita un generador entre llaves { }
Esta lista puede devolver (si es requerida) los siguientes elementos (0, 3, 6, 9, 12)

Al usar un generador de forma explícita el punto final debe ser uno de los valores que el generador pueda devolver.
Si en el ejemplo anterior sustituimos el punto final 12 por un 10, el generador no se detendrá y saltará sobre el punto final y continuará.

De forma alternativa puedes sustituir 0 …​ 10 con 0 …​^ * > 10
Esto lo puedes leer como: De 0 hasta el primer valor mayor a 10 (excluyendo a éste)

Lo siguiente no detiene al generador
my $listaperezosa = (0, { $_ + 3 } ... 10);
say $listaperezosa;
Lo siguiente detiene al generador
my $listaperezosa = (0, { $_ + 3 } ...^ * > 10);
say $listaperezosa;

8.8. Clausuras

Todos los objetos de código en Raku son clausuras, lo que significa que se pueden referenciar variables léxicamente definidas desde un ámbito externo.

sub crear-saludo {
    my $nombre = "Juan Dios";
    sub saludo {
      say "Buenos días $nombre";
    };
    return &saludo;
}
my $saludo-creado = crear-saludo;
$saludo-creado();

Si ejecutas el código anterior verás en el terminal Buenos días Juan Dios.
El resultado es simple, pero lo interesante es que se devuelve la subrutina interna saludo desde una subrutina externa antes de ser ejecutada.

$saludo-creado es una clausura.

Una clausura es una especie de objeto especial que combina dos cosas:

  • Una Subrutina

  • El Entorno donde se creó esa subrutina.

El entorno es cualquier variable local que estaba dentro del alcance en el momento que se creó la clausura. En este caso, $saludo-creado es una clausura que incluye a la subrutina saludo y el texto Juan Dios que existía cuando se creó la clausura.

Veamos un ejemplo más interesante.

sub crear-saludo($momento) {
  return sub ($nombre) {
    return "Buenas $momento $nombre"
  }
}
my $tardes = crear-saludo("Tardes");
my $noches = crear-saludo("Noches");

say $tardes("Juan");
say $noches("Juana");

En este ejemplo hemos definido la subrutina crear-saludo($momento) aceptando el argumento $momento y devuelve una subrutina nueva. La subrutina devuelta acepta el argumento $nombre y devuelve el saludo.

crear-saludo es una fábrica de subrutinas, y en este ejemplo hemos creado dos subrutinas nuevas, una para decir Buenas Tardes y otra para decir Buenas Noches.

$tardes y $noches también son clausuras. Ambas comparten la misma definición del cuerpo de la subrutina pero alojan entornos distintos.
En el entorno de $tardes el $momento es Tardes y en el entorno de $noches el $momento es Noches.

9. Clases y Objetos

En el apartado anterior hemos visto cómo utilizar la Programación Funcional en Raku y en el siguiente apartado veremos cómo utilizar Raku en la Programación Orientada a Objetos.

9.1. Introducción

La programación Orientada a Objetos es uno de los paradigmas de programación más utilizados actualmente.
Un objeto es un conjunto de variables y subrutinas.
Las variables se llaman atributos y las subrutinas se llaman métodos.
Los atributos definen un estado y los métodos definen el comportamiento de un objeto.

Una clase es una plantilla para crear objetos.

Para entender esta relación veamos el siguiente ejemplo:

Hay 4 individuos en una sala

objetos ⇒ 4 personas

Los 4 individuos son humanos

clase ⇒ Humano

Tienen distintos nombres, edades, sexo y nacionalidad

atributos ⇒ nombre, edad, sexo, nacionalidad

En orientación a objetos decimos que los objetos son instancias de una clase.

Veamos el siguiente script:

class Humano {
  has $.nombre;
  has $.edad;
  has $.sexo;
  has $.nacionalidad;
}

my $juan = Humano.new(nombre => 'Juan', edad => 23, sexo => 'M', nacionalidad => 'Español');
say $juan;

La palabra clave class se utiliza para definir una clase.
La palabra clave has se utiliza para definir los atributos de una clase.
El método .new() se denomina constructor y crea el objeto como una instancia de la clase a la que ha sido llamada.

En el script anterior, la variable nueva $juan tiene una referencia a una instancia nueva de "Humano" definida por Humano.new().

Los argumentos que se pasan al método .new() son utilizados para establecer los atributos del objeto en cuestión.

Una clase puede tener un alcance léxico mediante my:

my class Humano {

}

9.2. Encapsulación

La encapsulación es un concepto de la programación Orientada a Objetos que consiste en empaquetar un conjunto de datos y métodos.

Los datos (atributos) dentro de un objeto deben ser privados, dicho de otro modo, solo son accesibles desde dentro del objeto.
Para acceder a los atributos de un objeto desde fuera de él utilizamos métodos de acceso.

Los siguientes dos scripts dan el mismo resultado.

Acceso directo a la variable:
my $var = 7;
say $var;
Encapsulación:
my $var = 7;
sub sayvar {
  $var;
}
say sayvar;

El método sayvar es un método de acceso que nos permite acceder al valor de la variable sin acceder directamente a ella.

Raku realiza la encapsulación mediante twigils (sigilos secundarios) y se ubican entre el sigilo y el nombre del atributo.
En las clases se utilizan dos twigils:

  • ! para indicar de forma explícita que el atributo es privado.

  • . para crear automáticamente un método de accceso al atributo.

Por defecto todos los atributos son privados pero es muy recomendable utilizar siempre el twigil !.

Por lo tanto, podemos reescribir la clase anterior como:

class Humano {
  has $!nombre;
  has $!edad;
  has $!sexo;
  has $!nacionalidad;
}

my $juan = Humano.new(nombre => 'Juan', edad => 23, sexo => 'M', nacionalidad => 'Español');
say $juan;

Si añades al script la siguiente sentencia: say $juan.edad; devolverá el siguiente error: Method 'edad' not found for invocant of class 'Humano' debido a que $!edad es un atributo privado y solo puede utilizarse desde dentro del objeto. Como hemos visto, tendremos un error al intentar acceder a este atributo desde fuera del objeto.

Sustituye has $!edad por has $.edad y comprueba el resultado de say $juan.edad;

9.3. Parámetros Posicionales vs. Parámetros con nombre

En Raku todas las clases heredan un constructor .new() por defecto que puede utilizarse para crear objetos proporcionando argumentos.
El constructor por defecto solo acepta argumentos con nombre.
Como puedes ver en el ejemplo que vimos antes, los argumentos que tiene .new() están definidos con un nombre:

  • nombre => 'Juan'

  • edad => 23

¿Puedo ahorrarme el nombre de cada atributo al crear un objeto? Sí, pero necesito crear otro constructor que acepte argumentos posicionales.

class Humano {
  has $.nombre;
  has $.edad;
  has $.sexo;
  has $.nacionalidad;
  # nuevo constructor que sustituye el de por defecto.
  method new ($nombre,$edad,$sexo,$nacionalidad) {
    self.bless(:$nombre,:$edad,:$sexo,:$nacionalidad);
  }
}

my $juan = Humano.new('Juan',23,'M','Español');
say $juan;

9.4. Métodos

9.4.1. Introducción

Los métodos son las subrutinas de un objeto.
Al igual que las subrutinas, los métodos pueden empaquetar un conjunto de funcionalidades, aceptar argumentos, tener una signatura y estar sobrecargadas con multi.

Los métodos se definen con la palabra clave method y normalmente se utilizan para realizar alguna acción sobre los atributos de los objetos, reforzando así el concepto de encapsulación donde los atributos del objeto solo pueden manipularse dentro del objeto mediante sus métodos. Desde fuera solo podemos acceder a los métodos de los objetos y no hay acceso directo a sus atributos.

class Humano {
  has $.nombre;
  has $.edad;
  has $.sexo;
  has $.nacionalidad;
  has $.es-adulto;
  method evalúa_es-adulto {
      if self.edad < 18 {
        $!es-adulto = 'No'
      } else {
        $!es-adulto = 'Sí'
      }
  }
}

my $juan = Humano.new(nombre => 'Juan', edad => 23, sexo => 'M', nacionalidad => 'Español');
$juan.evalúa_es-adulto;
say $juan.es-adulto;

Una vez definidos los métodos de una clase, pueden invocarse en un objeto mediante la notación de punto:
objeto . método, como en el ejemplo que hemos visto antes: $juan.evalúa_es-adulto

Si en la definición del método necesitamos hacer referencia al objeto en sí para invocar a otro método utilizaremos la palabra clave self.
Si en la definición del método necesitamos hacer referencia a un atributo utilizaremos ! aunque el atributo esté definido con .
La razón de esto es que el twigil . declara un atributo con ! y crea automáticamente el método de acceso. En el ejemplo anterior, if self.edad < 18 y if $!edad < 18 tendrán el mismo efecto, aunque técnicamente son distintos:

  • self.edad es una llamada al método (de acceso) .edad
    También puede escribirse como $.edad

  • $!edad es una llamada directa a la variable

9.4.2. Métodos privados

Puede llamarse a un método normal de un objeto desde fuera de la clase.

Los métodos privados solo pueden llamarse desde dentro de la clase.
Este es el caso donde un método llama a otro para realizar una acción concreta. El método que interactúa con el mundo exterior es público y a la vez llama al otro método que permanece privado. Al declarar el método como privado conseguimos que el usuario no pueda interactuar con él directamente.

Declarar un método privado requiere utilizar el twigil ! antes de su nombre.
Estos métodos privados se llaman mediante ! en lugar de .

method !soyprivado {
  # código
}

method soypúblico {
  self!soyprivado;
  # más código
}

9.5. Atributos de Clase

Los atributos de Clase son atributos que pertenecen a la clase en sí y no a sus objetos.
Pueden inicializarse durante su definición.
Los atributos de Clase se declaran mediante my en lugar de has.
Se llaman en la clase en sí en lugar de sus objetos.

class Humano {
  has $.nombre;
  my $.contador = 0;
  method new($nombre) {
    Humano.contador++;
    self.bless(:$nombre);
  }
}
my $a = Humano.new('a');
my $b = Humano.new('b');

say Humano.contador;

9.6. Tipo de Acceso

Todos los ejemplos que hemos visto hasta ahora utilizan métodos de acceso para conseguir la información de los atributos de los objetos.

¿Y si necesitamos modificar el valor de un atributo?
Para ello necesitamos etiquetar ese atributo como lectura/escritura utilizando las palabras clave is rw

class Humano {
  has $.nombre;
  has $.edad is rw;
}
my $juan = Humano.new(nombre => 'Juan', edad => 21);
say $juan.edad;

$juan.edad = 23;
say $juan.edad;

Todos los atributos se declaran por defecto como solo lectura y también puedes hacerlo de forma explícita mediante is readonly

9.7. Herencia

9.7.1. Introducción

Herencia es otro concepto de la programación Orientada a Objetos.

Cuando definimos clases nos damos cuenta de que algunas veces utilizan los mismos métodos y atributos.
¿Es necesario duplicar código?
¡NO! Hay que utilizar la herencia

Pensemos en definir dos clases, una clase para seres humanos y otra clase para empleados.
Los seres humanos tienen 2 atributos: nombre y edad.
Los empleados tienen 4 atributos: nombre, edad, compañía y salario.

Con prisas, uno definiría las clases así:

class Humano {
  has $.nombre;
  has $.edad;
}

class Empleado {
  has $.nombre;
  has $.edad;
  has $.compañía;
  has $.salario;
}

El código anterior aunque técnicamente es correcto, conceptualmente es pobre.

Hay una forma mejor de escribirlo:

class Humano {
  has $.nombre;
  has $.edad;
}

class Empleado is Humano {
  has $.compañía;
  has $.salario;
}

La herencia se define mediante la palabra clave is.
En orientación a objetos, decimos que Empleado es hijo de Humano, y que Humano es padre de Empleado.

Todas las clases hijas heredan los atributos y métodos de su clase padre, y así ahorramos duplicar su definición.

9.7.2. Anulación de herencia

Las clases heredan todos los atributos y métodos de sus clases padre correspondientes.
Hay casos donde es necesario que un método heredado actúe de forma distinta.
Para conseguirlo, redefinimos el método en cuestión en la clase hija.
Este concepto se llama anulación de herencia.

En el siguiente ejemplo, el método preséntate se hereda de la clase Empleado.

class Humano {
  has $.nombre;
  has $.edad;
  method preséntate {
    say 'Hola, soy un ser humano y mi nombre es ' ~ self.nombre;
  }
}

class Empleado is Humano {
  has $.compañía;
  has $.salario;
}

my $juan = Humano.new(nombre =>'Juan', edad => 23,);
my $juana = Empleado.new(nombre =>'Juana', edad => 25, compañía => 'Acme', salario => 4000);

$juan.preséntate;
$juana.preséntate;

La anulación de herencia funciona así:

class Humano {
  has $.nombre;
  has $.edad;
  method preséntate {
    say 'Hola, soy un ser humano y mi nombre es ' ~ self.nombre;
  }
}

class Empleado is Humano {
  has $.compañía;
  has $.salario;
  method preséntate {
    say 'Hola, soy un empleado, mi nombre es ' ~ self.nombre ~ ' y trabajo en: ' ~ self.compañía;
  }
}

my $juan = Humano.new(nombre =>'Juan',edad => 23,);
my $juana = Empleado.new(nombre =>'Juana',edad => 25,compañía => 'Acme',salario => 4000);

$juan.preséntate;
$juana.preséntate;

El método correspondiente será aplicado dependiendo de la clase a la que pertenece el objeto.

9.7.3. Submétodos

Los submétodos son métodos que no se heredan en las clases hijas.
Solo son accesibles desde la clase donde son declarados.
Se definen utilizando la palabra clave submethod.

9.8. Herencia Múltiple

Raku permite la herencia múltiple. Una clase puede heredar de varias clases.

class graf-barras {
  has Int @.valores-barras;
  method dibujar {
    say @.valores-barras;
  }
}

class graf-líneas {
  has Int @.valores-líneas;
  method dibujar {
    say @.valores-líneas;
  }
}

class multi-gráfica is graf-barras is graf-líneas {
}

my $ventas-actuales = graf-barras.new(valores-barras => [10,9,11,8,7,10]);
my $previsión-ventas = graf-líneas.new(valores-líneas => [9,8,10,7,6,9]);

my $actual-vs-previsión = multi-gráfica.new(valores-barras => [10,9,11,8,7,10],
                                            valores-líneas => [9,8,10,7,6,9]);
say "Ventas actuales:";
$ventas-actuales.dibujar;
say "Previsión de ventas:";
$previsión-ventas.dibujar;
say "Actual vs Previsión:";
$actual-vs-previsión.dibujar;
Salida
Ventas actuales:
[10 9 11 8 7 10]
Previsión de ventas:
[9 8 10 7 6 9]
Actual vs Previsión:
[10 9 11 8 7 10]
Explicación

La clase multi-gráfica debería ser capaz de tener dos series, una para los valores actuales de las barras y otra para los valores de las previsiones de las líneas.
Por esa razón la hemos definido como hija de graf-líneas y graf-barras.
Te habrás dado cuenta que al llamar al método dibujar en multi-gráfica no tenemos el resultado deseado. Solo se dibuja una serie.
¿Qué ha ocurrido?
multi-gráfica hereda de graf-líneas y de graf-barras y ambas tienen un método llamado dibujar. Cuando llamamos a ese método desde multi-gráfica Raku trata de resolver internamente el conflicto llamando a uno de los métodos heredados.

Correción

Para que funcione correctamente necesitamos anular la herencia del método dibujar en multi-gráfica.

class graf-barras {
  has Int @.valores-barras;
  method dibujar {
    say @.valores-barras;
  }
}

class graf-líneas {
  has Int @.valores-líneas;
  method dibujar {
    say @.valores-líneas;
  }
}

class multi-gráfica is graf-barras is graf-líneas {
  method dibujar {
    say @.valores-barras;
    say @.valores-líneas;
  }
}

my $ventas-actuales = graf-barras.new(valores-barras => [10,9,11,8,7,10]);
my $previsión-ventas = graf-líneas.new(valores-líneas => [9,8,10,7,6,9]);

my $actual-vs-previsión = multi-gráfica.new(valores-barras => [10,9,11,8,7,10],
                                            valores-líneas => [9,8,10,7,6,9]);
say "Ventas actuales:";
$ventas-actuales.dibujar;
say "Previsión de ventas:";
$previsión-ventas.dibujar;
say "Actual vs Previsión:";
$actual-vs-previsión.dibujar;
Salida
Ventas actuales:
[10 9 11 8 7 10]
Previsión de ventas:
[9 8 10 7 6 9]
Actual vs Previsión:
[10 9 11 8 7 10]
[9 8 10 7 6 9]

9.9. Roles

Los Roles son similares a las clases en cuanto a que son una colección de atributos y métodos.

Los roles se declaran con la palabra clave role. Las clases que quieran implementar un rol, pueden hacerlo utilizando la palabra clave does.

Vamos a escribir de nuevo el ejemplo de la herencia múltiple pero mediante roles:
role graf-barras {
  has Int @.valores-barras;
  method dibujar {
    say @.valores-barras;
  }
}

role graf-líneas {
  has Int @.valores-líneas;
  method dibujar {
    say @.valores-líneas;
  }
}

class multi-gráfica does graf-barras does graf-líneas {
  method dibujar {
    say @.valores-barras;
    say @.valores-líneas;
  }
}

my $ventas-actuales = graf-barras.new(valores-barras => [10,9,11,8,7,10]);
my $previsión-ventas = graf-líneas.new(valores-líneas => [9,8,10,7,6,9]);

my $actual-vs-previsión = multi-gráfica.new(valores-barras => [10,9,11,8,7,10],
                                            valores-líneas => [9,8,10,7,6,9]);
say "Ventas actuales:";
$ventas-actuales.dibujar;
say "Previsión de ventas:";
$previsión-ventas.dibujar;
say "Actual vs Previsión:";
$actual-vs-previsión.dibujar;

Verás que el resultado es el mismo que antes sin utilizar roles.

Y ahora te preguntarás: si un rol es como una clase ¿para qué se utilizan?
Para responder la pregunta, modifica el primer script que hemos utilizado para mostrar el caso de la herencia múltiple, en el que olvidamos anular la herencia del método dibujar.

role graf-barras {
  has Int @.valores-barras;
  method dibujar {
    say @.valores-barras;
  }
}

role graf-líneas {
  has Int @.valores-líneas;
  method dibujar {
    say @.valores-líneas;
  }
}

class multi-gráfica does graf-barras does graf-líneas {
}

my $ventas-actuales = graf-barras.new(valores-barras => [10,9,11,8,7,10]);
my $previsión-ventas = graf-líneas.new(valores-líneas => [9,8,10,7,6,9]);

my $actual-vs-previsión = multi-gráfica.new(valores-barras => [10,9,11,8,7,10],
                                            valores-líneas => [9,8,10,7,6,9]);
say "Ventas actuales:";
$ventas-actuales.dibujar;
say "Previsión de ventas:";
$previsión-ventas.dibujar;
say "Actual vs Previsión:";
$actual-vs-previsión.dibujar;
Salida
===SORRY!=== Error while compiling
Method 'dibujar' must be resolved by class multi-gráfica because it exists in multiple roles (graf-líneas, graf-barras)
Explicación

Tendremos un error en tiempo de compilación si aplicamos varios roles a la misma clase si existe un conflicto.
Este enfoque es mucho más seguro que la herencia múltiple, donde los conflictos no se consideran errores y se resuelven simplemente en tiempo de ejecución.

Los roles te avisarán si existe un conflicto.

9.10. Introspección

La Introspección es la forma de conseguir información de un objeto; como su tipo, atributos o métodos.

class Humano {
  has Str $.nombre;
  has Int $.edad;
  method preséntate {
    say 'Hola, soy un ser humano y mi nombre es ' ~ self.nombre;
  }
}

class Empleado is Humano {
  has Str $.compañía;
  has Int $.salario;
  method preséntate {
    say 'Hola, soy un empleado, mi nombre es ' ~ self.nombre ~ ' y trabajo en: ' ~ self.compañía;
  }
}

my $juan = Humano.new(nombre =>'Juan',edad => 23,);
my $juana = Empleado.new(nombre =>'Juana',edad => 25,compañía => 'Acme',salario => 4000);

say $juan.WHAT;
say $juana.WHAT;
say $juan.^attributes;
say $juana.^attributes;
say $juan.^methods;
say $juana.^methods;
say $juana.^parents;
if $juana ~~ Humano {say 'Juana es Humana'};

La introspeción proporciona la siguiente información:

  • .WHAT devuelve la clase a la que pertenece el objeto.

  • .^attributes devuelve todos los atributos del objeto.

  • .^methods devuelve todos los métodos accesibles del objeto.

  • .^parents devuelve todas las clases padre a las que pertenece la clase del objeto.

  • ~~ es el operador de coincidencia inteligente. Devuelve True si el objeto pertenece a la clase con la que se compara o con cualquier clase heredada.

Consulta:

para obtener más información sobre Programación Orientada a Objetos en Raku.

10. Control de Excepciones

10.1. Captura de Excepciones

Las excepciones son situaciones especiales que ocurren en tiempo de ejecución cuando algo va mal.
Decimos que las excepciones son lanzadas.

Veamos una ejecución correcta como en el siguiente script:

my Str $nombre;
$nombre = "Juana";
say "Hola " ~ $nombre;
say "¿Qué haces hoy?"
Salida
Hola Juana
¿Qué haces hoy?

Ahora veamos un script que lanza una excepción:

my Str $nombre;
$nombre = 123;
say "Hola " ~ $nombre;
say "¿Qué haces hoy?"
Salida
Type check failed in assignment to $nombre; expected Str but got Int
  in block <unit> at exceptions.raku line 2

Debes tener en cuenta que cuando se produce un error (en este caso, debido a la asignación de un número a una variable de texto) el programa se interrumpirá y no evaluará cualquier otra línea de código.

El Control de excepciones se produce cuando se lanza una excepción y es capturada de forma que el script continúa su ejecución.

my Str $nombre;
try {
  $nombre = 123;
  say "Hola " ~ $nombre;
  CATCH {
    default {
      say "¿Puedes decirme tu nombre de nuevo? No podemos encontrarlo en el registro.";
    }
  }
}
say "¿Qué haces hoy?";
Salida
¿Puedes decirme tu nombre de nuevo? No podemos encontrarlo en el registro.
¿Qué haces hoy?

El Control de excepciones se realiza utilizando un bloque try-catch.

try {
  # código
  # si algo va mal, el script saltará al bloque CATCH
  # si todo es correcto, el script ignorará el bloque CATCH
  CATCH {
    default {
      # aquí se ejecuta código si se lanza una excepción
    }
  }
}

El bloque CATCH puede definirse igual que el bloque given. Esto significa que podemos capturar y controlar distintos tipos de excepciones.

try {
  # código
  # si algo va mal, el script saltará al bloque CATCH
  # si todo es correcto, el script ignorará el bloque CATCH
  CATCH {
    when X::AdHoc { # hace algo si se lanza una excepción de tipo X::AdHoc }
    when X::IO    { # hace algo si se lanza una excepción de tipo X::IO }
    when X::OS    { # hace algo si se lanza una excepción de tipo X::OS }
    default       { # hace algo si se lanza una excepción y no está contemplada en los tipos anteriores }
  }
}

10.2. Lanzando Excepciones

Raku también te permite lanzar excepciones de forma explícita.
Se pueden lanzar dos tipos de excepciones:

  • Excepciones ad-hoc

  • Excepciones por tipo

ad-hoc
my Int $edad = 21;
die "¡Error!";
por tipo
my Int $edad = 21;
X::AdHoc.new(payload => '¡Error!').throw;

Las excepciones ad-hoc se lanzan utilizando la subrutina die, seguida del mensaje describiendo la excepción.

Las excepciones por tipo son objetos, y como vemos en el ejemplo anterior utilizan el constructor .new().
Todas las excepciones por tipo pertenecen a la clase X. Estos son algunos ejemplos:
X::AdHoc es el tipo de excepción más simple
X::IO errores relacionados con operaciones de E/S
X::OS errores relacionados con el Sistema Operativo
X::Str::Numeric errores relacionados con la conversión de una cadena de texto a un valor numérico

Tienes una lista completa de tipos de excepciones y sus métodos asociados en https://docs.raku.org/type-exceptions.html

11. Expresiones Regulares

Una expresión regular, o regex es una secuencia de caracteres que se utiliza para encontrar un patrón.
Piensa en ello como un patrón.

if 'iluminación' ~~ m/ ilumina / {
    say "iluminación contiene la palabra ilumina";
}

En este ejemplo, el operador inteligente de coincidencia ~~ sirve para comprobar si el texto (iluminación) contiene la palabra (ilumina).
"Iluminación" se compara con la regex m/ ilumina /

11.1. Definición de Regex

Una expresión regular puede definirse así:

  • /ilumina/

  • m/ilumina/

  • rx/ilumina/

A menos que se indique de forma explícita, el espacio en blanco es ignorado, da igual m/ilumina/ que m/ ilumina /.

11.2. Búsqueda de caracteres

Los caracteres alfanuméricos y el guión bajo _ se escriben tal cual.
El resto de caracteres deben ser escapados utilizando la barra invertida o backslash o ir entre comillas.

Barra invertida o Backslash
if 'Temperatura: 13' ~~ m/ \: / {
    say "El texto contiene el caracter dos puntos :";
}
Comillas simples
if 'Edad = 13' ~~ m/ '=' / {
    say "El texto contiene el caracter igual = ";
}
Comillas dobles
if 'nombre@empresa.com' ~~ m/ "@" / {
    say "Dirección de mail válida porque contiene el caracter @";
}

11.3. Categorías de caracteres

Los caracteres se pueden clasificar en categorías y podemos realizar comparaciones con ellas.
También podemos comparar la inversa de la categoría (todo menos ella):

Categoría

Regex

Inversa

Regex

Caracter de palabra (letra, dígito o guión bajo)

\w

Cualquier caracter menos un caracter de palabra

\W

Dígito

\d

Cualquier caracter menos un dígito

\D

Espacio en blanco

\s

Cualquier caracter menos un espacio en blanco

\S

Espacio en blanco horizontal

\h

Cualquier caracter menos un caracter en blanco horizontal

\H

Espacio en blanco vertical

\v

Cualquier caracter menos un caracter en blanco horizontal

\V

Tabulador

\t

Cualquier caracter menos el tabulador

\T

Línea nueva

\n

Cualquier caracter menos una línea nueva

\N

if "Juan123" ~~ / \d / {
  say "Nombre no válido, no se permiten números";
} else {
  say "Nombre válido"
}
if "Juan-Dios" ~~ / \s / {
  say "El texto contiene un espacio en blanco";
} else {
  say "El texto no contiene un espacio en blanco"
}

11.4. Propiedades Unicode

Lo normal es comparar categorías de caracteres como hemos visto.
Dicho esto, podemos tener un enfoque más sistemático utilizando propiedades Unicode, de forma que puedas realizar coincidencias con categorías de caracteres dentro y fuera del estandar ASCII.
Las propiedades Unicode se indican entre <: >

if "Números Devanagari १२३" ~~ / <:N> / {
  say "Contiene un número";
} else {
  say "No contiene un número"
}
if "Привет, Иван." ~~ / <:Lu> / {
  say "Contiene una letra en mayúsculas";
} else {
  say "No contiene una letra en mayúsculas"
}
if "Juan-Dios" ~~ / <:Pd> / {
  say "Contiene un guión";
} else {
  say "No contiene un guión"
}

11.5. Comodines

En una regex también se pueden utilizar comodines.

El punto . significa cualquier caracter.

if 'abc' ~~ m/ a.c / {
    say "Coincide";
}
if 'a2c' ~~ m/ a.c / {
    say "Coincide";
}
if 'ac' ~~ m/ a.c / {
    say "Coincide";
} else {
    say "No coincide";
}

11.6. Cuantificadores

Los cuantificadores van después de un caracter y especifican cuantas veces se repite éste.

El interrogante ? significa que se repite una vez o ninguna.

if 'ac' ~~ m/ a?c / {
    say "Coincide";
} else {
    say "No coincide";
}
if 'c' ~~ m/ a?c / {
    say "Coincide";
} else {
    say "No coincide";
}

El asterisco * significa que se repite una vez o más de una vez o ninguna.

if 'az' ~~ m/ a*z / {
    say "Coincide";
} else {
    say "No coincide";
if 'aaz' ~~ m/ a*z / {
    say "Coincide";
} else {
    say "No coincide";
}
if 'aaaaaaaaaaz' ~~ m/ a*z / {
    say "Coincide";
} else {
    say "No coincide";
}
if 'z' ~~ m/ a*z / {
    say "Coincide";
} else {
    say "No coincide";
}

El símbolo + significa que se repite al menos una vez.

if 'az' ~~ m/ a+z / {
    say "Coincide";
} else {
    say "No coincide";
}
if 'aaz' ~~ m/ a+z / {
    say "Coincide";
} else {
    say "No coincide";
}
if 'aaaaaaaaaaz' ~~ m/ a+z / {
    say "Coincide";
} else {
    say "No coincide";
}
if 'z' ~~ m/ a+z / {
    say "Coincide";
} else {
    say "No coincide";
}

11.7. Extracción de resultados

Cuando se encuentra el patrón buscado, el resultado se guarda en la variable especial $/

Script
if 'Rakudo es el compilador de Raku' ~~ m/:s Raku/ {
    say "El resultado es: " ~ $/;
    say "El texto antes del resultado es: " ~ $/.prematch;
    say "El texto después del resultado es: " ~ $/.postmatch;
    say "La posición de comienzo del resultado es: " ~ $/.from;
    say "La posición final del resultado es: " ~ $/.to;
}
Salida
El resultado es: Raku
El texto antes del resultado es: Rakudo es el compilador de
El texto después del resultado es:
La posición inicial del resultado es: 27
La posición final del resultado es: 33
Explicación

$/ devuelve un Objeto de Coincidencia (el texto encontrado o resultado de la regex)
El Objeto de Coincidencia tiene los siguientes métodos:
.prematch devuelve el texto que hay antes del resultado.
.postmatch devuelve el texto que hay después del resultado.
.from devuelve la posición inicial del resultado.
.to devuelve la posición final del resultado.

Por defecto, el espacio en blanco en una regex se ignora.
Si queremos tener en cuenta los espacios en blanco en una regex, tenemos que hacerlo de forma explícita.
El parámetro :s en la regex m/:s Raku/ hace que la regex tenga en cuenta los espacios en blanco.
Otra forma de hacerlo sería así: m/ Perl\s6 / donde \s representa el espacio en blanco.
Si la regex contiene más de un espacio en blanco, es mejor utilizar :s que utilizar \s para cada espacio en blanco.

11.8. Ejemplo

Vamos a comprobar si una dirección de email es correcta o no.
Para este ejemplo asumiremos que una dirección de email correcta tiene este formato:
nombre [punto] apellido [arroba] compañía [punto] (com/org/net)

La regex que utilizaremos en este ejemplo para validar una dirección de email no es muy precisa, y como su propósito es demostrar el funcionamiento de las regex en Raku, conviene no utilizarla en producción.
Script
my $email = 'juan.dios@perl6.org';
my $regex = / <:L>+\.<:L>+\@<:L+:N>+\.<:L>+ /;

if $email ~~ $regex {
  say $/ ~ " es un email válido";
} else {
  say "No es una email válido";
}
Salida

juan.dios@perl6.org es un email válido

Explicación

<:L> coincide con una letra
<:L>` coincide con una letra o más + `\.` coincide con un caracter de [punto] + `\@` coincide con un caracter de [arroba] + `<:L:N> coincide con una letra o más de una y un número
<:L+:N>+ coincide con una o más (una o más letras y un número)

La regex se puede descomponer así:

  • nombre <:L>+

  • [punto] \.

  • apellido <:L>+

  • [arroba] \@

  • nombre de la compañía <:L+:N>+

  • [punto] \.

  • com/org/net <:L>+

Otra forma de hacerlo es descomponer la regex en varias regex con nombre
my $email = 'juan.dios@perl6.org';
my regex varias-letras { <:L>+ };
my regex punto { \. };
my regex arroba { \@ };
my regex varias-letras-numeros { <:L+:N>+ };

if $email ~~ / <varias-letras> <punto> <varias-letras> <arroba> <varias-letras-numeros> <punto> <varias-letras> / {
  say $/ ~ " es un email válido";
} else {
  say "No es una email válido";
}

Una regex con nombre se define de la siguiente forma: my regex nombre-regex { definición de la regex }
Para utilizar una regex, la invocamos con su nombre de esta forma: <nombre-regex>

En https://docs.raku.org/language/regexes tienes más información sobre regex.

12. Módulos en Raku

Raku es un lenguaje de programación de propósito general. Puede utilizarse para llevar a cabo multitud de tareas, incluyendo: manipulación de texto, gráficos, web, bases de datos, protocolos de red, etc.

La reutilización es un concepto muy importante para que los programadores no tengan que reinventar la rueda cada vez que quieran llevar a cabo una nueva tarea.

Raku permite la creación y redistribución de módulos. Cada módulo es un paquete de funcionalidades que, una vez instalado, se puede reutilizar.

Zef es el gestor de módulos que incorpora Rakudo.

Para instalar un módulo concreto, utiliza el siguiente comando en el terminal:

zef install "nombre del módulo"

Puedes encontrar el directorio de módulos de Raku en: https://modules.raku.org/

12.1. Utilizando Módulos

MD5 es una función de cifrado de tipo hash que produce un valor hash de 128-bit.
MD5 tiene muchas aplicaciones, incluyendo el cifrado de contraseñas guardadas en una base de datos. Cuando se registra un nuevo usuario sus credenciales no se guardan en texto plano, se guardan cifradas, de forma que si un atacante compromete la base de datos, este atacante no podría conocer las contraseñas. Por suerte, no necesitas implementar el algoritmo MD5, pues ya lo implementa un módulo de Raku.

Vamos a instalarlo:
zef install Digest::MD5

Ahora, ejecutemos el siguiente script:

use Digest::MD5;
my $contraseña = "contraseña123";
my $contraseña-cifrada = Digest::MD5.new.md5_hex($contraseña);

say $contraseña-cifrada;

Para utilizar la función md5_hex() que produce el cifrado necesitaremos antes cargar el módulo requerido.
La palabra clave use carga el módulo para después utilizarlo en el script.

En la práctica el cifrado MD5 no es suficientemente seguro pues es vulnerable a ataques de diccionario.
Debería combinarse con sal https://es.wikipedia.org/wiki/Sal_(criptografía).

13. Unicode

Unicode es un estándar para codificar y representar texto en la mayoría de los sistemas de escritura del mundo.
UTF-8 es una codificación de caracteres que puede codificar todos los caracteres o números de código en Unicode.

Los caracteres se definen con un:
Grafema: Representación visual.
Número de código: Un número asignado a un caracter.

13.1. Utilizando Unicode

Veamos cómo podemos visualizar caracteres mediante Unicode
say "a";
say "\x0061";
say "\c[LATIN SMALL LETTER A]";

Las 3 líneas anteriores muestran formas distintas de construir un caracter:

  1. Escribir el caracter directamente (grafema)

  2. Utilizar \x y el número de código

  3. Utilizar \c y el nombre del número de código

Visualicemos ahora una sonrisa
say "☺";
say "\x263a";
say "\c[WHITE SMILING FACE]";
Otro ejemplo combinando dos números de código
say "á";
say "\x00e1";
say "\x0061\x0301";
say "\c[LATIN SMALL LETTER A WITH ACUTE]";

La letra á puede escribirse:

  • utilizando su número de código único \x00e1

  • o combinando sus números de código de a y la tilde \x0061\x0301

Algunos de los métodos que pueden utilizarse:
say "á".NFC;
say "á".NFD;
say "á".uniname;
Salida
NFC:0x<00e1>
NFD:0x<0061 0301>
LATIN SMALL LETTER A WITH ACUTE

NFC devuelve el número de código único.
NFD descompone el caracter y devuelve el número de código de cada parte.
uniname devuelve el nombre del número de código.

Las letras Unicode pueden utilizarse como identificadores:
my  = 1;
++;
say ;
Se pueden utilizar símbolos matemáticos Unicode:
my $var = 2 + ;
say $var;

13.2. Operaciones contempladas con Unicode

13.2.1. Números

Los números arábigos son los diez dígitos: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9. Este conjunto de números es el más utilizado en el mundo.

No obstante y en menor medida en diferentes lugares del mundo se utilizan otros conjuntos de números.

Al utilizar otros conjuntos de números distintos de los números arábigos no hay que hacer nada especial; todos los métodos y operadores funcionarán como de costumbre.

say (٤,٥,٦,1,2,3).sort; # (1 2 3 4 5 6)
say 1 + ٩;              # 10

13.2.2. Texto

Cuidado al utilizar operaciones génericas con texto pues no siempre obtendríamos lo resultados esperados, sobre todo al comparar u ordenar.

Comparación
say 'a' cmp 'B'; # Más

Este ejemplo dice que a es mayor que B porque el número de código de a es mayor que el número de código de B.

Si bien la lógica anterior es técnicamente correcta en la práctica no es muy útil.

Por suerte Raku tiene métodos y operadores que implementan el Algoritmo del conjunto ordenado de caracteres de Unicode.
Uno de estos operadores es unicmp, funciona como cmp pero contemplado por Unicode.

say 'a' unicmp 'B'; # Menos

Como puedes ver el operador unicmp da el resultado esperado donde a es menor que B.

Ordenación

Como alternativa a sort para ordenar por números de código Raku incluye el método collate que implementa el Algoritmo del conjunto ordenado de caracteres de Unicode.

say ('a','b','c','D','E','F').sort;    # (D E F a b c)
say ('a','b','c','D','E','F').collate; # (a b c D E F)

14. Paralelismo, Concurrencia y Asincronía

14.1. Paralelismo

En condiciones normales todas las tareas de un programa se ejecutan de forma secuencial.
Esto no suele ser un problema, a menos que tarde demasiado tiempo.

Raku te permite hacer cosas en paralelo.
Llegados aquí, es importante saber que el paralelismo puede tener dos significados:

  • Paralelismo de Tareas: Dos (o más) expresiones independientes ejecutándose en paralelo.

  • Paralelismo de Datos: Una única expresión iterando en una lista de elementos en paralelo.

Comencemos con la última.

14.1.1. Paralelismo de Datos

my @array = 0..50000;                        # Creación del array
my @resultado = @array.map({ is-prime $_ }); # Llama a is-prime por cada elemento del array
say now - INIT now;                          # Visualiza el tiempo que toma el script hasta finalizar
Consideremos el ejemplo anterior:

Hacemos solo una operación @array.map({ is-prime $_ })
La subrutina is-prime es llamada por cada elemento del array de forma secuencial:
is-prime @array[0] después is-prime @array[1] después is-prime @array[2] etc.

Afortunadamente podemos llamar a is-prime en múltiples elementos del array al mismo tiempo:
my @array = 0..50000;                             # Creación del array
my @resultado = @array.race.map({ is-prime $_ }); # Llama a is-prime por cada elemento del array
say now - INIT now;                               # Visualiza el tiempo que toma el script hasta finalizar

Fíjate que la expresión utiliza race. Este método permite la iteración en paralelo de los elementos del array.

Después de ejecutar ambos ejemplos (con y sin race), compara los tiempos consumidos de ambos scripts.

race no guarda el orden de los elementos. Si quieres respetar este orden, utiliza hyper.

race
my @array = 1..1000;
my @resultado = @array.race.map( {$_ + 1} );
.say for @resultado;
hyper
my @array = 1..1000;
my @resultado = @array.hyper.map( {$_ + 1} );
.say for @resultado;

Si ejecutas ambos ejemplos verás que uno muestra los resultados ordenados y el otro no.

14.1.2. Paralelismo de Tareas

my @array1 = 0..49999;
my @array2 = 2..50001;

my @resultado1 = @array1.map( {is-prime($_ + 1)} );
my @resultado2 = @array2.map( {is-prime($_ - 1)} );

say @resultado1 == @resultado2;

say now - INIT now;
Veamos el ejemplo anterior:
  1. Definimos 2 arrays

  2. Realizamos una operación con cada array y guardamos los resultados

  3. Y comprobamos si ambos resultados son iguales

El script espera mientras realiza @array1.map( {is-prime($_ + 1)} ) hasta finalizar
y después realiza @array2.map( {is-prime($_ - 1)} )

Ambas operaciones realizadas en cada array son independientes.

¿Por qué no hacer las dos en paralelo?
my @array1 = 0..49999;
my @array2 = 2..50001;

my $promesa1 = start @array1.map( {is-prime($_ + 1)} ).eager;
my $promesa2 = start @array2.map( {is-prime($_ - 1)} ).eager;

my @resultado1 = await $promesa1;
my @resultado2 = await $promesa2;

say @resultado1 eqv @resultado2;

say now - INIT now;
Explicación

La subrutina start evalúa el código y devuelve un objeto de tipo promesa o una promesa (promise).
Si el código se evalúa correctamente, la promesa se cumple (kept).
Si el código lanza una excepción, la promesa se rompe (broken).

La subrutina await espera a una promesa.
Si esta se cumple devuelve los valores.
Si esta se rompe devuelve la excepción.

Comprueba lo que tarda cada script en completarse.

El paralelismo siempre añade una sobrecarga multihilo. Si esta sobrecarga no se compensa aumentando la velocidad de cómputo, el script tardará más en completarse.
Debido a esto, el uso de race, hyper, start y await en scripts muy simples podría ralentizarlos.

14.2. Concurrencia y Asincronía

En https://docs.raku.org/language/concurrency tienes más información sobre Concurrencia y Programación Asíncrona.

15. Interfaz de Llamadas Nativas

Raku nos permite utilizar librerías de C mediante la Interfaz de Llamadas Nativas (NativeCall).

NativeCall es un módulo estandar que viene incluido con Raku y ofrece un conjunto de funcionalidades para facilitar el trabajo con la interfaz entre Raku y C.

15.1. Llamada a una función

Consideremos un programa en C con una función llamada holadesdec. Esta función visualiza Hola desde C en el terminal. No acepta argumentos ni devuelve ningún valor.

ncitest.c
#include <stdio.h>

void holadesdec () {
  printf("Hola desde C\n");
}

Dependiendo de tu sistema operativo, compila el código de C anterior en una librería de C.

En Linux:
gcc -c -fpic ncitest.c
gcc -shared -o libncitest.so ncitest.o
En Windows:
gcc -c ncitest.c
gcc -shared -o ncitest.dll ncitest.o
En MacOS:
gcc -dynamiclib -o libncitest.dylib ncitest.c

En la misma carpeta donde has compilado la librería en C, crea un nuevo archivo de Raku que contenga el siguiente código y ejecútalo.

ncitest.raku
use NativeCall;

constant LIBPATH = "$*CWD/ncitest";
sub holadesdec() is native(LIBPATH) { * }

holadesdec();
Explicación:

Lo primero es declarar que vamos a utilizar el módulo NativeCall.
Después creamos la constante LIBPATH que contiene la ruta de la librería de C.
Como puedes ver, $*CWD contiene la ruta de la carpeta actual.
Después creamos una subrutina de Raku denominada holadesdec() que se corresponde y tiene el mismo nombre que la función que reside en la librería de C, la cual se encuentra en LIBPATH.
Todo esto se hace utilizando is native.
Por último realizamos la llamada a la subrutina de Raku.

En esencia, todo se reduce a declarar una subrutina con is native y el nombre de la biblioteca de C.

15.2. Cambiando el nombre a una función

Hemos visto cómo realizar una llamada a una función simple de C mediante una subrutina de Raku con el mismo nombre y utilizando is native.

Es posible que en algunos casos necesitemos que el nombre de la función de Raku sea distinto al de la función en C.
Para hacerlo utilizamos is symbol.

Vamos a modificar el script de Raku que hemos visto de forma que la función de Raku se llame hola en lugar de holadesdec.

ncitest.raku
use NativeCall;

constant LIBPATH = "$*CWD/ncitest";
sub hola() is native(LIBPATH) is symbol('holadesdec') { * }

hola();
Explicación:

En caso de que la subrutina de Raku tenga un nombre distinto al de la función de C correspondiente, utilizamos is symbol con el nombre de la función de C original.

15.3. Pasando Argumentos

Vamos a modificar y compilar la librería de C y el script de Raku de forma que acepten, en C un argumento de tipo char* y el mismo argumento en Raku pero de tipo Str.

ncitest.c
#include <stdio.h>

void holadesdec (char* nombre) {
  printf("¡Hola, %s! ¡Esto es C!\n", nombre);
}
ncitest.raku
use NativeCall;

constant LIBPATH = "$*CWD/ncitest";
sub hola(Str) is native(LIBPATH) is symbol('holadesdec') { * }

hola('Juana');

15.4. Valores de retorno

Vamos a repetir el proceso una vez más para crear una calculadora simple que toma dos enteros, los suma, devuelve el resultado y lo visualiza.
Compilamos la librería de C y ejecutamos el script de Raku.

ncitest.c
int suma (int a, int b) {
  return (a + b);
}
ncitest.raku
use NativeCall;

constant LIBPATH = "$*CWD/ncitest";
sub suma(int32,int32 --> int32) is native(LIBPATH) { * }

say suma(2,3);

Como puedes ver, ambas funciones aceptan dos enteros y devuelven uno (int en C y int32 en Raku).

15.5. Tipos

Te preguntarás por qué en el script de Raku utilizamos int32 en lugar de int.
En Raku no pueden utilizarse algunos tipos como Int, Rat, etc. para pasarlos y recibirlos como valores de una función de C.
En Raku hay que utilizar los mismos tipos que en C.

Por fortuna, Raku proporciona muchos tipos que se corresponden con los de C.

Tipo de C Tipo de Raku

char

int8

int8_t

short

int16

int16_t

int

int32

int32_t

int64_t

int64

unsigned char

uint8

uint8_t

unsigned short

uint16

uint16_t

unsigned int

uint32

uint32_t

uint64_t

uint64

long

long

long long

longlong

float

num32

double

num64

size_t

size_t

bool

bool

char* (String)

Str

Arrays: Por ejemplo int* (Array de int) y double* (Array de double)

CArray: Por ejemplo CArray[int32] y CArray[num64]

En https://docs.raku.org/language/nativecall tienes más información sobre la Interfaz de Llamadas Nativas.

16. La Comunidad


1. Notación de intervalos: https://es.wikipedia.org/wiki/Intervalo_(matemática)#Notación