Funciones y genéricos
Funciones y métodos, sobrecargas, lambdas y tipos de función, genéricos y restricciones, operadores y bloques de extensión en Antril.
En esta página
Funciones y métodos
Una función se declara con su nombre, sus parámetros y, después de :, su resultado. Si no tiene resultado, no devuelve nada. Las funciones pueden vivir en un espacio de nombres (funciones libres) o en un tipo (métodos); ambas se escriben de la misma forma.
import System;
into namespace Math2
{
Square(i32 x) : i32
{
return x * x;
}
// A body that is a single expression.
Cube(i32 x) : i32 => x * x * x;
Report(str label, i32 value)
{
Console.WriteLine(label + ": " + value);
}
// Overloads: same name, different parameters.
Describe(i32 x) : str => "the number " + x;
Describe(str s) : str => "the text " + s;
Main()
{
Report("square", Square(4));
Report("cube", Cube(3));
Console.WriteLine(Describe(7));
Console.WriteLine(Describe("seven"));
}
}
larva run
square: 16
cube: 27
the number 7
the text seven
Una sobrecarga se elige por la cantidad de argumentos y luego por sus tipos. Dos sobrecargas con los mismos parámetros, o una llamada que encaja igual de bien en dos, son errores.
Antril no tiene valores predeterminados para los parámetros ni argumentos con nombre; en su lugar, escribe una sobrecarga:
Greet(str name, str greeting) : str => greeting + ", " + name;
Greet(str name) : str => Greet(name, "Hello");
Lambdas y tipos de función
func<A, B> : R es el tipo de una función que recibe un A y un B y devuelve un R. func : R no recibe nada; sin : R no devuelve nada (func<str>).
Una lambda escribe una función en el lugar donde se usa. Los tipos de sus parámetros vienen del tipo func al que se asigna, o se escriben explícitamente. Cualquier función, método estático u object.Method también es un valor de función.
import System;
into namespace Funcs
{
Twice(i32 x) : i32 => x * 2;
Apply(func<i32> : i32 f, i32 x) : i32 => f(x);
Map<T, U>(List<T> items, func<T> : U f) : List<U>
{
List<U> result = new List<U>();
loop (T item in items)
result.Add(f(item));
return result;
}
Main()
{
Console.WriteLine(Apply(Twice, 21));
func<i32, i32> : i32 add = (a, b) => a + b;
Console.WriteLine(add(2, 3));
i32 offset = 10;
func<i32> : i32 shift = x => x + offset;
Console.WriteLine(shift(5));
func<str> show = (str text) => { Console.WriteLine("> " + text); };
show("hi");
List<i32> numbers = new List<i32>();
numbers.Add(1);
numbers.Add(2);
List<str> labels = Map(numbers, n => "#" + n);
Console.WriteLine(String.Join(", ", labels));
}
}
larva run
42
5
15
> hi
#1, #2
Una lambda captura las variables que usa: las ve tal como están cuando se ejecuta, y asignar una dentro de la lambda cambia la variable. Dentro de un método de instancia, una lambda ve el objeto y sus miembros igual que el método.
Las colecciones también reciben lambdas: List<T> tiene Where, Map, Any, All, FindIndex, ForEach, RemoveAll y Sort(compare).
Iteradores
Una función que devuelve Iterator<T> puede entregar sus valores de uno en uno con yield. Cada vuelta de un loop sobre ella ejecuta la función hasta su siguiente yield:
import System;
Squares(i32 count) : Iterator<i32>
{
loop (i32 i = 1..=count)
yield i * i;
}
Main()
{
loop (i32 x in Squares(4))
Console.Write(x + " ");
Console.WriteLine();
}
larva run
1 4 9 16
Un iterador se recorre una sola vez; vuelve a llamar a la función para empezar de nuevo.
Genéricos
Los tipos y las funciones reciben parámetros de tipo entre <…>. Por lo general, los argumentos se infieren de la llamada.
import System;
into namespace Generic
{
public class Stack<T>
{
List<T> items = new List<T>();
public:
Push(T item) { items.Add(item); }
Pop() : T => items.Pop();
i32 Count { get => items.Count; }
}
Largest<T : IComparable<T>>(T a, T b) : T => a.CompareTo(b) >= 0 ? a : b;
Sum<T : number>(T[] values) : T
{
T total = T.Zero;
loop (T v in values)
total = total + v;
return total;
}
Main()
{
Stack<str> stack = new Stack<str>();
stack.Push("a");
stack.Push("b");
Console.WriteLine(stack.Pop() + " " + stack.Count);
Console.WriteLine(Largest("pear", "apple"));
Console.WriteLine(Largest(3, 7));
Console.WriteLine(Sum([1, 2, 3]));
Console.WriteLine(Sum([1.5, 2.25]));
}
}
larva run
b 1
pear
7
6
3.75
Restricciones
Una restricción después de : indica lo que un argumento de tipo debe ser o tener. Si hay varias, se separan con comas.
| Restricción | El argumento de tipo debe… |
|---|---|
T : IShape, T : IComparable<T> |
implementar la interfaz (o derivar de la clase). |
T : number |
ser un número: + - * / % == <, T.Zero, T.One (System.INumber<T>). |
T : snumber |
ser un número con signo: también -x y T.NegativeOne (System.ISignedNumber<T>). |
T : [u8, u16, u32] |
ser uno de los tipos de la lista. |
T : int, uint, float, numeric |
pertenecer a ese grupo de tipos primitivos. |
T : class, T : value |
ser un tipo de objeto, o un primitivo o un value. |
T : new(), T : new(i32, str) |
tener un constructor con esos parámetros, para que new T(…) funcione. |
T : copy, T : plain |
ser copiable; ser un tipo cuyo cero es un valor válido (para que default(T) funcione). |
T : op +(T, T) : T |
tener ese operador. |
T : (i64)T |
convertirse a i64 con una conversión explícita. |
T : exception, T : attribute |
ser una clase de excepción o de atributo. |
T : bitflag enum |
ser un enum de banderas de bits. |
import System;
public class Thing
{
public:
i32 Id = 7;
}
Twice<T : op +(T, T) : T>(T x) : T => x + x;
Make<T : new()>() : T => new T();
Main()
{
Console.WriteLine(Twice(21));
Console.WriteLine(Twice(1.25));
Console.WriteLine(Make<Thing>().Id);
}
larva run
42
2.5
7
Math está escrita con estas restricciones: Math.Max, Math.Abs, Math.Sqrt y las demás funcionan con cualquier tipo numérico, incluido tu propio value que implemente INumber.
Genéricos variádicos
El último parámetro de tipo puede ser un paquete de tipos, T...: acepta cualquier cantidad de tipos. static loop (E in T) repite una instrucción para cada tipo del paquete, en tiempo de compilación.
import System;
CountArguments<T...>(T values) : i32
{
i32 n = 0;
static loop (E in T)
n++;
return n;
}
Main()
{
Console.WriteLine(CountArguments(1, "two", 3.0));
}
larva run
3
Así es como System.ValueTuple y System.Tuple admiten cualquier cantidad de elementos.
Operadores
Las clases y los valores declaran operadores con op. Un símbolo puede ser uno de los habituales (+, ==…) o una secuencia nueva de los caracteres + - * / % & | ^ ~ ! < > = ? @ # $ .. Un símbolo binario nuevo indica con qué fuerza se asocia mediante precedence(PrecedenceLevel.X) y, opcionalmente, associativity(Associativity.Right).
import System;
public value Money
{
public:
i64 Cents;
new(i64 Cents);
op +(Money a, Money b) : Money => new Money(a.Cents + b.Cents);
op ==(Money a, Money b) : bool => a.Cents == b.Cents;
op <+>(Money a, Money b) : Money precedence(PrecedenceLevel.Additive) => new Money(a.Cents * 2 + b.Cents);
op (i64)(Money m) => m.Cents;
}
Main()
{
Money total = new Money(150) + new Money(250);
Console.WriteLine(total.Cents);
Console.WriteLine(total == new Money(400));
Console.WriteLine((new Money(1) <+> new Money(2)).Cents);
Console.WriteLine((i64)total);
}
larva run
400
true
4
400
left op ++(T x)es un operador unario prefijo (++a) yright op !(T x)uno postfijo (a!); sinleftniright, un operador unario funciona de ambos lados.op (T)(S s)declara una conversión deSaT.- Los niveles de
PrecedenceLevelvan deAssignmentaPower; un nivel más alto se asocia con más fuerza.
Bloques de extensión
ext(T name) { … } agrega métodos a un tipo existente, incluidos los primitivos y las interfaces. Dentro del bloque, name es el valor sobre el que se llama al método. static ext(T) agrega métodos estáticos al propio tipo, y ext<T : …>(T name) extiende todos los tipos que cumplen una restricción.
import System;
into namespace Extras
{
ext(i32 n)
{
public:
IsEven() : bool => n % 2 == 0;
}
ext(str s)
{
public:
Shout() : str => s.ToUpper() + "!";
}
static ext(i32)
{
public:
Answer() : i32 => 42;
}
Main()
{
i32 n = 4;
Console.WriteLine(n.IsEven());
Console.WriteLine("hello".Shout());
Console.WriteLine(i32.Answer());
}
}
larva run
true
HELLO!
42
Un bloque de extensión no tiene campos. La biblioteca estándar usa uno, ext<T : bitflag enum>(T flags), para darle a todo enum de banderas de bits HasFlag, With, Without y Toggle.
Siguiente: Errores y excepciones.
Editar esta página en GitHub Reportar un problema