Tipos

Clases, valores, propiedades, herencia, interfaces, enums, registros, tuplas y tipos anulables en Antril.

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

En esta página

Antril tiene estas categorías de tipos:

Categoría Qué es
class Un tipo referencia: las variables comparten un mismo objeto. Una clase base y cualquier cantidad de interfaces.
base class Una clase que no se puede crear directamente, solo derivar.
value Un tipo valor: asignarlo o pasarlo lo copia. Implementa interfaces, no tiene clase base.
record class, record value Campos fijados por el constructor, igualdad por contenido, copias con with.
interface Métodos y propiedades que un tipo promete, con cuerpos predeterminados opcionales.
enum, bitflag enum Un conjunto cerrado de valores con nombre; los flags se combinan con |.
exception, attribute Clases derivadas de System.Exception y System.Attribute (consulta Errores).

Clases

AntrilRun
import System;

into namespace Bank
{
	public class Account
	{
		public:
			new(str owner, i32 start)
			{
				this.owner = owner;
				balance = start;
				Opened += 1;
			}

			str Owner { get => owner; }
			i32 Balance { get { return balance; } private set { balance = value; } }
			static i32 Opened { get; set; }

			Deposit(i32 amount)
			{
				Balance = balance + amount;
			}

		private:
			str owner;
			i32 balance;
	}

	Main()
	{
		Account a = new Account("Ana", 10);
		a.Deposit(5);
		Account b = a;
		b.Deposit(1);
		Console.WriteLine(a.Owner + " has " + a.Balance);
		Console.WriteLine(Account.Opened + " account opened");
	}
}
Terminal
larva run
Ana has 16
1 account opened

b = a copia la referencia: a y b son la misma cuenta.

Secciones de acceso

Los miembros se agrupan bajo etiquetas de acceso: public:, private:, internal:, inherited:, friendly: y file:. Una etiqueta se aplica hasta la siguiente o hasta el final del bloque, y se puede repetir. Un modificador escrito en una declaración individual tiene prioridad sobre la etiqueta.

Por convención, la etiqueta va una tabulación más adentro que su bloque, y sus miembros una tabulación más; de lo contrario, el compilador emite una advertencia.

Acceso Visible desde
public Cualquier lugar, también otras bibliotecas.
private El propio tipo y sus tipos anidados. Es el valor predeterminado para los miembros.
internal La misma biblioteca o programa. Es el valor predeterminado para los tipos de nivel superior.
inherited El tipo y los tipos derivados de él.
friendly El tipo y sus amigos, declarados con [FriendOf(Other)] (la amistad es mutua).
file El mismo archivo fuente.

Los miembros de interfaces, los operadores y los valores de enums siempre son públicos. Un proyecto puede cambiar el acceso predeterminado de las declaraciones que no indican ninguno con access.default en su archivo .queen.

Campos y constructores

Los campos se declaran como variables. Los campos de tipos valor empiezan en cero; un campo readonly solo se asigna en el constructor.

Un constructor es new(…). Una clase que no tiene ninguno recibe uno vacío. Los constructores se pueden sobrecargar, y las formas de abajo ahorran escritura:

AntrilRun
import System;

into namespace Geometry
{
	public value Size
	{
		public:
			i32 Width;
			i32 Height;

			// No body: each parameter is stored in the field of the same name.
			new(i32 Width, i32 Height);

			// Calls the other constructor.
			new(i32 side) : (side, side);

			i32 Area { get => Width * Height; }
	}

	public class Label
	{
		readonly str text;

		public:
			new(str text) { this.text = text; }
			override ToString() : str => "[" + text + "]";
	}

	Main()
	{
		Size square = new Size(4);
		Console.WriteLine(square.Area);
		Console.WriteLine(new Label("ok"));
	}
}
Terminal
larva run
16
[ok]

Una clase puede tener un destructor, destroy(). Se ejecuta una vez, cuando el objeto deja de existir; cuándo ocurre eso depende de cómo el programa gestiona la memoria (consulta Memoria).

Propiedades

Forma Qué es
i32 Count { get; set; } Un campo que se lee y se escribe como propiedad.
i32 Count { get; private set; } Lo mismo, pero solo el propio tipo lo asigna.
i32 Area { get => w * h; } Se calcula cada vez que se lee.
i32 Balance { get { … } private set { … } } Cuerpos para cada descriptor de acceso; dentro de set, el nuevo valor es value. Un descriptor de acceso puede ser más restringido que la propiedad.

Miembros estáticos y clases estáticas

Los campos, propiedades y métodos static pertenecen al tipo: Account.Opened. Una static class solo tiene miembros estáticos (sus miembros son estáticos sin necesidad de indicarlo) y no se puede usar como tipo de una variable:

Antril
public static class Settings
{
	public:
		static i32 Retries = 3;
		Describe() : str => "retries: " + Retries;
}

Los tipos se pueden anidar dentro de otros tipos; desde fuera se nombran como Outer.Inner.

Valores

Un value se copia cuando se asigna o se pasa. Sus métodos trabajan sobre el original cuando se llaman sobre una variable.

AntrilRun
import System;

public value Point
{
	public:
		i32 X;
		i32 Y;
		new(i32 X, i32 Y);
		Length2() : i32 => X * X + Y * Y;
		Move(i32 dx) { X = X + dx; }
}

Main()
{
	Point p = new Point(3, 4);
	Point q = p;
	q.X = 0;
	p.Move(1);
	Console.WriteLine(p.Length2() + " " + q.Length2());
}
Terminal
larva run
32 16

Herencia e interfaces

Una clase deriva de una sola clase base e implementa cualquier cantidad de interfaces: class Circle : Shape, IDrawable.

  • Los métodos no son virtuales a menos que lo indiquen. virtual permite que las clases derivadas reemplacen un método, y override lo reemplaza con la misma firma.
  • En una base class, un método sin cuerpo debe escribirlo cada clase derivada.
  • : base(args) después de los parámetros de un constructor llama al constructor de la clase base.
  • De una final class no se puede derivar; final override impide que un método se siga reemplazando.
  • Una interfaz enumera métodos y propiedades. Puede dar un cuerpo predeterminado, que los tipos que la implementan heredan.
AntrilRun
import System;

into namespace Shapes
{
	public interface IShape
	{
		Area() : f64;
		Describe() : str => "a shape of area " + Area();
	}

	public base class Shape : IShape
	{
		public:
			Area() : f64;
			virtual Name() : str => "shape";
	}

	public class Circle : Shape
	{
		f64 radius;

		public:
			new(f64 radius) { this.radius = radius; }
			override Area() : f64 => 3.14 * radius * radius;
			override Name() : str => "circle";
	}

	public final class Square : Shape
	{
		f64 side;

		public:
			new(f64 side) { this.side = side; }
			override Area() : f64 => side * side;
	}

	Main()
	{
		List<Shape> shapes = new List<Shape>();
		shapes.Add(new Circle(1));
		shapes.Add(new Square(2));
		loop (Shape s in shapes)
			Console.WriteLine(s.Name() + ": " + s.Area());

		IShape i = new Square(3);
		Console.WriteLine(i.Describe());

		Shape first = shapes[0];
		if (first is Circle c)
			Console.WriteLine("a circle of area " + c.Area());
		Square? square = first as Square;
		Console.WriteLine(square == null);
	}
}
Terminal
larva run
circle: 3.14
shape: 4
a shape of area 9
a circle of area 3.14
true

x is T comprueba un tipo, y x is T name además da el valor con ese tipo. x as T da un T?, que es null cuando x no es un T.

Un value también puede implementar interfaces. Si se guarda en una variable del tipo de la interfaz, se copia dentro de una caja (boxing).

Toda clase deriva de any (System.Any), que tiene ToString(), Equals y Hash(). Reemplaza ToString() para controlar cómo se imprime un objeto.

Enums

Un enum es un conjunto cerrado de valores con nombre sobre un tipo base: un entero (i32 por defecto), str o un value.

AntrilRun
import System;

into namespace Palette
{
	public enum Size { Small, Medium = 5, Large }

	public enum Word : str { Hello = "hola", World }

	public bitflag enum Access : u8 { Read, Write, Execute, All = declbits }

	public record value Rgb
	{
		public:
			u8 R;
			u8 G;
			u8 B;
			new(u8 R, u8 G, u8 B);
	}

	public enum Color : Rgb
	{
		Red = new Rgb(255, 0, 0),
		Green = new Rgb(0, 255, 0),
	}

	Main()
	{
		Size s = Size.Large;
		Console.WriteLine(s + " is " + (i32)s);
		Console.WriteLine((str)Word.Hello + " " + (str)Word.World);

		Access a = Access.Read | Access.Write;
		Console.WriteLine((a & Access.Write) == Access.Write);
		Console.WriteLine(a.HasFlag(Access.Execute));
		Console.WriteLine((u8)Access.All);

		Rgb green = (Rgb)Color.Green;
		Console.WriteLine(green);
	}
}
Terminal
larva run
Large is 6
hola World
true
false
7
Rgb { R = 0, G = 255, B = 0 }
  • Los miembros enteros sin valor toman el del anterior más 1, empezando en 0. Un miembro str sin valor es su propio nombre.
  • Un enum se convierte desde y hacia su tipo base solo con un cast: (i32)s, (Size)6.
  • ToString() (y la impresión) da el nombre del miembro.
  • En un bitflag enum (con base u32 por defecto), los miembros sin valor toman el siguiente bit libre: 1, 2, 4… |, &, ^ y ~ conservan el tipo del enum. declbits son todos los bits declarados, allbits todos los bits de la base. HasFlag, HasAny, With, Without y Toggle comprueban y cambian bits.
  • Los miembros se pueden calcular a partir de otros miembros y constantes: ReadWrite = Read | Write.
  • Con una base value, cada miembro es un valor de ese tipo. Para convertir un valor de vuelta al enum ((Color)rgb) hace falta igualdad por contenido: un record value o un op ==.

Registros

Un record class o record value son datos: el constructor asigna sus campos y estos no cambian después. Los registros se comparan por contenido, imprimen sus campos, calculan su hash para usarse como claves de diccionario y se copian con cambios usando with.

AntrilRun
import System;

into namespace People
{
	public record class Person
	{
		public:
			str Name;
			i32 Age;
			new(str Name, i32 Age);
	}

	Main()
	{
		Person ana = new Person("Ana", 31);
		Person older = ana with { Age = 32 };
		Console.WriteLine(ana);
		Console.WriteLine(ana == new Person("Ana", 31));
		Console.WriteLine(older.Age);
		if (ana is Person(var name, > 30))
			Console.WriteLine(name + " is over 30");
		Console.WriteLine(older is { Name: "Ana", Age: 32 });
	}
}
Terminal
larva run
Person { Name = Ana, Age = 31 }
true
32
Ana is over 30
true

Un registro de clase no deriva de otra clase y no se puede derivar de él; sí puede implementar interfaces.

Tuplas

(T1, T2, …) es un tipo tupla y (a, b, …) crea una. Los elementos son Item1, Item2… Las tuplas son registros: se comparan por contenido, se imprimen como (1, one) y sirven como claves de diccionario.

AntrilRun
import System;

MinMax(i32[] values) : (i32, i32)
{
	i32 low = values[0], high = values[0];
	loop (i32 v in values)
	{
		if (v < low)
			low = v;
		if (v > high)
			high = v;
	}
	return (low, high);
}

Main()
{
	(i32, i32) range = MinMax([4, 9, 1, 7]);
	Console.WriteLine(range);
	Console.WriteLine(range.Item2 - range.Item1);

	(i32, str) pair = (1, "one");
	if (pair is (1, var word))
		Console.WriteLine(word);

	Dictionary<(i32, i32), str> grid = new Dictionary<(i32, i32), str>();
	grid[(0, 1)] = "a";
	Console.WriteLine(grid[(0, 1)]);
}
Terminal
larva run
(1, 9)
8
one
a

(…) es un System.ValueTuple, cuyos elementos se pueden cambiar. ToTuple() da un System.Tuple, una clase cuyos elementos son fijos.

Una tupla que contiene objetos, como (i32, str), no puede ser el argumento de tipo de un tipo genérico (List<(i32, str)>). Las tuplas de números y valores simples sí pueden: Dictionary<(i32, i32), str>.

Tipos anulables

Un tipo sin ? nunca contiene null. T? sí puede:

AntrilRun
import System;

public class Person
{
	public:
		str Name;
		new(str Name);
}

Find(str name) : Person?
{
	if (name == "ana")
		return new Person("Ana");
	return null;
}

Main()
{
	Person? found = Find("ana");
	Person? missing = Find("bob");
	Console.WriteLine(found?.Name ?? "nobody");
	Console.WriteLine(missing?.Name ?? "nobody");

	i32? count = null;
	Console.WriteLine(count ?? -1);
	count = 7;
	Console.WriteLine(count.HasValue);
	Console.WriteLine(count.Value);

	if (found is Person p)
		Console.WriteLine("found " + p.Name);
}
Terminal
larva run
Ana
nobody
-1
true
7
found Ana
  • ?. lee un miembro, o da null cuando el lado izquierdo es null.
  • ?? da su lado izquierdo salvo que sea null.
  • x is T name comprueba que haya un valor y le da un nombre.
  • Para tipos valor, T? tiene HasValue y Value; leer Value cuando no hay ninguno detiene el programa.

Siguiente: Funciones y genéricos.

¿Te resultó útil esta página?

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

Esc