Un recorrido por el lenguaje en doce programas. Todos se compilan y se ejecutan en la verificación que publica esta página, así que lo que lees acá es lo que el compilador del pie acepta hoy.
Variables y tipos
let liga una sola vez, var se puede reasignar, const es una constante de compilación. Los tipos van después del nombre; el lenguaje es de tipado gradual, pero anotar es lo que mantiene honesto al compilador — y, como verás con los arrays, a veces es lo que lo mantiene correcto.
fn main() { let name: String = "Nyx" // inmutable var counter: int = 0 // mutable const MAX: int = 100 // constante counter = counter + 1 print(name) print(counter) print(MAX) }
Funciones
Los parámetros llevan tipo, el tipo de retorno va después de ->, y una función sin tipo de retorno no devuelve nada.
fn add(a: int, b: int) -> int { return a + b } fn greet(name: String) -> String { return "Hola, " + name + "!" } fn main() { print(add(3, 4)) print(greet("mundo")) }
Control de flujo
if/else, while, y for sobre un rango o sobre cualquier cosa iterable. Sin paréntesis alrededor de la condición; las llaves no son opcionales.
fn main() { let x: int = 10 if x > 5 { print("mayor") } else { print("menor o igual") } var i: int = 0 while i < 3 { print(i) i = i + 1 } for j in 0..3 { print(j) } }
Arrays
Los arrays crecen con push, se recorren con for, y se encadenan con iter(). El lambda que recibe filter es una función tipada común y corriente, sin nombre.
fn main() { var nums: Array = [1, 2, 3, 4, 5] nums.push(6) print(nums.length()) for n in nums { print(n) } let evens: Array = nums.iter() .filter(fn(x: int) -> bool { return x % 2 == 0 }) .collect() print(evens.length()) }
Maps
insert, get, has, size, y keys() para recorrerlos.
fn main() { var capitales: Map = Map.new() capitales.insert("Argentina", "Buenos Aires") capitales.insert("Francia", "Paris") print(capitales.size()) // 2 print(capitales.get("Francia")) // Paris print(capitales.has("Brasil")) // false let ks: Array = capitales.keys() var i: int = 0 while i < ks.length() { let k: String = ks[i] print(k + " -> " + capitales.get(k)) i = i + 1 } }
Fíjate en let k: String = ks[i] en vez de usar ks[i] directo. Un elemento leído de un Array sin anotación de tipo se trata como int — y eso, pasado a get, no es un error de compilación sino la búsqueda equivocada. Anota el elemento; cuesta una línea y elimina toda una familia de bugs confusos.
Los strings son bytes
length(), substring(), indexOf() y charAt() cuentan bytes, no caracteres. Para caracteres — codepoints UTF-8 — está char_length(). El resto es lo que esperarías: trim(), split(), contains(), toUpper(), toLower(), e interpolación ${…} dentro de un literal.
fn main() { let texto: String = " café latte " let limpio: String = texto.trim() print(limpio.length()) // 11 — BYTES: la é ocupa dos print(limpio.char_length()) // 10 — codepoints UTF-8 print(limpio.substring(0, 3)) // "caf" — substring también corta bytes print(limpio.contains("latte")) let partes: Array = limpio.split(" ") print(partes.length()) // 2 let lang: String = "Nyx" print("lenguaje: ${lang}") }
Structs
Los campos llevan tipo; los métodos viven en un bloque impl y reciben self.
struct Point { x: int, y: int } impl Point { fn distance_sq(self) -> int { return self.x * self.x + self.y * self.y } } fn main() { let p: Point = Point { x: 3, y: 4 } print(p.distance_sq()) // 25 }
Enums y match
Una variante puede llevar datos, y match tiene que cubrirlas todas. El separador es un punto — Shape.Circle — nunca dos puntos dobles; ese error es tan común que nyx vet tiene un aviso para él.
enum Shape { Circle(int), Rect(int, int), Empty } fn area(s: Shape) -> int { return match s { Shape.Circle(r) => 3 * r * r, Shape.Rect(w, h) => w * h, Shape.Empty => 0 } } fn main() { print(area(Shape.Circle(5))) print(area(Shape.Rect(3, 7))) print(area(Shape.Empty)) }
Option
La ausencia es un valor, no un nulo. match maneja los dos casos, y if let es la forma corta cuando solo te importa uno.
fn first_positive(nums: Array, index: int) -> Option<int> { let val: int = nums[index] if val > 0 { return Option.Some(val) } return Option.None } fn describe(opt: Option<int>) -> String { return match opt { Option.Some(v) => "encontrado: " + int_to_string(v), Option.None => "nada" } } fn main() { let nums: Array = [10, -5, 3] print(describe(first_positive(nums, 0))) print(describe(first_positive(nums, 1))) if let Option.Some(v) = first_positive(nums, 2) { print("if let: " + int_to_string(v)) } else { print("if let: nada") } }
Result y el operador ?
El fallo también es un valor. ? desenvuelve el caso exitoso y devuelve el error a quien llamó, así una cadena de pasos que pueden fallar se lee como una cadena de pasos comunes.
enum Result { Ok(int), Err(String) } fn parse_positive(s: String) -> Result { let n: int = string_to_int(s) if n <= 0 { return Result.Err("no es positivo: " + s) } return Result.Ok(n) } fn double_it(n: int) -> Result { if n > 1000 { return Result.Err("demasiado grande") } return Result.Ok(n * 2) } // ? le devuelve el Err a quien llamó, sin match explícito fn pipeline(s: String) -> Result { let n: int = parse_positive(s)? let d: int = double_it(n)? return Result.Ok(d + 1) } fn show(r: Result) { match r { Result.Ok(v) => print("ok: " + int_to_string(v)), Result.Err(e) => print("err: " + e) } } fn main() { show(pipeline("42")) show(pipeline("-5")) show(pipeline("2000")) }
Traits
Un trait es un conjunto de métodos que un tipo promete tener. dyn Trait como tipo de parámetro acepta cualquier tipo que lo implemente.
trait Describable { fn describe(self) -> String } struct Cat { name: String } impl Describable for Cat { fn describe(self) -> String { return self.name + " el gato" } } fn print_desc(d: dyn Describable) { print(d.describe()) } fn main() { let c: Cat = Cat { name: "Bigotes" } print_desc(c) }
Closures
Una función definida dentro de otra captura su entorno, y Fn es el tipo de un valor función — se puede devolver, guardar y pasar como argumento.
fn make_adder(n: int) -> Fn { fn add(x: int) -> int { return n + x } return add } fn apply(f: Fn, value: int) -> int { return f(value) } fn main() { let add5: Fn = make_adder(5) print(add5(3)) print(apply(add5, 10)) let nums: Array = [1, 2, 3, 4, 5, 6] let evens: Array = nums.iter() .filter(fn(x: int) -> bool { return x % 2 == 0 }) .collect() print(evens.length()) }
A dónde ir después
Esta era la forma del lenguaje. Lo que puede hacer la biblioteca estándar — servidores y clientes HTTP, JSON, TOML, CSV, SQLite, sockets, TLS, hilos y canales, criptografía, fechas — es otra pregunta, y se responde con ejemplos resueltos en vez de con prosa.
Nyx por ejemploAlrededor de cien recetas, desde el hola mundo hasta un servidor HTTP. Lee una, cópiala, cámbiala.Abrir →