# Un comentario de una sola línea comienza con un signo de número #`( Comentarios multilíneas usan #` y signos de encerradura tales como (), [], {}, 「」, etc. ) ## En Raku, se declara una variable lexical usando `my` my $variable; ## Raku tiene 3 tipos básicos de variables: escalares, arrays, y hashes. # Un escalar representa un solo valor. Variables escalares comienzan # con un `$` my $str = 'Cadena'; # Las comillas inglesas ("") permiten la intepolación (lo cual veremos # luego): my $str2 = "Cadena"; ## Los nombres de variables pueden contener pero no terminar con comillas ## simples y guiones. Sin embargo, pueden contener ## (y terminar con) guiones bajos (_): my $nombre'de-variable_ = 5; # Esto funciona! my $booleano = True; # `True` y `False` son valores booleanos en Raku. my $inverso = !$booleano; # Puedes invertir un booleano con el operador prefijo `!` my $bool-forzado = so $str; # Y puedes usar el operador prefijo `so` que # convierte su operador en un Bool ## Un array representa varios valores. Variables arrays comienzan con `@`. ## Las listas son similares pero son un tipo inmutable. my @array = 'a', 'b', 'c'; # equivalente a: my @letras = ; # array de palabras, delimitado por espacios. # Similar al qw de perl, o el %w de Ruby. my @array = 1, 2, 3; say @array[2]; # Los índices de un array empiezan por el 0 -- Este es # el tercer elemento. say "Interpola todos los elementos de un array usando [] : @array[]"; #=> Interpola todos los elementos de un array usando [] : 1 2 3 @array[0] = -1; # Asigna un nuevo valor a un índice del array @array[0, 1] = 5, 6; # Asigna varios valores my @llaves = 0, 2; @array[@llaves] = @letras; # Asignación usando un array que contiene valores # índices say @array; #=> a 6 b ## Un hash contiene parejas de llaves y valores. ## Puedes construir un objeto Pair usando la sintaxis `LLave => Valor`. ## Tablas de hashes son bien rápidas para búsqueda, y son almacenadas ## sin ningún orden. ## Ten en cuenta que las llaves son "aplanadas" en contexto de hash, y ## cualquier llave duplicada es deduplicada. my %hash = 1 => 2, 3 => 4; my %hash = foo => "bar", # las llaves reciben sus comillas # automáticamente. "some other" => "value", # las comas colgantes estań bien. ; ## Aunque los hashes son almacenados internamente de forma diferente a los ## arrays, Raku te permite crear un hash usando un array ## con un número par de elementos fácilmente. my %hash = ; my %hash = llave1 => 'valor1', llave2 => 'valor2'; # ¡el mismo resultado! ## También puedes usar la sintaxis "pareja con dos puntos": ## (especialmente útil para parámetros nombrados que verás más adelante) my %hash = :w(1), # equivalente a `w => 1` # esto es útil para el atajo `True`: :truey, # equivalente a `:truey(True)`, o `truey => True` # y para el `False`: :!falsey, # equivalente a `:falsey(False)`, o `falsey => False` ; say %hash{'llave1'}; # Puedes usar {} para obtener el valor de una llave say %hash; # Si es una cadena de texto, puedes actualmente usar <> # (`{llave1}` no funciona, debido a que Raku no tiene # palabras desnudas (barewords en inglés)) ## Subrutinas, o funciones como otros lenguajes las llaman, son ## creadas con la palabra clave `sub`. sub di-hola { say "¡Hola, mundo!" } ## Puedes proveer argumentos (tipados). Si especificado, ## el tipo será chequeado al tiempo de compilación si es posible. ## De lo contrario, al tiempo de ejecución. sub di-hola-a(Str $nombre) { say "¡Hola, $nombre!"; } ## Una subrutina devuelve el último valor evaluado del bloque. sub devolver-valor { 5; } say devolver-valor; # imprime 5 sub devolver-vacio { } say devolver-vacio; # imprime Nil ## Algunas estructuras de control producen un valor. Por ejemplo if: sub devuelva-si { if True { "Truthy"; } } say devuelva-si; # imprime Truthy ## Otras no, como un bucle for: sub return-for { for 1, 2, 3 { } } say return-for; # imprime Nil ## Una subrutina puede tener argumentos opcionales: sub con-opcional($arg?) { # el signo "?" marca el argumento opcional say "Podría returnar `(Any)` (valor de Perl parecido al 'null') si no me pasan un argumento, o returnaré mi argumento"; $arg; } con-opcional; # devuelve Any con-opcional(); # devuelve Any con-opcional(1); # devuelve 1 ## También puedes proveer un argumento por defecto para ## cuando los argumentos no son proveídos: sub hola-a($nombre = "Mundo") { say "¡Hola, $nombre!"; } hola-a; #=> ¡Hola, Mundo! hola-a(); #=> ¡Hola, Mundo! hola-a('Tú'); #=> ¡Hola, Tú! ## De igual manera, al usar la sintaxis parecida a la de los hashes ## (¡Hurra, sintaxis unificada!), puedes pasar argumentos *nombrados* ## a una subrutina. Ellos son opcionales, y por defecto son del tipo "Any". sub con-nombre($arg-normal, :$nombrado) { say $arg-normal + $nombrado; } con-nombre(1, nombrado => 6); #=> 7 ## Sin embargo, debes tener algo en cuenta aquí: ## Si pones comillas alrededor de tu llave, Raku no será capaz de verla ## al tiempo de compilación, y entonces tendrás un solo objeto Pair como ## un argumento posicional, lo que significa que el siguiente ejemplo ## falla: con-nombre(1, 'nombrado' => 6); con-nombre(2, :nombrado(5)); #=> 7 ## Para hacer un argumento nombrado mandatorio, puedes utilizar el ## inverso de `?`, `!`: sub con-nombre-mandatorio(:$str!) { say "$str!"; } con-nombre-mandatorio(str => "Mi texto"); #=> Mi texto! con-nombre-mandatorio; # error al tiempo de ejecución: # "Required named parameter not passed" # ("Parámetro nombrado requerido no proveído") con-nombre-mandatorio(3); # error al tiempo de ejecución: # "Too many positional parameters passed" # ("Demasiados argumentos posicionales proveídos") ## Si una subrutina toma un argumento booleano nombrado ... sub toma-un-bool($nombre, :$bool) { say "$nombre toma $bool"; } ## ... puedes usar la misma sintaxis de hash de un "booleano corto": takes-a-bool('config', :bool); # config toma True takes-a-bool('config', :!bool); # config toma False ## También puedes proveer tus argumentos nombrados con valores por defecto: sub nombrado-definido(:$def = 5) { say $def; } nombrado-definido; #=> 5 nombrado-definido(def => 15); #=> 15 ## Dado que puedes omitir los paréntesis para invocar una función sin ## argumentos, necesitas usar "&" en el nombre para almacenar la función ## `di-hola` en una variable. my &s = &di-hola; my &otra-s = sub { say "¡Función anónima!" } ## Una subrutina puede tener un parámetro "slurpy", o "no importa cuantos", ## indicando que la función puede recibir cualquier número de parámetros. sub muchos($principal, *@resto) { #`*@` (slurpy) consumirá lo restante ## Nota: Puedes tener parámetros *antes que* un parámetro "slurpy" (como ## aquí) pero no *después* de uno. say @resto.join(' / ') ~ "!"; } say muchos('Feliz', 'Cumpleaño', 'Cumpleaño'); #=> Feliz / Cumpleaño! # Nota que el asterisco (*) no # consumió el parámetro frontal. ## Puedes invocar un función con un array usando el ## operador "aplanador de lista de argumento" `|` ## (actualmente no es el único rol de este operador pero es uno de ellos) sub concat3($a, $b, $c) { say "$a, $b, $c"; } concat3(|@array); #=> a, b, c # `@array` fue "aplanado" como parte de la lista de argumento ## En Raku, valores son actualmente almacenados en "contenedores". ## El operador de asignación le pregunta al contenedor en su izquierda ## almacenar el valor a su derecha. Cuando se pasan alrededor, contenedores ## son marcados como inmutables. Esto significa que, en una función, tu ## tendrás un error si tratas de mutar uno de tus argumentos. ## Si realmente necesitas hacerlo, puedes preguntar por un contenedor ## mutable usando `is rw`: sub mutar($n is rw) { $n++; say "¡\$n es ahora $n!"; } my $m = 42; mutar $m; # ¡$n es ahora 43! ## Esto funciona porque estamos pasando el contenedor $m para mutarlo. Si ## intentamos pasar un número en vez de pasar una variable, no funcionará ## dado que no contenedor ha sido pasado y números enteros son inmutables ## por naturaleza: mutar 42; # Parámetro '$n' esperaba un contenedor mutable, # pero recibió un valor Int ## Si en cambio quieres una copia, debes usar `is copy`. ## Por si misma, una subrutina devuelve un contenedor, lo que significa ## que puede ser marcada con rw: my $x = 42; sub x-almacena() is rw { $x } x-almacena() = 52; # En este caso, los paréntesis son mandatorios # (porque de otra forma, Raku piensa que la función # `x-almacena` es un identificador). say $x; #=> 52 ## - `if` ## Antes de hablar acerca de `if`, necesitamos saber cuales valores son ## "Truthy" (representa True (verdadero)), y cuales son "Falsey" ## (o "Falsy") -- representa False (falso). Solo estos valores son ## Falsey: 0, (), {}, "", Nil, un tipo (como `Str` o`Int`) y ## por supuesto False. Todos los valores son Truthy. if True { say "¡Es verdadero!"; } unless False { say "¡No es falso!"; } ## Como puedes observar, no necesitas paréntesis alrededor de condiciones. ## Sin embargo, necesitas las llaves `{}` alrededor del cuerpo de un bloque: # if (true) say; # !Esto no funciona! ## También puedes usar sus versiones sufijos seguidas por la palabra clave: say "Un poco verdadero" if True; ## - La condicional ternaria, "?? !!" (como `x ? y : z` en otros lenguajes) ## devuelve $valor-si-verdadera si la condición es verdadera y ## $valor-si-falsa si es falsa. ## my $resultado = $valor condición ?? $valor-si-verdadera !! $valor-si-falsa; my $edad = 30; say $edad > 18 ?? "Eres un adulto" !! "Eres menor de 18"; ## - `given`-`when` se parece al `switch` de otros lenguajes, pero es más ## poderoso gracias a la coincidencia inteligente ("smart matching" en inglés) ## y la "variable tópica" $_ de Perl. ## ## Esta variable ($_) contiene los argumentos por defecto de un bloque, ## la iteración actual de un loop (a menos que sea explícitamente ## nombrado), etc. ## ## `given` simplemente pone su argumento en `$_` (como un bloque lo haría), ## y `when` lo compara usando el operador de "coincidencia inteligente" (`~~`). ## ## Dado que otras construcciones de Raku usan esta variable (por ejemplo, ## el bucle `for`, bloques, etc), esto se significa que el poderoso `when` no ## solo se aplica con un `given`, sino que se puede usar en cualquier ## lugar donde exista una variable `$_`. given "foo bar" { say $_; #=> foo bar when /foo/ { # No te preocupies acerca de la coincidencia inteligente – # solo ten presente que `when` la usa. # Esto es equivalente a `if $_ ~~ /foo/`. say "¡Yay!"; } when $_.chars > 50 { # coincidencia inteligente con cualquier cosa True es True, # i.e. (`$a ~~ True`) # por lo tanto puedes también poner condiciones "normales". # Este `when` es equivalente a este `if`: # if $_ ~~ ($_.chars > 50) {...} # que significa: # if $_.chars > 50 {...} say "¡Una cadena de texto bien larga!"; } default { # lo mismo que `when *` (usando la Whatever Star) say "Algo más"; } } ## - `loop` es un bucle infinito si no le pasas sus argumentos, ## pero también puede ser un bucle for al estilo de C: loop { say "¡Este es un bucle infinito!"; last; # last interrumpe el bucle, como la palabra clave `break` # en otros lenguajes. } loop (my $i = 0; $i < 5; $i++) { next if $i == 3; # `next` salta a la siguiente iteración, al igual # que `continue` en otros lenguajes. Ten en cuenta que # también puedes usar la condicionales postfix (sufijas) # bucles, etc. say "¡Este es un bucle al estilo de C!"; } ## - `for` - Hace iteraciones en un array for @array -> $variable { say "¡He conseguido una $variable!"; } ## Como vimos con `given`, la variable de una "iteración actual" por defecto ## es `$_`. Esto significa que puedes usar `when` en un bucle `for` como ## normalmente lo harías con `given`. for @array { say "he conseguido a $_"; .say; # Esto es también permitido. # Una invocación con punto (dot call) sin "tópico" (recibidor) es # enviada a `$_` por defecto. $_.say; # lo mismo de arriba, lo cual es equivalente. } for @array { # Puedes... next if $_ == 3; # Saltar a la siguiente iteración (`continue` en # lenguages parecido a C) redo if $_ == 4; # Re-hacer la iteración, manteniendo la # misma variable tópica (`$_`) last if $_ == 5; # Salir fuera del bucle (como `break` # en lenguages parecido a C) } ## La sintaxis de "bloque puntiagudo" no es específica al bucle for. ## Es solo una manera de expresar un bloque en Raku. if computación-larga() -> $resultado { say "El resultado es $resultado"; } ## Dados que los lenguajes de la familia Perl son lenguages basados ## mayormente en operadores, los operadores de Raku son actualmente ## subrutinas un poco cómicas en las categorías sintácticas. Por ejemplo, ## infix:<+> (adición) o prefix: (bool not). ## Las categorías son: ## - "prefix" (prefijo): anterior a (como `!` en `!True`). ## - "postfix" (sufijo): posterior a (como `++` en `$a++`). ## - "infix" (infijo): en medio de (como `*` en `4 * 3`). ## - "circumfix" (circunfijo): alrededor de (como `[`-`]` en `[1, 2]`). ## - "post-circumfix" (pos-circunfijo): alrededor de un término, ## posterior a otro término. ## (como `{`-`}` en `%hash{'key'}`) ## La lista de asociatividad y precedencia se explica más abajo. ## ¡Bueno, ya estás listo(a)! ## * Chequeando igualdad ## - `==` se usa en comparaciones numéricas. 3 == 4; # Falso 3 != 4; # Verdadero ## - `eq` se usa en comparaciones de cadenas de texto. 'a' eq 'b'; 'a' ne 'b'; # no igual 'a' !eq 'b'; # lo mismo que lo anterior ## - `eqv` es equivalencia canónica (or "igualdad profunda") (1, 2) eqv (1, 3); ## - Operador de coincidencia inteligente (smart matching): `~~` ## Asocia (aliasing en inglés) el lado izquierda a la variable $_ ## y después evalúa el lado derecho. ## Aquí algunas comparaciones semánticas comunes: ## Igualdad de cadena de texto o numérica 'Foo' ~~ 'Foo'; # True si las cadenas de texto son iguales. 12.5 ~~ 12.50; # True si los números son iguales. ## Regex - Para la comparación de una expresión regular en contra ## del lado izquierdo. Devuelve un objeto (Match), el cual evalúa ## como True si el regex coincide con el patrón. my $obj = 'abc' ~~ /a/; say $obj; # 「a」 say $obj.WHAT; # (Match) ## Hashes 'llave' ~~ %hash; # True si la llave existe en el hash ## Tipo - Chequea si el lado izquierdo "tiene un tipo" (puede chequear ## superclases y roles) 1 ~~ Int; # True (1 es un número entero) ## Coincidencia inteligente contra un booleano siempre devuelve ese ## booleano (y lanzará una advertencia). 1 ~~ True; # True False ~~ True; # True ## La sintaxis general es $arg ~~ &función-returnando-bool; ## Para una lista completa de combinaciones, usa esta tabla: ## http://perlcabal.org/syn/S03.html#Smart_matching ## También, por supuesto, tienes `<`, `<=`, `>`, `>=`. ## Sus equivalentes para cadenas de texto están disponibles: ## `lt`, `le`, `gt`, `ge`. 3 > 4; ## * Constructores de rango 3 .. 7; # 3 a 7, ambos incluidos ## `^` en cualquier lado excluye a ese lado: 3 ^..^ 7; # 3 a 7, no incluidos (básicamente `4 .. 6`) ## Esto también funciona como un atajo para `0..^N`: ^10; # significa 0..^10 ## Esto también nos permite demostrar que Raku tiene arrays ## ociosos/infinitos, usando la Whatever Star: my @array = 1..*; # 1 al Infinito! `1..Inf` es lo mismo. say @array[^10]; # puedes pasar arrays como subíndices y devolverá # un array de resultados. Esto imprimirá # "1 2 3 4 5 6 7 8 9 10" (y no se quedaré sin memoria!) ## Nota: Al leer una lista infinita, Raku "cosificará" los elementos que ## necesita y los mantendrá en la memoria. Ellos no serán calculados más de ## una vez. Tampoco calculará más elementos de los que necesita. ## Un índice de array también puede ser una clausura ("closure" en inglés). ## Será llamada con la longitud como el argumento say join(' ', @array[15..*]); #=> 15 16 17 18 19 ## lo que es equivalente a: say join(' ', @array[-> $n { 15..$n }]); ## Nota: Si tratas de hacer cualquiera de esos con un array infinito, ## provocará un array infinito (tu programa nunca terminará) ## Puedes usar eso en los lugares que esperaría, como durante la asignación ## a un array my @números = ^20; ## Aquí los números son incrementados por "6"; más acerca del ## operador `...` adelante. my @seq = 3, 9 ... * > 95; # 3 9 15 21 27 [...] 81 87 93 99; @números[5..*] = 3, 9 ... *; # aunque la secuencia es infinita, # solo los 15 números necesarios será calculados. say @números; #=> 0 1 2 3 4 3 9 15 21 [...] 81 87 # (solamente 20 valores) ## * And &&, Or || 3 && 4; # 4, el cual es Truthy. Invoca `.Bool` en `4` y obtiene `True`. 0 || False; # False. Invoca `.Bool` en `0` ## * Versiones circuito corto de lo de arriba ## && Devuelve el primer argumento que evalúa a False, o el último. my ( $a, $b, $c ) = 1, 0, 2; $a && $b && $c; # Devuelve 0, el primer valor que es False ## || Devuelve el primer argumento que evalúa a True. $b || $a; # 1 ## Y porque tu lo querrás, también tienes operadores de asignación ## compuestos: $a *= 2; # multiplica y asigna. Equivalente a $a = $a * 2; $b %%= 5; # divisible por y asignación. Equivalente $b = $b %% 5; @array .= sort; # invoca el método `sort` y asigna el resultado devuelto. ## Como dijimos anteriormente, Raku tiene subrutinas realmente poderosas. ## Veremos unos conceptos claves que la hacen mejores que en cualquier otro ## lenguaje :-). ## Es la abilidad de extraer arrays y llaves (También conocido como ## "destructuring"). También funcionará en `my` y en las listas de parámetros. my ($f, $g) = 1, 2; say $f; #=> 1 my ($, $, $h) = 1, 2, 3; # mantiene los anónimos no interesante say $h; #=> 3 my ($cabeza, *@cola) = 1, 2, 3; # Sí, es lo mismo que con subrutinas "slurpy" my (*@small) = 1; sub desempacar_array(@array [$fst, $snd]) { say "Mi primero es $fst, mi segundo es $snd! De todo en todo, soy un @array[]."; # (^ recuerda que `[]` interpola el array) } desempacar_array(@cola); #=> My first is 2, my second is 3 ! All in all, I'm 2 3 ## Si no está usando el array, puedes también mantenerlo anónimo, como un ## escalar: sub primero-de-array(@ [$fst]) { $fst } primero-de-array(@small); #=> 1 primero-de-array(@tail); # Lanza un error "Demasiados argumentos posicionales # proveídos" # (lo que significa que el array es muy grande). ## También puedes usar un slurp ... sub slurp-en-array(@ [$fst, *@rest]) { # Podrías mantener `*@rest` anónimos say $fst + @rest.elems; # `.elems` returna la longitud de una lista. # Aquí, `@rest` es `(3,)`, since `$fst` holds the `2`. } slurp-en-array(@tail); #=> 3 ## Hasta podrías hacer un extracción usando una slurpy (pero no sería útil ;-).) sub fst(*@ [$fst]) { # o simplemente: `sub fst($fst) { ... }` say $fst; } fst(1); #=> 1 fst(1, 2); # errores con "Too many positional parameters passed" ## También puedes desestructurar hashes (y clases, las cuales ## veremos adelante). La sintaxis es básicamente ## `%nombre-del-hash (:llave($variable-para-almacenar))`. ## El hash puede permanecer anónimos si solo necesitas los valores extraídos. sub llave-de(% (:azul($val1), :red($val2))) { say "Valores: $val1, $val2."; } ## Después invócala con un hash: (necesitas mantener las llaves ## de los parejas de llave y valor para ser un hash) llave-de({azul => 'blue', rojo => "red"}); #llave-de(%hash); # lo mismo (para un `%hash` equivalente) ## La última expresión de una subrutina es devuelta inmediatamente ## (aunque puedes usar la palabra clave `return`): sub siguiente-indice($n) { $n + 1; } my $nuevo-n= siguiente-indice(3); # $nuevo-n es ahora 4 ## Este es cierto para todo, excepto para las construcciones de bucles ## (debido a razones de rendimiento): Hay una razón de construir una lista ## si la vamos a desechar todos los resultados. ## Si todavías quieres construir una, puedes usar la sentencia prefijo `do`: ## (o el prefijo `gather`, el cual veremos luego) sub lista-de($n) { do for ^$n { # nota el uso del operador de rango `^` (`0..^N`) $_ # iteración de bucle actual } } my @list3 = lista-de(3); #=> (0, 1, 2) ## Puedes crear una lambda con `-> {}` ("bloque puntiagudo") o `{}` ("bloque") my &lambda = -> $argumento { "El argumento pasado a esta lambda es $argumento" } ## `-> {}` y `{}` son casi la misma cosa, excepto que la primerra puede ## tomar argumentos, y la segunda puede ser malinterpretada como un hash ## por el parseador. ## Podemos, por ejemplo, agregar 3 a cada valor de un array usando map: my @arraymas3 = map({ $_ + 3 }, @array); # $_ es el argumento implícito ## Una subrutina (`sub {}`) tiene semánticas diferentes a un ## bloque (`{}` or `-> {}`): Un bloque no tiene "contexto funcional" ## (aunque puede tener argumentos), lo que significa que si quieres devolver ## algo desde un bloque, vas a returnar desde la función parental. Compara: sub is-in(@array, $elem) { # esto `devolverá` desde la subrutina `is-in` # Una vez que la condición evalúa a True, el bucle terminará map({ return True if $_ == $elem }, @array); } sub truthy-array(@array) { # esto producirá un array de `True` Y `False`: # (también puedes decir `anon sub` para "subrutina anónima") map(sub ($i) { if $i { return True } else { return False } }, @array); # ^ el `return` solo devuelve desde la `sub` } ## También puedes usar la "whatever star" para crear una función anónima ## (terminará con el último operador en la expresión actual) my @arraymas3 = map(*+3, @array); # `*+3` es lo mismo que `{ $_ + 3 }` my @arraymas3 = map(*+*+3, @array); # lo mismo que `-> $a, $b { $a + $b + 3 }` # también `sub ($a, $b) { $a + $b + 3 }` say (*/2)(4); #=> 2 # Inmediatamente ejecuta la función que Whatever creó. say ((*+3)/5)(5); #=> 1.6 # ¡funciona hasta con los paréntesis! ## Pero si necesitas más que un argumento (`$_`) en un bloque ## (sin depender en `-> {}`), también puedes usar la sintaxis implícita ## de argumento, `$` : map({ $^a + $^b + 3 }, @array); # equivalente a lo siguiente: map(sub ($a, $b) { $a + $b + 3 }, @array); # (aquí con `sub`) ## Nota : Esos son ordernados lexicográficamente. # `{ $^b / $^a }` es como `-> $a, $b { $b / $a }` ## Raku es gradualmente tipado. Esto quiere decir que tu especifica el ## tipo de tus variables/argumentos/devoluciones (return), o puedes omitirlos ## y serán "Any" por defecto. ## Obviamente tienes acceso a algunas tipos básicos, como Int y Str. ## Las construcciones para declarar tipos son "class", "role", lo cual ## verás más adelante. ## Por ahora, examinemos "subset" (subconjunto). ## Un "subset" es un "sub-tipo" con chequeos adicionales. ## Por ejemplo: "un número entero bien grande es un Int que es mayor que 500" ## Puedes especificar el tipo del que creas el subconjunto (por defecto, Any), ## y añadir chequeos adicionales con la palabra clave "where" (donde): subset EnteroGrande of Int where * > 500; ## Raku puede decidir que variante de una subrutina invocar basado en el ## tipo de los argumento, o precondiciones arbitrarias, como con un tipo o ## un `where`: ## con tipos multi sub dilo(Int $n) { # nota la palabra clave `multi` aquí say "Número: $n"; } multi dilo(Str $s) { # un multi es una subrutina por defecto say "Cadena de texto: $s"; } dilo("azul"); # prints "Cadena de texto: azul" dilo(True); # falla al *tiempo de compilación* con # (invocar 'dilo' nunca funcionará con argumentos de tipos ...") ## con precondición arbitraria (¿recuerdas los subconjuntos?): multi es-grande(Int $n where * > 50) { "¡Sí!" } # usando una clausura multi es-grande(Int $ where 10..50) { "Tal vez." } # Usando coincidencia inteligente # (podrías usar un regexp, etc) multi es-grande(Int $) { "No" } subset Par of Int where * %% 2; multi inpar-o-par(Par) { "Par" } # El caso principal usando el tipo. # No nombramos los argumentos, multi inpar-o-par($) { "Inpar" } # "else" ## ¡Podrías despachar basado en la presencia de argumentos posicionales! multi sin_ti-o-contigo(:$with!) { # Necesitas hacerlo mandatorio # para despachar en contra del argumento. say "¡Puedo vivir! Actualmente, no puedo."; } multi sin_ti-o-contigo { say "Definitivamente no puedo vivir."; } ## Esto es muy útil para muchos propósitos, como subrutinas `MAIN` (de las ## cuales hablaremos luego), y hasta el mismo lenguaje la está usando ## en muchos lugares. ## ## - `is`, por ejemplo, es actualmente un `multi sub` llamado ## `trait_mod:`. ## - `is rw`, es simplemente un despacho a una función con esta signatura: ## sub trait_mod:(Routine $r, :$rw!) {} ## ## (¡lo pusimos en un comentario dado que ejecutando esto sería una terrible ## idea!) ## En Raku, a diferencia de otros lenguajes de scripting, (tales como ## (Python, Ruby, PHP), debes declarar tus variables antes de usarlas. El ## declarador `my`, del cual aprendiste anteriormente, usa "ámbito léxical". ## Hay otros declaradores (`our`, `state`, ..., ) los cuales veremos luego. ## Esto se llama "ámbito léxico", donde en los bloques internos, ## puedes acceder variables de los bloques externos. my $archivo-en-ámbito = 'Foo'; sub externo { my $ámbito-externo = 'Bar'; sub interno { say "$archivo-en-ámbito $ámbito-externo"; } &interno; # devuelve la función } outer()(); #=> 'Foo Bar' ## Como puedes ver, `$archivo-en-ámbito` y `$ámbito-externo` ## fueron capturados. Pero si intentaramos usar `$bar` fuera de `foo`, ## la variable estaría indefinida (y obtendrías un error al tiempo de ## compilación). ## Hay muchos `twigils` especiales (sigilos compuestos) en Raku. ## Los twigils definen el ámbito de las variables. ## Los twigils * y ? funcionan con variables regulares: ## * Variable dinámica ## ? Variable al tiempo de compilación ## Los twigils ! y . son usados con los objetos de Raku: ## ! Atributo (miembro de la clase) ## . Método (no una variable realmente) ## El twigil `*`: Ámbito dinámico ## Estas variables usan el twigil `*` para marcar variables con ámbito ## dinámico. Variables con ámbito dinámico son buscadas a través del ## invocador, no a través del ámbito externo. my $*ambito_din_1 = 1; my $*ambito_din_2 = 10; sub di_ambito { say "$*ambito_din_1 $*ambito_din_2"; } sub invoca_a_di_ambito { my $*ambito_din_1 = 25; # Define a $*ambito_din_1 solo en esta subrutina. $*ambito_din_2 = 100; # Cambiará el valor de la variable en ámbito. di_ambito(); #=> 25 100 $*ambito_din_1 y 2 serán buscadas en la invocación. # Se usa el valor de $*ambito_din_1 desde el ámbito léxico de esta # subrutina aunque los bloques no están anidados (están anidados por # invocación). } di_ambito(); #=> 1 10 invoca_a_di_ambito(); #=> 25 100 # Se usa a $*ambito_din_1 como fue definida en invoca_a_di_ambito # aunque la estamos invocando desde afuera. di_ambito(); #=> 1 100 Cambiamos el valor de $*ambito_din_2 en invoca_a_di_ambito # por lo tanto su valor a cambiado. ## Para invocar a un método en un objeto, agrega un punto seguido por el ## nombre del objeto: ## => $object.method ## Las classes son declaradas usando la palabra clave `class`. Los atributos ## son declarados con la palabra clave `has`, y los métodos con `method`. ## Cada atributo que es privado usa el twigil `!`. Por ejemplo: `$!attr`. ## Atributos públicos inmutables usan el twigil `.` (los puedes hacer ## mutables con `is rw`). ## La manera más fácil de recordar el twigil `$.` is comparándolo ## con como los métodos son llamados. ## El modelo de objeto de Raku ("SixModel") es muy flexible, y te permite ## agregar métodos dinámicamente, cambiar la semántica, etc ... ## (no hablaremos de todo esto aquí. Por lo tanto, refiérete a: ## https://docs.raku.org/language/objects.html). class Clase-Atrib { has $.atrib; # `$.atrib` es inmutable. # Desde dentro de la clase, usa `$!atrib` para modificarlo. has $.otro-atrib is rw; # Puedes marcar un atributo como público con `rw`. has Int $!atrib-privado = 10; method devolver-valor { $.atrib + $!atrib-privado; } method asignar-valor($param) { # Métodos pueden tomar parámetros. $!attrib = $param; # Esto funciona porque `$!` es siempre mutable. # $.attrib = $param; # Incorrecto: No puedes usar la versión inmutable `$.`. $.otro-atrib = 5; # Esto funciona porque `$.otro-atrib` es `rw`. } method !metodo-privado { say "Este método es privado para la clase !"; } }; ## Crear una nueva instancia de Clase-Atrib con $.atrib asignado con 5: ## Nota: No puedes asignarle un valor a atrib-privado desde aquí (más de ## esto adelante). my $class-obj = Clase-Atrib.new(atrib => 5); say $class-obj.devolver-valor; #=> 5 # $class-obj.atrib = 5; # Esto falla porque `has $.atrib` es inmutable $class-obj.otro-atrib = 10; # En cambio, esto funciona porque el atributo # público es mutable (`rw`). ## Raku también tiene herencia (junto a herencia múltiple) ## Mientras los métodos declarados con `method` son heredados, aquellos ## declarados con `submethod` no lo son. ## Submétodos son útiles para la construcción y destrucción de tareas, ## tales como BUILD, o métodos que deben ser anulados por subtipos. ## Aprenderemos acerca de BUILD más adelante. class Padre { has $.edad; has $.nombre; # Este submétodo no será heredado por la clase Niño. submethod color-favorito { say "Mi color favorito es Azul"; } # Este método será heredado method hablar { say "Hola, mi nombre es $!nombre" } } # Herencia usa la palabra clave `is` class Niño is Padre { method hablar { say "Goo goo ga ga" } # Este método opaca el método `hablar` de Padre. # Este niño no ha aprendido a hablar todavía. } my Padre $Richard .= new(edad => 40, nombre => 'Richard'); $Richard.color-favorito; #=> "Mi color favorito es Azul" $Richard.hablar; #=> "Hola, mi nombre es Richard" ## $Richard es capaz de acceder el submétodo; él sabe como decir su nombre. my Niño $Madison .= new(edad => 1, nombre => 'Madison'); $Madison.hablar; # imprime "Goo goo ga ga" dado que el método fue cambiado # en la clase Niño. # $Madison.color-favorito # no funciona porque no es heredado ## Cuando se usa `my T $var` (donde `T` es el nombre de la clase), `$var` ## inicia con `T` en si misma, por lo tanto puedes invocar `new` en `$var`. ## (`.=` es sólo la invocación por punto y el operador de asignación: ## `$a .= b` es lo mismo que `$a = $a.b`) ## Por ejemplo, la instancia $Richard pudo también haber sido declarada así: ## my $Richard = Padre.new(edad => 40, nombre => 'Richard'); ## También observa que `BUILD` (el método invocado dentro de `new`) ## asignará propiedades de la clase padre, por lo que puedes pasar ## `val => 5`. ## Roles son suportados también (comúnmente llamados Mixins en otros ## lenguajes) role PrintableVal { has $!counter = 0; method print { say $.val; } } ## Se "importa" un mixin (un "role") con "does": class Item does PrintableVal { has $.val; ## Cuando se utiliza `does`, un `rol` se mezcla en al clase literalmente: ## los métodos y atributos se ponen juntos, lo que significa que una clase ## puede acceder los métodos y atributos privados de su rol (pero no lo inverso!): method access { say $!counter++; } ## Sin embargo, esto: ## method print {} ## es SÓLO válido cuando `print` no es una `multi` con el mismo dispacho. ## (esto significa que una clase padre puede opacar una `multi print() {}` ## de su clase hijo/a, pero es un error sin un rol lo hace) ## NOTA: Puedes usar un rol como una clase (con `is ROLE`). En este caso, ## métodos serán opacados, dado que el compilador considerará `ROLE` ## como una clase. } ## Excepciones están construidas al tope de las clases, en el paquete ## `X` (como `X::IO`). ## En Raku, excepciones son lanzadas automáticamente. open 'foo'; #=> Failed to open file foo: no such file or directory ## También imprimirá la línea donde el error fue lanzado y otra información ## concerniente al error. ## Puedes lanzar una excepción usando `die`: die 'Error!'; #=> Error! ## O más explícitamente: die X::AdHoc.new(payload => 'Error!'); ## En Raku, `orelse` es similar al operador `or`, excepto que solamente ## coincide con variables indefinidas, en cambio de cualquier cosa ## que evalúa a falso. ## Valores indefinidos incluyen: `Nil`, `Mu` y `Failure`, también como ## `Int`, `Str` y otros tipos que no han sido inicializados a ningún valor ## todavía. ## Puedes chequear si algo está definido o no usando el método defined: my $no-inicializada; say $no-inicializada.defined; #=> False ## Al usar `orelse`, se desarmará la excepción y creará un alias de dicho ## fallo en $_ ## Esto evitará que sea automáticamente manejado e imprima una marejada de ## mensajes de errores en la pantalla. ## Podemos usar el método de excepción en $_ para acceder la excepción: open 'foo' orelse say "Algo pasó {.exception}"; ## Esto también funciona: open 'foo' orelse say "Algo pasó $_"; #=> Algo pasó #=> Failed to open file foo: no such file or directory ## Ambos ejemplos anteriores funcionan pero en caso de que consigamos un ## objeto desde el lado izquierdo que no es un fallo, probablemente ## obtendremos una advertencia. Más abajo vemos como usar `try` y `CATCH` ## para ser más expecíficos con las excepciones que capturamos. ## Al usar `try` y `CATCH`, puedes contener y manejar excepciones sin ## interrumpir el resto del programa. `try` asignará la última excepción ## a la variable especial `$!`. ## Nota: Esto no tiene ninguna relación con las variables $!. try open 'foo'; say "Bueno, lo intenté! $!" if defined $!; #=> Bueno, lo intenté! Failed to open file #foo: no such file or directory ## Ahora, ¿qué debemos hacer si queremos más control sobre la excepción? ## A diferencia de otros lenguajes, en Raku se pone el bloque `CATCH` ## *dentro* del bloque a intentar (`try`). Similarmente como $_ fue asignada ## cuando 'disarmamos' la excepción con `orelse`, también usamos $_ en el ## bloque CATCH. ## Nota: ($! es solo asignada *después* del bloque `try`) ## Por defecto, un bloque `try` tiene un bloque `CATCH` que captura ## cualquier excepción (`CATCH { default {} }`). try { my $a = (0 %% 0); CATCH { say "Algo pasó: $_" } } #=> Algo pasó: Attempt to divide by zero using infix:<%%> ## Puedes redefinir lo anterior usando `when` y (`default`) ## para manejar las excepciones que desees: try { open 'foo'; CATCH { # En el bloque `CATCH`, la excepción es asignada a $_ when X::AdHoc { say "Error: $_" } #=>Error: Failed to open file /dir/foo: no such file or directory ## Cualquier otra excepción será levantada de nuevo, dado que no ## tenemos un `default`. ## Básicamente, si un `when` ## Basically, if a `when` matches (or there's a `default`) marks the ## exception as ## "handled" so that it doesn't get re-thrown from the `CATCH`. ## You still can re-throw the exception (see below) by hand. } } ## En Raku, excepciones poseen ciertas sutilezas. Algunas ## subrutinas en Raku devuelven un `Failure`, el cual es un tipo de ## "excepción no levantada". Ellas no son levantadas hasta que tu intentas ## mirar a sus contenidos, a menos que invoques `.Bool`/`.defined` sobre ## ellas - entonces, son manejadas. ## (el método `.handled` es `rw`, por lo que puedes marcarlo como `False` ## por ti mismo) ## Puedes levantar un `Failure` usando `fail`. Nota que si el pragma ## `use fatal` estás siendo utilizado, `fail` levantará una excepión (como ## `die`). fail "foo"; # No estamos intentando acceder el valor, por lo tanto no problema. try { fail "foo"; CATCH { default { say "Levantó un error porque intentamos acceder el valor del fallo!" } } } ## También hay otro tipo de excepción: Excepciones de control. ## Esas son excepciones "buenas", las cuales suceden cuando cambias el flujo ## de tu programa, usando operadores como `return`, `next` or `last`. ## Puedes capturarlas con `CONTROL` (no lista un 100% en Rakudo todavía). ## Paquetes son una manera de reusar código. Paquetes son como ## "espacio de nombres" (namespaces en inglés), y cualquier elemento del ## modelo seis (`module`, `role`, `class`, `grammar`, `subset` y `enum`) ## son paquetes por ellos mismos. (Los paquetes son como el mínimo común ## denominador) ## Los paquetes son importantes - especialmente dado que Perl es bien ## reconocido por CPAN, the Comprehensive Perl Archive Nertwork. ## Puedes usar un módulo (traer sus declaraciones al ámbito) con `use` use JSON::Tiny; # si intalaste Rakudo* o Panda, tendrás este módulo say from-json('[1]').perl; #=> [1] ## A diferencia de Perl, no deberías declarar paquetes usando ## la palabra clave `package`. En vez, usa `class Nombre::Paquete::Aquí;` ## para declarar una clase, o si solamente quieres exportar ## variables/subrutinas, puedes usar `module`. module Hello::World { # forma de llaves # Si `Hello` no existe todavía, solamente será una cola ("stub"), # que puede ser redeclarada más tarde. # ... declaraciones aquí ... } unit module Parse::Text; # forma de ámbito de archivo grammar Parse::Text::Grammar { # Una gramática (grammar en inglés) es un paquete, # en el cual puedes usar `use` } # Aprenderás más acerca de gramáticas en la sección de regex ## Como se dijo anteriormente, cualquier parte del modelo seis es también un ## paquete. Dado que `JSON::Tiny` usa su propia clase `JSON::Tiny::Actions`, ## tu puedes usarla de la manera siguiente: my $acciones = JSON::Tiny::Actions.new; ## Veremos como exportar variables y subrutinas en la siguiente parte: ## En Raku, tu obtienes diferentes comportamientos basado en como declaras ## una variable. ## Ya has visto `my` y `has`, ahora exploraremos el resto. ## * las declaraciones `our` ocurren al tiempo `INIT` (ve "Phasers" más abajo) ## Es como `my`, pero también crea una variable paquete. ## (Todas las cosas relacionadas con paquetes (`class`, `role`, etc) son ## `our` por defecto) module Var::Incrementar { our $nuestra-var = 1; # Nota: No puedes colocar una restricción de tipo my $mi-var = 22; # como Int (por ejemplo) en una variable `our`. our sub Inc { our sub disponible { # Si tratas de hacer subrutinas internas `our`... # Mejor que sepas lo que haces (No lo haga!). say "No hagas eso. En serio. Estás jugando con fuego y te quemarás."; } my sub no-disponible { # `my sub` es por defecto say "No puedes acceder aquí desde fuera. Soy 'my'!"; } say ++$nuestra-var; # Incrementa la variable paquete y muestra su valor } } say $Var::Incrementar::nuestra-var; #=> 1 Esto funciona say $Var::Incrementar::mi-var; #=> (Any) Esto no funcionará. Var::Incrementar::Inc; #=> 2 Var::Incrementar::Inc; #=> 3 # Nota como el valor de $nuestra-var fue # retenido Var::Incrementar::no-disponible; #=> Could not find symbol '&no-disponible' ## * `constant` (ocurre al tiempo `BEGIN`) ## Puedes usar la palabra clave `constant` para declarar una ## variable/símbolo al tiempo de compilación: constant Pi = 3.14; constant $var = 1; ## Y por si te estás preguntando, sí, también puede contener listas infinitas. constant porque-no = 5, 15 ... *; say porque-no[^5]; #=> 5 15 25 35 45 ## * `state` (ocurre al tiempo de ejecución, pero una sola vez) ## Variables "states" son solo inicializadas una vez. ## (ellas existen en otros lenguaje como `static` en C) sub aleatorio-fijo { state $valor = rand; say $valor; } aleatorio-fijo for ^10; # imprimirá el mismo número 10 veces ## Nota, sin embargo, que ellas existen separadamente en diferentes contextos. ## Si declaras una función con un `state` dentro de un bucle, recreará la ## variable por cada iteración del bucle. Observa: for ^5 -> $a { sub foo { state $valor = rand; # Esto imprimirá un valor diferente # por cada valor de `$a` } for ^5 -> $b { say foo; # Esto imprimirá el mismo valor 5 veces, pero sólo 5. # La siguiente iteración ejecutará `rand` nuevamente. } } ## Un phaser en Raku es un bloque que ocurre a determinados puntos de tiempo ## en tu programa. Se les llama phaser porque marca un cambio en la fase de ## de tu programa. Por ejemplo, cuando el programa es compilado, un bucle ## for se ejecuta, dejas un bloque, o una excepción se levanta. ## (¡`CATCH` es actualmente un phaser!) ## Algunos de ellos pueden ser utilizados por sus valores devueltos, otros ## no pueden (aquellos que tiene un "[*]" al inicio de su texto de ## explicación). ## ¡Tomemos una mirada! ## * Phasers al tiempo de compilación BEGIN { say "[*] Se ejecuta al tiempo de compilación, " ~ "tan pronto como sea posible, una sola vez" } CHECK { say "[*] Se ejecuta al tiempo de compilación, " ~ "tan tarde como sea posible, una sola vez" } ## * Phasers al tiempo de ejecución INIT { say "[*] Se ejecuta al tiempo de ejecución, " ~ "tan pronto como sea posible, una sola vez" } END { say "Se ejecuta al tiempo de ejecución, " ~ "tan tarde como sea posible, una sola vez" } ## * Phasers de bloques ENTER { say "[*] Se ejecuta cada vez que entra en un bloque, " ~ "se repite en bloques de bucle" } LEAVE { say "Se ejecuta cada vez que abandona un bloque, incluyendo " ~ "cuando una excepción ocurre. Se repite en bloques de bucle"} PRE { say "Impone una precondición a cada entrada de un bloque, " ~ "antes que ENTER (especialmente útil para bucles)"; say "Si este bloque no returna un valor truthy, " ~ "una excepción del tipo X::Phaser::PrePost será levantada."; } ## Ejemplos: for 0..2 { PRE { $_ > 1 } # Esto fallará con un "Precondition failed" } POST { say "Impone una postcondAsserts a poscondición a la salida de un bloque, " ~ "después de LEAVE (especialmente útil para bucles)"; say "Si este bloque no returna un valor truthy, " ~ "una excepción del tipo X::Phaser::PrePost será levantada, como con PRE."; } for 0..2 { POST { $_ < 2 } # Esto fallará con un "Postcondition failed" } ## * Phasers de bloques/excepciones sub { KEEP { say "Se ejecuta cuando sales de un bloque exitosamente (sin lanzar un excepción)" } UNDO { say "Se ejecuta cuando sale de bloque sin éxito (al lanzar una excepción)" } } ## * Phasers de bucle for ^5 { FIRST { say "[*] La primera vez que un bucle se ejecuta, antes que ENTER" } NEXT { say "Al tiempo de la continuación del bucle, antes que LEAVE" } LAST { say "Al tiempo de la terminación del bucle, después de LEAVE" } } ## * Phasers de rol/clase COMPOSE { "Cuando un rol es compuesto en una clase. /!\ NO IMPLEMENTADO TODAVÍA" } ## Ellos permite pequeños trucos o código brillante...: say "Este código tomó " ~ (time - CHECK time) ~ "s para compilar"; ## ... o brillante organización: sub do-db-stuff { $db.start-transaction; # comienza una transacción nueva KEEP $db.commit; # commit (procede con) la transacción si todo estuvo bien UNDO $db.rollback; # o retrocede si todo falló } ## Los prefijos de sentencias actúan como los phasers: Ellos afectan el ## comportamiento del siguiente código. ## Debido a que son ejecutados en línea con el código ejecutable, ellos ## se escriben en letras minúsculas. (`try` and `start` están teoréticamente ## en esa lista, pero serán explicados en otra parte) ## Nota: Ningunos de estos (excepto `start`) necesitan las llaves `{` y `}`. ## - `do` (el cual ya viste) - ejecuta un bloque o una sentencia como un ## término. ## Normalmente no puedes usar una sentencia como un valor (o término): ## ## my $valor = if True { 1 } # `if` es una sentencia - error del parseador ## ## Esto funciona: my $a = do if True { 5 } # con `do`, `if` ahora se comporta como un término. ## - `once` - se asegura que una porción de código se ejecute una sola vez. for ^5 { once say 1 }; #=> 1 # solo imprime ... una sola vez. ## Al igual que `state`, ellos son clonados por ámbito for ^5 { sub { once say 1 }() } #=> 1 1 1 1 1 # Imprime una sola vez por ámbito léxico ## - `gather` - Hilo de co-rutina ## `gather` te permite tomar (`take`) varios valores en un array, ## al igual que `do`. Encima de esto, te permite tomar cualquier expresión. say gather for ^5 { take $_ * 3 - 1; take $_ * 3 + 1; } #=> -1 1 2 4 5 7 8 10 11 13 say join ',', gather if False { take 1; take 2; take 3; } # no imprime nada. ## - `eager` - Evalúa una sentencia ávidamente (forza contexto ávido) ## No intentes esto en casa: ## ## eager 1..*; # esto probablemente se colgará por un momento ## # (y podría fallar...). ## ## Pero considera lo siguiente: constant tres-veces = gather for ^3 { say take $_ }; # No imprime nada ## frente a esto: constant tres-veces = eager gather for ^3 { say take $_ }; #=> 0 1 2 ## En Raku, los iterables son objetos que pueden ser iterados similar ## a la construcción `for`. ## `flat`, aplana iterables: say (1, 10, (20, 10) ); #=> (1 10 (20 10)) Nota como la agrupación se mantiene say (1, 10, (20, 10) ).flat; #=> (1 10 20 10) Ahora el iterable es plano ## - `lazy` - Aplaza la evaluación actual hasta que el valor sea requirido ## (forza contexto perezoso) my @lazy-array = (1..100).lazy; say @lazy-array.is-lazy; #=> True # Chequea por "pereza" con el método `is-lazy`. say @lazy-array; #=> [...] No se ha iterado sobre la lista for @lazy-array { .print }; # Esto funciona y hará tanto trabajo como sea necesario. [//]: # ( TODO explica que gather/take y map son todos perezosos) ## - `sink` - Un `eager` que desecha los resultados (forza el contexto sink) constant nilthingie = sink for ^3 { .say } #=> 0 1 2 say nilthingie.perl; #=> Nil ## - `quietly` - Un bloque `quietly` reprime las advertencias: quietly { warn 'Esto es una advertencia!' }; #=> No salida ## - `contend` - Intenta efectos secundarios debajo de STM ## ¡No implementado todavía! ## ¡Todo el mundo ama los operadores! Tengamos más de ellos. ## La lista de precedencia puede ser encontrada aquí: ## https://docs.raku.org/language/operators#Operator_Precedence ## Pero primero, necesitamos un poco de explicación acerca ## de la asociatividad: ## * Operadores binarios: $a ! $b ! $c; # con asociatividad izquierda `!`, esto es `($a ! $b) ! $c` $a ! $b ! $c; # con asociatividad derecha `!`, esto es `$a ! ($b ! $c)` $a ! $b ! $c; # sin asociatividad `!`, esto es ilegal $a ! $b ! $c; # con una cadena de asociatividad `!`, esto es `($a ! $b) and ($b ! $c)` $a ! $b ! $c; # con asociatividad de lista `!`, esto es `infix:<>` ## * Operadores unarios: !$a! # con asociatividad izquierda `!`, esto es `(!$a)!` !$a! # con asociatividad derecha `!`, esto es `!($a!)` !$a! # sin asociatividad `!`, esto es ilegal ## Okay, has leído todo esto y me imagino que debería mostrarte ## algo interesante. ## Te mostraré un pequeño secreto (o algo no tan secreto): ## En Raku, todos los operadores son actualmente solo subrutinas. ## Puedes declarar un operador como declaras una subrutina: sub prefix:($ganador) { # se refiere a las categorías de los operadores # (exacto, es el "operador de palabras" `<>`) say "¡$ganador ganó!"; } ganar "El Rey"; #=> ¡El Rey Ganó! # (prefijo se pone delante) ## todavías puedes invocar la subrutina con su "nombre completo": say prefix:(True); #=> False sub postfix:(Int $n) { [*] 2..$n; # usando el meta-operador reduce ... Ve más abajo! } say 5!; #=> 120 # Operadores sufijos (postfix) van *directamente* después del témino. # No espacios en blanco. Puedes usar paréntesis para disambiguar, # i.e. `(5!)!` sub infix:(Int $n, Block $r) { # infijo va en el medio for ^$n { $r(); # Necesitas los paréntesis explícitos para invocar la función # almacenada en la variable `$r`. De lo contrario, te estaría # refiriendo a la variable (no a la función), como con `&r`. } } 3 veces -> { say "hola" }; #=> hola #=> hola #=> hola # Se te recomienda que ponga espacios # alrededor de la invocación de operador infijo. ## Para los circunfijos y pos-circunfijos sub circumfix:<[ ]>(Int $n) { $n ** $n } say [5]; #=> 3125 # un circunfijo va alrededor. De nuevo, no espacios en blanco. sub postcircumfix:<{ }>(Str $s, Int $idx) { ## un pos-circunfijo es ## "después de un término y alrededor de algo" $s.substr($idx, 1); } say "abc"{1}; #=> b # depués del término `"abc"`, y alrededor del índice (1) ## Esto es de gran valor -- porque todo en Raku usa esto. ## Por ejemplo, para eliminar una llave de un hash, tu usas el adverbio ## `:delete` (un simple argumento con nombre debajo): %h{$llave}:delete; ## es equivalente a: postcircumfix:<{ }>(%h, $llave, :delete); # (puedes invocar # operadores de esta forma) ## ¡*Todos* usan los mismos bloques básicos! ## Categorías sintácticas (prefix, infix, ...), argumentos nombrados ## (adverbios), ... - usados para construir el lenguaje - están al alcance ## de tus manos y disponibles para ti. ## (obviamente, no se te recomienda que hagas un operador de *cualquier ## cosa* -- Un gran poder conlleva una gran responsabilidad.) ## ¡Prepárate! Prepárate porque nos estamos metiendo bien hondo ## en el agujero del conejo, y probablemente no querrás regresar a ## otros lenguajes después de leer esto. ## (Me imagino que ya no quieres a este punto). ## Meta-operadores, como su nombre lo sugiere, son operadores *compuestos*. ## Básicamente, ellos son operadores que se aplican a otros operadores. ## * El meta-operador reduce (reducir) ## Es un meta-operador prefijo que toman una función binaria y ## una o varias listas. Sino se pasa ningún argumento, ## returna un "valor por defecto" para este operador ## (un valor sin significado) o `Any` si no hay ningún valor. ## ## De lo contrario, remueve un elemento de la(s) lista(s) uno a uno, y ## aplica la función binaria al último resultado (o al primer elemento de ## la lista y el elemento que ha sido removido). ## ## Para sumar una lista, podrías usar el meta-operador "reduce" con `+`, ## i.e.: say [+] 1, 2, 3; #=> 6 ## es equivalente a `(1+2)+3` say [*] 1..5; #=> 120 ## es equivalente a `((((1*2)*3)*4)*5)`. ## Puedes reducir con cualquier operador, no solo con operadores matemáticos. ## Por ejemplo, podrías reducir con `//` para conseguir ## el primer elemento definido de una lista: say [//] Nil, Any, False, 1, 5; #=> False # (Falsey, pero definido) ## Ejemplos con valores por defecto: say [*] (); #=> 1 say [+] (); #=> 0 # valores sin significado, dado que N*1=N y N+0=N. say [//]; #=> (Any) # No hay valor por defecto para `//`. ## También puedes invocarlo con una función de tu creación usando ## los dobles corchetes: sub add($a, $b) { $a + $b } say [[&add]] 1, 2, 3; #=> 6 ## * El meta-operador zip ## Este es un meta-operador infijo que también puede ser usado como un ## operador "normal". Toma una función binaria opcional (por defecto, solo ## crear un par), y remueve un valor de cada array e invoca su función ## binaria hasta que no tenga más elementos disponibles. Al final, returna ## un array con todos estos nuevos elementos. (1, 2) Z (3, 4); # ((1, 3), (2, 4)), dado que por defecto, la función # crea un array. 1..3 Z+ 4..6; # (5, 7, 9), usando la función personalizada infix:<+> ## Dado que `Z` tiene asociatividad de lista (ve la lista más arriba), ## puedes usarlo en más de una lista (True, False) Z|| (False, False) Z|| (False, False); # (True, False) ## Y pasa que también puedes usarlo con el meta-operador reduce: [Z||] (True, False), (False, False), (False, False); # (True, False) ## Y para terminar la lista de operadores: ## * El operador secuencia ## El operador secuencia es uno de la más poderosas características de ## Raku: Está compuesto, en la izquierda, de la lista que quieres que ## Raku use para deducir (y podría incluir una clausura), y en la derecha, ## un valor o el predicado que dice cuando parar (o Whatever para una ## lista infinita perezosa). my @list = 1, 2, 3 ... 10; # deducción básica #my @list = 1, 3, 6 ... 10; # esto muere porque Raku no puede deducir el final my @list = 1, 2, 3 ...^ 10; # como con rangos, puedes excluir el último elemento # (la iteración cuando el predicado iguala). my @list = 1, 3, 9 ... * > 30; # puedes usar un predicado # (con la Whatever Star, aquí). my @list = 1, 3, 9 ... { $_ > 30 }; # (equivalente a lo de arriba) my @fib = 1, 1, *+* ... *; # lista infinita perezosa de la serie fibonacci, # computada usando una clausura! my @fib = 1, 1, -> $a, $b { $a + $b } ... *; # (equivalene a lo de arriba) my @fib = 1, 1, { $^a + $^b } ... *; #(... también equivalene a lo de arriba) ## $a and $b siempre tomarán el valor anterior, queriendo decir que ## ellos comenzarán con $a = 1 y $b = 1 (valores que hemos asignado ## de antemano). Por lo tanto, $a = 1 y $b = 2 (resultado del anterior $a+$b), ## etc. say @fib[^10]; #=> 1 1 2 3 5 8 13 21 34 55 # (usandi un rango como el índice) ## Nota: Los elementos de un rango, una vez cosificados, no son re-calculados. ## Esta es la razón por la cual `@primes[^100]` tomará más tiempo la primera ## vez que se imprime. Después de esto, será hará en un instante. ## Estoy seguro que has estado esperando por esta parte. Bien, ahora que ## sabes algo acerca de Raku, podemos comenzar. Primeramente, tendrás ## que olvidarte acerca de "PCRE regexps" (perl-compatible regexps) ## (expresiones regulares compatible de perl). ## ## IMPORTANTE: No salte esto porque ya sabes acerca de PCRE. Son totalmente ## distintos. Algunas cosas son las mismas (como `?`, `+`, y `*`) pero ## algunas veces la semántica cambia (`|`). Asegúrate de leer esto ## cuidadosamente porque podrías trospezarte sino lo haces. ## ## Raku tiene muchas características relacionadas con RegExps. Después de ## todo, Rakudo se parsea a si mismo. Primero vamos a estudiar la sintaxis ## por si misma, después hablaremos acerca de gramáticas (parecido a PEG), ## las diferencias entre los declaradores `token`, `regex`, y `rule` y ## mucho más. ## Nota aparte: Todavía tienes acceso a los regexes PCRE usando el ## mofificador `:P5` (Sin embargo, no lo discutiremos en este tutorial). ## ## En esencia, Raku implementa PEG ("Parsing Expression Grammars") ## ("Parseado de Expresiones de Gramáticas") nativamente. El orden jerárquico ## para los parseos ambiguos es determinado por un examen multi-nivel de ## desempate: ## - La coincidencia de token más larga. `foo\s+` le gana a `foo` ## (por 2 o más posiciones) ## - El prefijo literal más largo. `food\w*` le gana a `foo\w*` (por 1) ## - Declaración desde la gramática más derivada a la menos derivada ## (las gramáticas son actualmente clases) ## - La declaración más temprana gana say so 'a' ~~ /a/; #=> True say so 'a' ~~ / a /; #=> True # ¡Más legible con los espacios! ## Nota al lector (del traductor): ## Como pudiste haber notado, he decidido traducir "match" y sus diferentes ## formas verbales como "coincidir" y sus diferentes formas. Cuando digo que ## un regex (o regexp) coincide con cierto texto, me refiero a que el regex ## describe cierto patrón dentro del texto. Por ejemplo, el regex "cencia" ## coincide con el texto "reminiscencia", lo que significa que dentro del ## texto aparece ese patrón de caracteres (una `c`, seguida de una `e`, ## (seguida de una `n`, etc.) ## En todos nuestros ejemplos, vamos a usar el operador de ## "coincidencia inteligente" contra una expresión regular ("regexp" or ## "regex" de aquí en adelante). Estamos convirtiendo el resultado usando `so`, ## pero en efecto, está devolviendo un objeto Match. Ellos saben como responder ## a la indexación de lista, indexación de hash, y devolver la cadena de ## texto coincidente. ## Los resultados de la coincidencia están disponible como `$/` (en ## ámbito implícito lexical). También puedes usar las variables de captura ## las cuales comienzan con 0: ## `$0`, `$1', `$2`... ## ## Nota que `~~` no hace un chequeo de inicio/final (es decir, ## el regexp puede coincider con solo un carácter de la cadena de texto). ## Explicaremos luego como hacerlo. ## En Raku, puedes tener un carácter alfanumérico como un literal, ## todo lo demás debe escaparse usando una barra invertida o comillas. say so 'a|b' ~~ / a '|' b /; # `True`. No sería lo mismo si no se escapara `|` say so 'a|b' ~~ / a \| b /; # `True`. Otra forma de escaparlo ## El espacio en blanco actualmente no se significa nada en un regexp, ## a menos que uses el adverbio `:s` (`:sigspace`, espacio significante). say so 'a b c' ~~ / a b c /; #=> `False`. Espacio no significa nada aquí. say so 'a b c' ~~ /:s a b c /; #=> `True`. Agregamos el modificador `:s` aquí. ## Si usamos solo un espacio entre cadenas de texto en un regexp, Raku ## nos advertirá: say so 'a b c' ~~ / a b c /; #=> 'False' # Espacio no significa nada aquí. ## Por favor usa comillas o el modificador :s (:sigspace) para suprimir ## esta advertencia, omitir el espacio, o cambiar el espaciamiento. Para ## arreglar esto y hacer los espacios menos ambiguos, usa por lo menos ## dos espacios entre las cadenas de texto o usa el adverbio `:s`. ## Como vimos anteriormente, podemos incorporar `:s` dentro de los ## delimitadores de barras. También podemos ponerlos fuera de ellos si ## especificamos `m` for `match` (coincidencia): say so 'a b c' ~~ m:s/a b c/; #=> `True` ## Al usar `m` para especificar 'match', podemos también otros delimitadore: say so 'abc' ~~ m{a b c}; #=> `True` say so 'abc' ~~ m[a b c]; #=> `True` ## Usa el adverbio :i para especificar que no debería haber distinción entre ## minúsculas y mayúsculas: say so 'ABC' ~~ m:i{a b c}; #=> `True` ## Sin embargo, es importante para como los modificadores son aplicados ## (lo cual verás más abajo)... ## Cuantificando - `?`, `+`, `*` y `**`. ## - `?` - 0 o 1 so 'ac' ~~ / a b c /; # `False` so 'ac' ~~ / a b? c /; # `True`, la "b" coincidió (apareció) 0 veces. so 'abc' ~~ / a b? c /; # `True`, la "b" coincidió 1 vez. ## ... Como debes saber, espacio en blancos son importante porque ## determinan en que parte del regexp es el objetivo del modificador: so 'def' ~~ / a b c? /; # `False`. Solamente la `c` es opcional so 'def' ~~ / a b? c /; # `False`. Espacio en blanco no es significante so 'def' ~~ / 'abc'? /; # `True`. El grupo "abc"completo es opcional. ## Aquí (y más abajo) el cuantificador aplica solamente a la `b` ## - `+` - 1 o más so 'ac' ~~ / a b+ c /; # `False`; `+` quiere por lo menos una coincidencia so 'abc' ~~ / a b+ c /; # `True`; una es suficiente so 'abbbbc' ~~ / a b+ c /; # `True`, coincidió con 4 "b"s ## - `*` - 0 o más so 'ac' ~~ / a b* c /; # `True`, todos son opcionales. so 'abc' ~~ / a b* c /; # `True` so 'abbbbc' ~~ / a b* c /; # `True` so 'aec' ~~ / a b* c /; # `False`. "b"(s) son opcionales, no reemplazables. ## - `**` - Cuantificador (sin límites) ## Si entrecierras los ojos lo suficiente, pueder ser que entiendas ## por qué la exponenciación es usada para la cantidad. so 'abc' ~~ / a b**1 c /; # `True` (exactamente una vez) so 'abc' ~~ / a b**1..3 c /; # `True` (entre una y tres veces) so 'abbbc' ~~ / a b**1..3 c /; # `True` so 'abbbbbbc' ~~ / a b**1..3 c /; # `False` (demasiado) so 'abbbbbbc' ~~ / a b**3..* c /; # `True` (rangos infinitos no son un problema) ## - `<[]>` - Clases de carácteres ## Las clases de carácteres son equivalentes a las clases `[]` de PCRE, ## pero usan una sintaxis de Raku: say 'fooa' ~~ / f <[ o a ]>+ /; #=> 'fooa' ## Puedes usar rangos: say 'aeiou' ~~ / a <[ e..w ]> /; #=> 'ae' ## Al igual que regexes normales, si quieres usar un carácter especial, ## escápalo (el último está escapando un espacio) say 'he-he !' ~~ / 'he-' <[ a..z \! \ ]> + /; #=> 'he-he !' ## Obtendrás una advertencia si pones nombres duplicados ## (lo cual tiene el efecto de capturar la frase escrita) 'he he' ~~ / <[ h e ' ' ]> /; # Advierte "Repeated characters found in characters # class" ## También puedes negarlos... (equivalenta a `[^]` en PCRE) so 'foo' ~~ / <-[ f o ]> + /; # False ## ... y componerlos: so 'foo' ~~ / <[ a..z ] - [ f o ]> + /; # False (cualquier letra excepto f y o) so 'foo' ~~ / <-[ a..z ] + [ f o ]> + /; # True (no letra excepto f and o) so 'foo!' ~~ / <-[ a..z ] + [ f o ]> + /; # True (el signo + no reemplaza la # parte de la izquierda) ## Grupo: Puedes agrupar partes de tu regexp con `[]`. ## Estos grupos *no son* capturados (como con `(?:)` en PCRE). so 'abc' ~~ / a [ b ] c /; # `True`. El agrupamiento no hace casi nada so 'foo012012bar' ~~ / foo [ '01' <[0..9]> ] + bar /; ## La línea anterior returna `True`. ## Coincidimos (o encotramos el patrón) "012" una o más de una vez ( ## (el signo `+` fue aplicado al grupo). ## Pero esto no va demasiado lejos, porque no podemos actualmente obtener ## devuelta el patrón que coincidió. ## Captura: Podemos actualmente *capturar* los resultados del regexp, ## usando paréntesis. so 'fooABCABCbar' ~~ / foo ( 'A' <[A..Z]> 'C' ) + bar /; # `True`. (usando `so` # aquí, `$/` más abajo) ## Ok. Comenzando con las explicaciones de grupos. Como dijimos, ### nuestra objeto `Match` está disponible en la variable `$/`: say $/; # Imprimirá algo extraño (explicaremos luego) o # "Nil" si nada coincidió ## Como dijimos anteriormente, un objeto Match tiene indexación de array: say $/[0]; #=> 「ABC」 「ABC」 # Estos corchetes extranos son los objetos `Match`. # Aquí, tenemos un array de ellos. say $0; # Lo mismo que lo anterior. ## Nuestra captura es `$0` porque es la primera y única captura en el ## regexp. Podrías estarte preguntando porque un array y la respuesta es ## simple: Algunas capturas (indezadas usando `$0`, `$/[0]` o una nombrada) ## será un array si y solo si puedes tener más de un elemento. ## (Así que, con `*`, `+` y `**` (cualquiera los operandos), pero no con `?`). ## Usemos algunos ejemplos para ver como funciona: ## Nota: Pusimos A B C entre comillas para demostrar que el espacio en blanco ## entre ellos no es significante. Si queremos que el espacio en blanco ## *sea* significante, podemos utilizar el modificador `:sigspace`. so 'fooABCbar' ~~ / foo ( "A" "B" "C" )? bar /; # `True` say $/[0]; #=> 「ABC」 say $0.WHAT; #=> (Match) # Puede haber más de uno, por lo tanto es solo un solo objeto match. so 'foobar' ~~ / foo ( "A" "B" "C" )? bar /; #=> True say $0.WHAT; #=> (Any) # Esta captura no coincidió, por lo tanto está vacía so 'foobar' ~~ / foo ( "A" "B" "C" ) ** 0..1 bar /; # `True` say $0.WHAT; #=> (Array) # Un cuantificador específico siempre capturará un Array, # puede ser un rango o un valor específico (hasta 1). ## Las capturas son indezadas por anidación. Esto quiere decir que un grupo ## dentro de un grup estará anidado dentro de su grupo padre: `$/[0][0]`, ## para este código: 'hello-~-world' ~~ / ( 'hello' ( <[ \- \~ ]> + ) ) 'world' /; say $/[0].Str; #=> hello~ say $/[0][0].Str; #=> ~ ## Esto se origina de un hecho bien simple: `$/` no contiene cadenas de ## texto, números enteros o arrays sino que solo contiene objetos Match. ## Estos objetos contienen los métodos `.list`, `.hash` y `.Str`. (Pero ## también puedes usar `match` para accesar un hash y `match[indice]` ## para accesar un array. say $/[0].list.perl; #=> (Match.new(...),).list # Podemos ver que es una lista de objetos Match. # Estos contienen un montón de información: dónde la # coincidencia comenzó o terminó, el "ast" # (chequea las acciones más abajo), etc. # Verás capturas nombradas más abajo con las gramáticas. ## Alternativas - el `or` de regexes ## Advertencia: Es diferente a los regexes de PCRE. so 'abc' ~~ / a [ b | y ] c /; # `True`. o "b" o "y". so 'ayc' ~~ / a [ b | y ] c /; # `True`. Obviamente suficiente... ## La diferencia entre este `|` y el otro al que estás acustombrado es LTM. ## LTM significa "Longest Token Matching", traducido libremente como ## "Coincidencia de Token Más Larga". Esto significa que el motor ("engine") ## siempre intentará coindidir tanto como sea posible en la cadena de texto. ## Básicamente, intentará el patrón más largo que concuerde con el regexp. 'foo' ~~ / fo | foo /; # `foo` porque es más largo. ## Para decidir cual parte es la "más larga", primero separa el regex en ## dos partes: ## El "prefijo declarativo" (la parte que puede ser analizada estáticamente) ## y las partes procedimentales. ## Los prefijos declarativos incluyen alternaciones (`|`), conjunciones (`&`), ## invocaciones de sub-reglas (no han sido introducidos todavía), clases de ## caracteres y cuantificadores. ## Las partes procidimentales incluyen todo lo demás: referencias a elementos ## anteriores, aserciones de código, y otras cosas que tradicionalmente no pueden ## ser representadas por regexes normales. ## ## Entonces, todas las alternativas se intentan al mismo tiempo, y la ## más larga gana. ## Ejemplos: ## DECLARATIVO | PROCEDIMENTAL / 'foo' \d+ [ || ] /; ## DECLARATIVO (grupos anidados no son un problema) / \s* [ \w & b ] [ c | d ] /; ## Sin embargo, las clausuras y la recursión (de regexes nombrados) ## son procedimentales. ## ... Hay más reglas complicadas, como la especifidad (los literales ganan ## son las clases de caracteres) + ## Nota: la primera coincidencia `or` todavía existen, pero ahora se ## deletrea `||` 'foo' ~~ / fo || foo /; # `fo` ahora. ## La subrutina `MAIN` se invoca cuando tu ejecuta un archivo de Raku ## directamente. Es realmente poderosa porque Raku actualmente parsea ## los argumentos y los pasas a la subrutina. También maneja argumentos ## nombrados (`--foo`) y hasta autogenerará un `--help`. sub MAIN($nombre) { say "¡Hola, $nombre!" } ## Esto produce: ## $ raku cli.pl ## Uso: ## t.pl ## Y dado que una subrutina regular en Raku, puedes tener múltiples ## despachos: ## (usando un "Bool" por un argumento nombrado para que podamos hacer ## `--replace` a cambio de `--replace=1`) subset File of Str where *.IO.d; # convierte a un objeto IO para chequear si # un archivo existe multi MAIN('add', $key, $value, Bool :$replace) { ... } multi MAIN('remove', $key) { ... } multi MAIN('import', File, Str :$as) { ... } # omitiendo parámetros nombrados ## Esto produce: ## $ raku cli.pl ## Uso: ## t.pl [--replace] add ## t.pl remove ## t.pl [--as=] import (File) ## Como puedes ver, esto es *realmente* poderoso. ## Fue tan lejos como para mostrar las constantes en líneas. ## (el tipo solo se muestra cuando el argumento `$`/ es nombrado) ## Consideramos que por ahora ya sabes lo básico de Raku. ## Esta sección es solo para listar algunas operaciones comunes ## las cuales no están en la "parte principal" del tutorial. ## Operadores ## * Comparación para ordenar ## Ellos returnan un valor de los enum `Order`: `Less`, `Same` y `More` ## (los cuales representan los números -1, 0 o +1). 1 <=> 4; # comparación de orden para caracteres numéricos 'a' leg 'b'; # comparación de orden para cadenas de texto $obj eqv $obj2; # comparación de orden usando la semántica eqv ## * Ordenación genérica 3 before 4; # True 'b' after 'a'; # True ## * Operador (por defecto) de circuito corto ## Al igual que `or` y `||`, pero devuelve el primer valor *defined* ## (definido): say Any // Nil // 0 // 5; #=> 0 ## * Circuito corto exclusivo or (XOR) ## Devuelve `True` si uno (y solo uno) de sus argumentos es verdadero: say True ^^ False; #=> True ## * Flip Flop ## Los operadores flip flop (`ff` y `fff`, equivalente a `..`/`...` en P5) ## son operadores que toman dos predicados para evalualarlos: ## Ellos son `False` hasta que su lado izquierdo devuelve `True`, entonces ## son `True` hasta que su lado derecho devuelve `True`. ## Como los rangos, tu puedes excluir la iteración cuando se convierte en ## `True`/`False` usando `^` en cualquier lado. ## Comencemos con un ejemplo: for { # por defecto, `ff`/`fff` hace coincidencia inteligente (`~~`) contra `$_`: if 'met' ^ff 'meet' { # no entrará el bucle if por "met" # (se explica más abajo). .say } if rand == 0 ff rand == 1 { # compara variables más que `$_` say "Esto ... probablemente nunca se ejecutará ..."; } } ## Esto imprimirá "young hero we shall meet" (exluyendo "met"): ## el flip-flop comenzará devolviendo `True` cuando primero encuentra "met" ## (pero no returnará `False` por "met" dabido al `^` al frente de `ff`), ## hasta que ve "meet", lo cual es cuando comenzará devolviendo `False`. ## La diferencia entre `ff` (al estilo de awk) y `fff` (al estilo de sed) ## es que `ff` probará su lado derecho cuando su lado izquierdo cambia ## a `True`, y puede returnar a `False` inmediamente (*excepto* que será ## `True` por la iteración con la cual coincidió). Por lo contrario, ## `fff` esperará por la próxima iteración para intentar su lado ## derecho, una vez que su lado izquierdo ha cambiado: .say if 'B' ff 'B' for ; #=> B B # porque el lado derecho se puso a prueba # directamente (y returnó `True`). # Las "B"s se imprimen dadó que coincidió # en ese momento (returnó a `False` # inmediatamente). .say if 'B' fff 'B' for ; #=> B C B # El lado derecho no se puso a prueba # hasta que `$_` se convirtió en "C" # (y por lo tanto no coincidió # inmediamente). ## Un flip-flop puede cambiar estado cuantas veces se necesite: for { .say if $_ eq 'start' ^ff^ $_ eq 'stop'; # excluye a "start" y "stop", #=> "print it print again" } ## También podrías usar una Whatever Star, lo cual es equivalente ## a `True` para el lado izquierdo o `False` para el lado derecho: for (1, 3, 60, 3, 40, 60) { # Nota: los paréntesis son superfluos aquí # (algunas veces se les llaman "paréntesis superticiosos") .say if $_ > 50 ff *; # Una vez el flip-flop alcanza un número mayor que 50, # no returnará jamás a `False` #=> 60 3 40 60 } ## También puedes usar esta propiedad para crear un `If` ## que no pasará la primera vez: for { .say if * ^ff *; # el flip-flop es `True` y nunca returna a `False`, # pero el `^` lo hace *que no se ejecute* en la # primera iteración #=> b c } ## - `===` es la identidad de valor y usa `.WHICH` ## en los objetos para compararlos. ## - `=:=` es la identidad de contenedor y usa `VAR()` ## en los objetos para compararlos.