- 1. Introducción
- 2. Operadores
- 3. Variables
- 4. Funciones y mutadores
- 5. Bucles y condiciones
- 6. I/O
- 7. Subrutinas
- 8. Programación Funcional
- 9. Clases y Objetos
- 10. Control de Excepciones
- 11. Expresiones Regulares
- 12. Módulos en Raku
- 13. Unicode
- 14. Paralelismo, Concurrencia y Asincronía
- 15. Interfaz de Llamadas Nativas
- 16. La Comunidad
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.
Este trabajo está bajo la licencia Creative Commons Attribution-ShareAlike 4.0 International License. Puedes encontrar una copia de esta licencia en
Puedes colaborar en este documento en:
Si te gusta este trabajo clica en Star del repositorio de Github.
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.
-
TMTOWTDI (Pronunciado como Tim Toady): There is more than one way to do it (Hay más de una forma para hacer algo).
-
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.
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
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
-
Para arquitectura 64-bit: descarga el instalador más reciente (.msi) desde https://rakudo.org/latest/star/win
-
Finalizada la instalación, comprueba que
C:\rakudo\bin
figura en el PATH del sistema.
-
Consigue la imagen oficial de Docker
docker pull rakudo-star
-
Y ejecuta un contenedor con la imagen
docker run -it rakudo-star
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].
Tip
|
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:
|
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.
Las últimas versiones de Vim permiten la visualización de la sintaxis mediante plug-ins; Emacs y Padre necesitan instalar paquetes adicionales.
Comenzamos con El ritual hola mundo
.
say 'hola mundo';
que también puede escribirse como:
'hola mundo'.say;
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 |
|
Infijo |
Entre términos |
|
Sufijo |
Después del término |
|
Circumfijo |
Al principio y al final del término |
|
Precircumfijo |
Después del término, al principio y al final de otro |
|
Los identificadores son los nombres que se le dan a los términos cuando los defines.
-
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 |
|
|
|
|
|
|
|
|
|
|
-
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.
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
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
La siguiente tabla muestra los operadores más utilizados.
Operador | Tipo | Descripción | Ejemplo | Resultado |
---|---|---|---|---|
|
|
Suma |
|
|
|
|
Resta |
|
|
|
|
Multiplicación |
|
|
|
|
Potencia |
|
|
|
|
División |
|
|
|
|
División Entera (redondeo inferior) |
|
|
|
|
Resto |
|
|
|
|
Divisible |
|
|
|
|
|||
|
|
Máximo común divisor |
|
|
|
|
Mínimo común múltiplo |
|
|
|
|
Igual numérico |
|
|
|
|
No igual numérico |
|
|
|
|
Menor que |
|
|
|
|
Mayor que |
|
|
|
|
Menor o igual |
|
|
|
|
Mayor o igual |
|
|
|
|
Texto igual |
|
|
|
|
Texto no igual |
|
|
|
|
Asignación |
|
|
|
|
Texto concatenado |
|
|
|
|
|||
|
|
Texto replicado |
|
|
|
|
|||
|
|
Expresión regular |
|
|
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
Incremento |
|
|
|
Incremento |
|
|
|
|
|
Decremento |
|
|
|
Decremento |
|
|
|
|
|
Fuerza el operando a un valor numérico |
|
|
|
|
|||
|
|
|||
|
|
Fuerza el operando a un valor numérico y devuelve la negación |
|
|
|
|
|||
|
|
|||
|
|
Fuerza el operando a un valor booleano |
|
|
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
Fuerza el operador a un valor booleano y devuelve la negación |
|
|
|
|
Constructor de Rangos |
|
|
|
|
Constructor de Rangos |
|
|
|
|
Constructor de Rangos |
|
|
|
|
Constructor de Rangos |
|
|
|
|
Constructor de Rangos |
|
|
|
|
Constructor de listas perezosas |
|
|
|
|
Aplanamiento |
|
|
|
|
Al agregar R
delante de cualquier operador hace que se intercambien sus operandos.
Operación original | Resultado | Intercambio de operandos | Resultado |
---|---|---|---|
|
|
|
|
|
|
|
|
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 |
---|---|---|---|
|
|
|
|
|
|
|
|
Note
|
En https://docs.raku.org/language/operators tienes una lista completa de los operadores, incluyendo su precedencia. |
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
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.
my $nombre = 'Juan De Dios';
say $nombre.uc;
say $nombre.chars;
say $nombre.flip;
JUAN DE DIOS
12
soiD eD nauJ
Note
|
Consulta https://docs.raku.org/type/Str para ver la lista completa de métodos de texto. |
my $edad = 17;
say $edad.is-prime;
True
Note
|
Consulta https://docs.raku.org/type/Int para ver la lista completa de métodos de enteros. |
my $edad = 2.3;
say $edad.numerator;
say $edad.denominator;
say $edad.nude;
23
10
(23 10)
Note
|
Consulta https://docs.raku.org/type/Rat para ver la lista completa de métodos de números racionales. |
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:
Tip
|
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
.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
.
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)
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]]
[1 x]
[2 y]
[3 z]
Note
|
Consulta https://docs.raku.org/type/Array para tener la referencia completa sobre Arrays. |
my %capitales = 'UK','Londres','Alemania','Berlín';
say %capitales;
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
.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>
Note
|
Consulta https://docs.raku.org/type/Hash para una referencia completa sobre hashes. |
En los ejemplos anteriores no hemos especificado el tipo de valor que debería contener cada variable.
Tip
|
.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.
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;
Es posible que nunca utilices los dos primeros, pero aparecen en la siguiente lista para que sepas que existen.
|
|
|
|
|
|
||
|
|
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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.
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;
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:
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 :=
.
my Int $var := 123;
say $var;
$var = 999;
say $var;
Salida
123
Cannot assign to an immutable value
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.
Note
|
En https://docs.raku.org/language/variables tienes más información sobre variables. |
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
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
.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)
Raku tiene muchos constructores de bucles y condiciones.
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'
}
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
.
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
.
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.
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?" }
}
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"
}
Note
|
En https://docs.raku.org/language/control tienes más información sobre bucles y condiciones |
En Raku, las dos interfaces más utilizadas de Entrada/Salida son el Terminal y los Ficheros.
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.
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.
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.
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.
my $nombre = 'Neo';
run 'echo', "Hola $nombre";
shell "ls";
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.
slurp
lee datos de un archivo.
Crea un archivo de texto con el siguiente contenido:
Juan 9
Juanito 7
Juana 8
Juanita 7
my $datos = slurp "datos.txt";
say $datos;
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.
Warning
|
en Windows puedes utilizar / o \\ para separar carpetas anidadasC:\\rakudo\\bin C:/rakudo/bin |
Note
|
En https://docs.raku.org/type/IO tienes más información sobre E/S. |
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.
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";
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.";
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");
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.
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);
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.
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)
Tip
|
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 Además, podemos indicar que el valor de retorno esté obligatoriamente definido o no utilizando las siguientes signaturas: Como hemos visto es una buena costumbre utilizar estas restricciones. sub cuadrado ($x --> Int:D) {
return $x ** 2;
}
say "1.2 al cuadrado es igual a " ~ cuadrado(1.2); |
Note
|
En https://docs.raku.org/language/functions encontrarás más información sobre subrutinas y funciones. |
En este apartado veremos algunas características que facilitan la Programación Funcional.
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.
my @array = <1 2 3 4 5>;
sub cuadrado($x) {
$x ** 2
}
say map(&cuadrado,@array);
(1 4 9 16 25)
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.
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
my $cuadrado = -> $x {
$x ** 2
}
say $cuadrado(9);
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.
El operador de alimentación, llamado tubería en algunos lenguajes de programación funcional, ejemplifica el encadenamiento de métodos.
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;
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.
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;
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.
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.
Warning
|
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 .
|
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.
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.
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)
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.
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)
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)
Warning
|
Al usar un generador de forma explícita el punto final debe ser uno de los valores que el generador pueda devolver. De forma alternativa puedes sustituir 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; |
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
.
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.
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 {
}
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.
my $var = 7;
say $var;
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;
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;
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
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
}
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;
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
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.
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.
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]
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.
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]
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
.
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)
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.
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.
Note
|
Consulta: para obtener más información sobre Programación Orientada a Objetos en Raku. |
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 }
}
}
Raku también te permite lanzar excepciones de forma explícita.
Se pueden lanzar dos tipos de excepciones:
-
Excepciones ad-hoc
-
Excepciones por tipo
my Int $edad = 21;
die "¡Error!";
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
Note
|
Tienes una lista completa de tipos de excepciones y sus métodos asociados en https://docs.raku.org/type-exceptions.html |
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 /
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 /
.
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.
if 'Temperatura: 13' ~~ m/ \: / {
say "El texto contiene el caracter dos puntos :";
}
if 'Edad = 13' ~~ m/ '=' / {
say "El texto contiene el caracter igual = ";
}
if '[email protected]' ~~ m/ "@" / {
say "Dirección de mail válida porque contiene el caracter @";
}
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"
}
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"
}
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";
}
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";
}
Cuando se encuentra el patrón buscado, el resultado se guarda en la variable especial $/
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;
}
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
$/
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.
Tip
|
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.
|
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)
Warning
|
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. |
my $email = '[email protected]';
my $regex = / <:L>+\.<:L>+\@<:L+:N>+\.<:L>+ /;
if $email ~~ $regex {
say $/ ~ " es un email válido";
} else {
say "No es una email válido";
}
[email protected] es un email válido
<: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>+
my $email = '[email protected]';
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>
Note
|
En https://docs.raku.org/language/regexes tienes más información sobre regex. |
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"
Note
|
Puedes encontrar el directorio de módulos de Raku en: https://modules.raku.org/ |
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.
Warning
|
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). |
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.
say "a";
say "\x0061";
say "\c[LATIN SMALL LETTER A]";
Las 3 líneas anteriores muestran formas distintas de construir un caracter:
-
Escribir el caracter directamente (grafema)
-
Utilizar
\x
y el número de código -
Utilizar
\c
y el nombre del número de código
say "☺";
say "\x263a";
say "\c[WHITE SMILING FACE]";
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
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.
my $Δ = 1;
$Δ++;
say $Δ;
my $var = 2 + ⅒;
say $var;
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
Cuidado al utilizar operaciones génericas con texto pues no siempre obtendríamos lo resultados esperados, sobre todo al comparar u ordenar.
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
.
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)
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.
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
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.
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.
Tip
|
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. |
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;
-
Definimos 2 arrays
-
Realizamos una operación con cada array y guardamos los resultados
-
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.
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;
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.
Warning
|
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.
|
Note
|
En https://docs.raku.org/language/concurrency tienes más información sobre Concurrencia y Programación Asíncrona. |
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.
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.
#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.
gcc -c -fpic ncitest.c
gcc -shared -o libncitest.so ncitest.o
gcc -c ncitest.c
gcc -shared -o ncitest.dll ncitest.o
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.
use NativeCall;
constant LIBPATH = "$*CWD/ncitest";
sub holadesdec() is native(LIBPATH) { * }
holadesdec();
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.
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
.
use NativeCall;
constant LIBPATH = "$*CWD/ncitest";
sub hola() is native(LIBPATH) is symbol('holadesdec') { * }
hola();
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.
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
.
#include <stdio.h>
void holadesdec (char* nombre) {
printf("¡Hola, %s! ¡Esto es C!\n", nombre);
}
use NativeCall;
constant LIBPATH = "$*CWD/ncitest";
sub hola(Str) is native(LIBPATH) is symbol('holadesdec') { * }
hola('Juana');
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.
int suma (int a, int b) {
return (a + b);
}
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).
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 |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Arrays: Por ejemplo |
|
Note
|
En https://docs.raku.org/language/nativecall tienes más información sobre la Interfaz de Llamadas Nativas. |
-
En el canal de IRC #raku se concentra la mayor parte del debate sobre Raku. En: https://perl6.org/community/irc puedes indicar cualquier duda.
-
Si tienes alguna consulta y necesitas respuestas en profundidad indícalas en StackOverflow Raku questions.
-
Echa un vistazo semanal de las novedades de Raku en Rakudo Weekly.
-
Mantente al día con los blogs que tratan de Raku en pl6anet.
-
Suscríbete en el apartado de Raku de Reddit en /r/rakulang.
-
Sigue a la comunidad en Twitter: @perl6org.