Esta página aún no está traducida al español. Leer la versión en inglés

Patterns

The patterns of Antril - discards, var, constants, comparisons, ranges, and/or/not, types, positions, properties and null - and where they are used - is, switch statements and switch expressions, with when guards.

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

En esta página

A pattern tests a value and can take parts of it into new variables. Patterns are used in three places:

Where Form
is value is pattern gives a bool.
A switch statement switch (value) { pattern [when condition] => statement … }
A switch expression value switch { pattern [when condition] => result, … }

The patterns

Pattern Matches
_ Anything.
var name Anything, and names it.
42, "stop", Color.Red, Limit A value equal to the constant.
null A null value.
< 0, <= 9, > 100, >= 90, != 3 A number that compares so with the constant.
1..10, 1..=9 A number in the range: .. leaves out the end, ..= includes it.
p and q A value both match.
p or q A value either matches.
not p A value p does not match.
(p) Grouping.
T, T name A value of type T (a class, an interface, a primitive, a generic type), optionally named.
T(p, q, …), (p, q, …) The value's positions, matched one by one.
T { A: p, B: q }, { A: p } The value's fields or properties, matched by name.

not binds tighter than and, and and tighter than or: < 0 or > 10 and not 20 is < 0 or (> 10 and (not 20)). Use parentheses for anything else.

AntrilRun
import System;

Grade(i32 score) : str => score switch
{
	< 0 or > 100 => "invalid",
	>= 90 => "A",
	80..90 => "B",
	70..=79 => "C",
	not 0 => "D",
	_ => "zero",
};

Main()
{
	loop (i32 s in [95, 85, 75, 50, 0, 101])
		Console.Write(Grade(s) + " ");
	Console.WriteLine();
	Console.WriteLine(5 is not (1 or 2));
	Console.WriteLine(3 is != 3);
}
Terminal
larva run
A B C D zero invalid 
true
false

Constants

A constant pattern is a literal, a const, an enum member, or an expression made of them (it is read up to the shift operators, so 1 + 1 works and a comparison does not). It compares with == of the value's type: numbers, bool, strings by content, enum members.

AntrilRun
import System;

public enum Color { Red, Green, Blue }

const i32 Limit = 10;

Main()
{
	Color c = Color.Green;
	switch (c)
	{
		Color.Red => Console.WriteLine("red");
		Color.Green or Color.Blue => Console.WriteLine("green or blue");
	}
	Console.WriteLine("stop" switch { "go" => 1, "stop" => 2, _ => 0 });
	Console.WriteLine(10 switch { Limit => "the limit", _ => "below" });
}
Terminal
larva run
green or blue
2
the limit

A pattern constant of a type the value cannot have is AN2090.

Comparisons and ranges

<, <=, >, >= and != compare numbers (AN2086 for other types); the bound is a constant. A range a..b is >= a and < b, and a..=b is >= a and <= b.

Types

T name matches a value whose type is T (or derives from it, or implements it) and gives it as a T in name. Without a name, T only tests. Primitives, generic types and interfaces work:

AntrilRun
import System;

Kind(any? x) : str => x switch
{
	null => "null",
	i32 n => "i32 " + n,
	str s => "text " + s,
	List<i32> l => "a list of " + l.Count,
	_ => "something else",
};

Main()
{
	Console.WriteLine(Kind(null));
	Console.WriteLine(Kind(4));
	Console.WriteLine(Kind("hi"));
	Console.WriteLine(Kind(new List<i32>()));
	Console.WriteLine(Kind(2.5));

	str? maybe = "x";
	if (maybe is str text)
		Console.WriteLine(text.Length);
	if (maybe is not null)
		Console.WriteLine("has a value");
}
Terminal
larva run
null
i32 4
text hi
a list of 0
something else
1
has a value
  • A type pattern on a nullable value matches only a value that is not null, so maybe is str text both tests for null and names the string.
  • A type the value can never have is AN2088 or AN2089 (a i32 is never a str).
  • Inside a pattern, ? after a type is not read as a nullable type (so x is T ? a : b is a conditional). Test nullable values with null and not null.

Positions

(p, q, …) matches the positions of the value, in order, and T(p, q, …) first tests the type T. The positions of a value are:

  • the fields of a tuple (Item1, Item2…);
  • the fields a record class or record value stores, in their order;
  • the fields of a value, in their order;
  • the &mut parameters of a method Deconstruct, for other classes.
AntrilRun
import System;

public record value Point
{
	public:
		i32 X;
		i32 Y;
		new(i32 X, i32 Y);
}

Where(Point p) : str => p switch
{
	(0, 0) => "origin",
	Point(0, var y) => "on the Y axis at " + y,
	(var x, 0) => "on the X axis at " + x,
	{ X: > 0, Y: > 0 } => "first quadrant",
	_ => "elsewhere",
};

Main()
{
	Console.WriteLine(Where(new Point(0, 0)));
	Console.WriteLine(Where(new Point(0, 5)));
	Console.WriteLine(Where(new Point(3, 0)));
	Console.WriteLine(Where(new Point(1, 2)));
	Console.WriteLine(Where(new Point(-1, 2)));
	Console.WriteLine((1, "a") is (1, _));
}
Terminal
larva run
origin
on the Y axis at 5
on the X axis at 3
first quadrant
elsewhere
true

A class gets positions by declaring Deconstruct with one &mut parameter per position. References need the borrow checker (borrowcheck: warn or enforce; see Memory):

Antril
public class Size
{
	public:
		i32 W;
		i32 H;
		new(i32 W, i32 H);
		Deconstruct(&mut i32 w, &mut i32 h) { w = W; h = H; }
}

Size s = new Size(2, 3);
if (s is (2, var h))
	Console.WriteLine(h);    // 3

The wrong number of positions is AN2135, and a type without positions is AN2136.

Properties

{ Name: p, … } matches fields and properties by name; with a type before it, T { … } also tests the type. Each sub-pattern is any pattern, so they nest:

Antril
if (order is { Customer: { Country: "AR" }, Total: > 100 })
	Ship(order);

A name the type does not have is AN2137.

when

A when condition after an arm's pattern adds a test. The variables the pattern names can be used in it:

AntrilRun
import System;

public base class Shape { }

public class Circle : Shape
{
	public:
		f64 Radius;
		new(f64 Radius);
}

public class Rect : Shape
{
	public:
		f64 W;
		f64 H;
		new(f64 W, f64 H);
}

Describe(Shape s) : str => s switch
{
	Circle c when c.Radius > 10 => "a big circle",
	Circle c => "a circle of radius " + c.Radius,
	Rect { W: 0 } or Rect { H: 0 } => "a flat rectangle",
	Rect r => "a rectangle " + r.W + "x" + r.H,
	_ => "something else",
};

Main()
{
	Console.WriteLine(Describe(new Circle(20)));
	Console.WriteLine(Describe(new Circle(2)));
	Console.WriteLine(Describe(new Rect(0, 4)));
	Console.WriteLine(Describe(new Rect(2, 3)));
}
Terminal
larva run
a big circle
a circle of radius 2
a flat rectangle
a rectangle 2x3

Variables

A name declared by a pattern (T name, var name) is a new local. In is, it is set where the test is true, so it can be used after && and in the if it guards:

Antril
if (n is var m && m > 5)
	Console.WriteLine(m);

In a switch arm, it is seen in the arm's when condition and its body.

Order and exhaustiveness

Arms are tried in order, and the first that matches wins: put the more specific ones first.

The compiler does not check that a switch covers every value:

  • a switch statement where no arm matches does nothing;
  • a switch expression where no arm matches stops the program with larva panic: null reference (exit code 101).

End a switch expression with _ => … unless its arms surely cover every value.

¿Te resultó útil esta página?

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

Esc