Capítulo 05: Listas, Tuplas e o truco ninja das Secuencias
Un programa que só move un número ou un texto á vez serve de pouco. Na vida real manexas listas de ficheiros, filas dunha base de datos, paquetes de rede ou coordenadas.
Para iso, Ridgun dáche tres envases distintos, cada un pensado para un traballo moi concreto:
- Listas (
[T]): "Teño moitos elementos do mesmo tipo (ou ningún) e quero percorrelos". - Tuplas (
(A, B)): "Quero devolver un par ou un trío de cousas distintas xuntas sen crear un tipo novo". - Secuencias e máscaras (
s(_ + _),(+ _ _ +)): "Quero filtrar ou reordenar argumentos ao voo coma se tivese un coador matemático".
Imos unha por unha.
5.1. Listas ([T]): O pan de cada día
En Ridgun, se pos corchetes arredor dun tipo, tes unha lista dese tipo:
[Int]é unha lista de enteiros:[10, 20, 30].[String]é unha lista de textos:["maza", "pera"].[]é a lista baleira (o comodín que encaixa con calquera[T]).
"Ouve, podo meter un número e un texto xuntos na mesma lista tipo
[1, "dous"]?" Tal cal, non (e dálle as grazas ao compilador por impedircho, porque así nunca che rebentará unha suma en produción ao atoparse un texto sorpresa). No Capítulo 06 veremos como resolver o caso de[1, "dous"]con elegancia total usando un Tipo Alxébrico (igual que fai Haskell).
Como se anaquea unha lista sen usar índices i, j, k: [] e (x:xs)
Nas linguaxes imperativas, percorrer unha lista adoita implicar for (int i = 0; i < lista.size(); i++), co clásico risco de pasarte por un (off-by-one).
En Ridgun non contas índices; desmontas a lista pola cabeza:
[]: encaixa se a lista está baleira.(x:xs): encaixa se a lista ten polo menos un elemento. Mete o primeiro elemento enx(a cabeza) e o resto da lista enxs(a cola).
Mira que gusto dá sumar unha lista ou contar cantos elementos ten:
sumar lista [Int] => Int
sumar lista [] = 0
sumar lista (x:xs) = x + (sumar lista xs)
contar elementos [A] => Int
contar elementos [] = 0
contar elementos (_:xs) = 1 + (contar elementos xs)
E un detalle técnico que che alegrará o día: en Ridgun, sacar a cola xs en (x:xs) non copia o resto da lista en memoria. Internamente é unha vista sobre o mesmo vector que só avanza o punteiro de inicio en 1 ($O(1)$ instantáneo).
Podes destapar varios elementos de golpe
O patrón : dá moitísimo xogo cando estás analizando comandos ou protocolos:
interpretar comando [String] => String
interpretar comando [] = "Non escribiches nada"
interpretar comando ("axuda":_) = "Mostrando a axuda..."
interpretar comando ("copiar":orixe:dest:[]) = "Copiando de {orixe} a {dest}"
interpretar comando (cmd:_) = "Comando descoñecido: {cmd}"
Mira a terceira fila: ("copiar":orixe:dest:[]) comproba dun só golpe que a lista teña exactamente 3 elementos, que o primeiro sexa "copiar", e déixache o segundo e o terceiro servidos en bandexa en orixe e dest. Intenta escribir iso en C ou Java e verás cantas liñas de args.length == 3 && args[0].equals("copiar") che saen.
Unir listas con dobres dous puntos (::)
Cando estás no lado dereito do = (nunha expresión) e queres pegar dúas listas, usas :::
pegar listas [Int] [Int] => [Int]
pegar listas a b = a :: b
engadir detras [Int] Int => [Int]
engadir detras xs x = xs :: [x]
(Regra mnemotécnica rápida: un só : nos patróns desmonta a lista; dous :: nas expresións pegan dúas listas).
E se de verdade queres sacar o elemento número 5 directamente sen percorrer nada? Usa corchetes pegados ao nome: mina_lista[5].
5.2. Funcións variádicas (T...): "Pásame todos os que queiras!"
Ás veces queres que quen chame á túa función poida escribir maximo de 4 19 2 88 7 comodamente, sen obrigalo a envolver os números entre corchetes [4, 19, 2, 88, 7].
Pon tres puntos ... detrás do último tipo da túa sinatura, e dentro da túa función recibirálo automaticamente empaquetado coma unha lista:
sumar varios Int... => Int
sumar varios nums = sumar lista nums
main => ()
main =
echo (sumar varios 10 20 30) -- Imprime 60
echo (sumar varios 1 2 3 4 5 6 7) -- Imprime 28
5.3. Tuplas: Cando unha función quere devolver máis dunha cousa
En moitas linguaxes devolver dúas cousas desde unha función é unha dor: ou creas unha clase auxiliar parva ResultadoConPosicion, ou pasas punteiros de saída por referencia.
En Ridgun devolves unha tupla (A, B):
dividir e resto Int Int => (Int, Int)
dividir e resto a b = (a / b, a % b)
Mesmo podes aforrar os parénteses na liña da sinatura se che resulta máis limpo:
dividir e resto Int Int => Int Int.
Como abrir unha tupla
Igual que a creas con (a, b), ábrela nos argumentos de calquera función con (cociente, resto):
explicar division (Int, Int) => String
explicar division (cociente, resto) = "Cabe a {cociente} e sobran {resto}"
O detalle estrela: Desempaquetado automático entre funcións
Supón que tes dividir e resto (que devolve unha tupla de 2 enteiros) e outra función sumar par Int Int => Int (que agarda 2 enteiros soltos). En Ridgun non tes que abrir a tupla á man nunha variable intermedia; podes enchufar unha función dentro da outra directamente:
sumar par Int Int => Int
sumar par x y = x + y
main => ()
main =
-- dividir e resto 17 5 devolve (3, 2), e sumar par recibe 3 e 2 directamente:
echo (sumar par (dividir e resto 17 5)) -- Imprime 5
5.4. O truco ninja: Secuencias e Máscaras Posicionais (s(_ + _), (+ _ _ +))
Aquí vén unha das xoias máis orixinais de Ridgun, herdada directamente das linguaxes vectoriais como J e APL.
Imaxina que recibes unha restra de argumentos co patrón de secuencia (s) e queres quedar só con algúns e descartar outros sen declarar cinco variables con nomes inventados (_a, b, _c, d...).
Aplícaslle a s unha máscara de tupla con + (queda con el) e _ (descártao):
s(_ + _): de 3 elementos, descarta o primeiro (_), queda co segundo (+) e descarta o terceiro (_).s(+ _ +): queda co primeiro e o terceiro; devolve a secuencia con eses dous.s(_ _ +...): descarta os dous primeiros (_ _) e aplica+...a todos os demais que veñan detrás, sexan cantos sexan.
Mírao funcionando:
o do medio Int Int Int => Int
o do medio (s) = s(_ + _)
descartar dous primeiros Int... => [Int]
descartar dous primeiros (s) = s(_ _ +...)
main => ()
main =
echo (o do medio 100 42 999) -- Imprime 42
echo (descartar dous primeiros 1 2 10 20 30) -- Imprime [10, 20, 30]
E o mellor de todo: podes intercalar unha máscara posicional (+ _ _ +) directamente no medio dunha tubaxe ->! Se unha función devolve unha tupla de varios valores e a seguinte función só precisa dous deles, filtras a tupla ao voo e Ridgun desempaquetará os elementos superviventes como argumentos separados da seguinte etapa:
-- parsear paquete devolve 4 valores: (Int, Int, [String String], String)
-- (+ _ _ +) queda co 1º (code) e o 4º (msg), e pásaollos a combinar codigo:
parsear paquete "v0.9.9" -> (+ _ _ +) -> combinar codigo -> resumo
Ademais, se usas o operador coma , fóra de parénteses (lista1 , lista2), Ridgun aplana ambas coleccións nunha única secuencia continua.
No Capítulo 06 imos dar o paso que converte a un programador funcional nun arquitecto de primeira: crear os teus propios Tipos de Datos Alxébricos (ADTs), escribir funcións polimórficas con A..Z e fabricar os teus propios operadores á medida.