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.

Última actualización: 10 min de lectura Editar en GitHub

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.

AntrilRun
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"));
	}
}
Terminal
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:

Antril
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.

AntrilRun
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));
	}
}
Terminal
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:

AntrilRun
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();
}
Terminal
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.

AntrilRun
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]));
	}
}
Terminal
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.
AntrilRun
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);
}
Terminal
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.

AntrilRun
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));
}
Terminal
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).

AntrilRun
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);
}
Terminal
larva run
400
true
4
400
  • left op ++(T x) es un operador unario prefijo (++a) y right op !(T x) uno postfijo (a!); sin left ni right, un operador unario funciona de ambos lados.
  • op (T)(S s) declara una conversión de S a T.
  • Los niveles de PrecedenceLevel van de Assignment a Power; 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.

AntrilRun
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());
	}
}
Terminal
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.

¿Te resultó útil esta página?

No se guarda ninguna dirección; tu nombre de usuario sí, si iniciaste sesión.

Esc