Saltar al contenido principal

Tipos de patrones

Referencia de tipos de patrones en Dart.

Esta página es una referencia para los diferentes tipos de patrones. Para obtener una descripción general de cómo funcionan los patrones, dónde puedes usarlos en Dart y los casos de uso comunes, visita la página principal de Patrones (Patterns).

Precedencia de patrones

#

De manera similar a la precedencia de operadores, la evaluación de patrones se rige por reglas de precedencia. Puedes utilizar patrones entre paréntesis para evaluar primero los patrones de menor precedencia.

Este documento enumera los tipos de patrones en orden ascendente de precedencia:

  • Los patrones OR lógico (logical-or) tienen menor precedencia que los AND lógicos (logical-and), los patrones AND lógicos tienen menor precedencia que los patrones relacionales, y así sucesivamente.

  • Los patrones unarios sufijos (cast, null-check, y null-assert) comparten el mismo nivel de precedencia.

  • Los patrones primarios restantes comparten la precedencia más alta. Los patrones de tipo colección (record, list y map) y los patrones Object engloban otros datos, por lo que se evalúan primero como patrones externos.

OR lógico (logical-or)

#

subpattern1 || subpattern2

Un patrón OR lógico (logical-or) separa subpatrones mediante || y coincide si cualquiera de las ramas coincide. Las ramas se evalúan de izquierda a derecha. Una vez que una rama coincide, el resto no se evalúa.

dart
var isPrimary = switch (color) {
  Color.red || Color.yellow || Color.blue => true,
  _ => false,
};

Los subpatrones en un patrón OR lógico pueden vincular variables, pero las ramas deben definir el mismo conjunto de variables, porque solo se evaluará una rama cuando coincida el patrón.

AND lógico (logical-and)

#

subpattern1 && subpattern2

Un par de patrones separados por && coincide solo si ambos subpatrones coinciden. Si la rama izquierda no coincide, la rama derecha no se evalúa.

Los subpatrones en un patrón AND lógico (logical-and) pueden vincular variables, pero las variables en cada subpatrón no deben superponerse, porque ambas se vincularán si el patrón coincide:

dart
switch ((1, 2)) {
  // Error, both subpatterns attempt to bind 'b'.
  case (var a, var b) && (var b, var c): // ...
}

Relacional

#

== expression

< expression

Los patrones relacionales comparan el valor coincidente con una constante dada usando cualquiera de los operadores de igualdad o relacionales: ==, !=, <, >, <= y >=.

El patrón coincide cuando al llamar al operador apropiado en el valor coincidente con la constante como argumento devuelve true.

Los patrones relacionales son útiles para coincidir en rangos numéricos, especialmente cuando se combinan con el patrón AND lógico (logical-and):

dart
String asciiCharType(int char) {
  const space = 32;
  const zero = 48;
  const nine = 57;

  return switch (char) {
    < space => 'control',
    == space => 'space',
    > space && < zero => 'punctuation',
    >= zero && <= nine => 'digit',
    _ => '',
  };
}

Conversión de tipo (Cast)

#

foo as String

Un patrón de conversión (cast pattern) te permite insertar una conversión de tipo (type cast) en medio de la desestructuración, antes de pasar el valor a otro subpatrón:

dart
(num, Object) record = (1, 's');
var (i as int, s as String) = record;

Los patrones de conversión (cast patterns) lanzarán una excepción (throw) si el valor no tiene el tipo indicado. Al igual que el patrón null-assert, esto te permite forzar la aserción del tipo esperado de algún valor desestructurado.

Comprobación de nulos (Null-check)

#

subpattern?

Los patrones null-check coinciden primero si el valor no es nulo, y luego comparan el patrón interno contra ese mismo valor. Te permiten vincular una variable cuyo tipo es el tipo base no anulable del valor anulable que se está comparando.

Para tratar los valores null como fallos de coincidencia sin lanzar excepciones, usa el patrón null-check.

dart
String? maybeString = 'nullable with base type String';
switch (maybeString) {
  case var s?:
  // 's' has type non-nullable String here.
}

Para coincidir cuando el valor sea null, usa el patrón constante null.

Aserción de nulos (Null-assert)

#

subpattern!

Los patrones null-assert coinciden primero si el objeto no es nulo, y luego en el valor. Permiten que los valores no nulos fluyan, pero lanzan una excepción si el valor coincidente es nulo.

Para asegurarse de que los valores null no se traten silenciosamente como fallos de coincidencia, usa un patrón null-assert al comparar:

dart
List<String?> row = ['user', null];
switch (row) {
  case ['user', var name!]: // ...
  // 'name' is a non-nullable string here.
}

Para eliminar valores null de los patrones de declaración de variables, usa el patrón null-assert:

dart
(int?, int?) position = (2, 3);

var (x!, y!) = position;

Para coincidir cuando el valor sea null, usa el patrón constante null.

Constante

#

123, null, 'string', math.pi, SomeClass.constant, const Thing(1, 2), const (1 + 2)

Los patrones constantes coinciden cuando el valor es igual a la constante:

dart
switch (number) {
  // Matches if 1 == number.
  case 1: // ...
}

Puedes usar literales simples y referencias a constantes nombradas directamente como patrones constantes:

  • Literales numéricos (123, 45.56)
  • Literales booleanos (true)
  • Literales de cadena (String literals) ('string')
  • Constantes nombradas (someConstant, math.pi, double.infinity)
  • Constructores constantes (const Point(0, 0))
  • Literales de colección constantes (const [], const {1, 2})

Las expresiones constantes más complejas deben estar entre paréntesis y llevar el prefijo const (const (1 + 2)):

dart
// List or map pattern:
case [a, b]: // ...

// List or map literal:
case const [a, b]: // ...

Variable

#

var bar, String str, final int _

Los patrones de variables vinculan nuevas variables a valores que han coincidido o se han desestructurado. Por lo general, ocurren como parte de un patrón de desestructuración (destructuring pattern) para capturar un valor desestructurado.

Las variables están dentro del alcance en una región de código que solo es accesible cuando el patrón ha coincidido.

dart
switch ((1, 2)) {
  // 'var a' and 'var b' are variable patterns that bind to 1 and 2, respectively.
  case (var a, var b): // ...
  // 'a' and 'b' are in scope in the case body.
}

Un patrón de variable con tipo (typed) solo coincide si el valor coincidente tiene el tipo declarado, y falla en caso contrario:

dart
switch ((1, 2)) {
  // Does not match.
  case (int a, String b): // ...
}

Puedes usar un patrón comodín (wildcard pattern) como un patrón de variable.

Identificador

#

foo, _

Los patrones de identificador se comportan como un patrón constante o como un patrón de variable, dependiendo del contexto donde aparezcan:

  • Contexto de declaración (Declaration): declara una nueva variable con el nombre del identificador: var (a, b) = (1, 2);
  • Contexto de asignación (Assignment): asigna a una variable existente con el nombre del identificador: (a, b) = (3, 4);
  • Coincidencia context: treated as a named constant pattern (unless its name is _):
    dart
    const c = 1;
    switch (2) {
      case c:
        print('match $c');
      default:
        print('no match'); // Prints "no match".
    }
    
  • Identificador comodín (Wildcard) en cualquier contexto: coincide con cualquier valor y lo descarta: case [_, var y, _]: print('El elemento del medio es $y');

Entre paréntesis

#

(subpattern)

Al igual que las expresiones entre paréntesis, los paréntesis en un patrón te permiten controlar la precedencia de patrones e insertar un patrón de menor precedencia donde se espera uno de mayor precedencia.

Por ejemplo, imagina que las constantes booleanas x, y y z son iguales a true, true y false, respectivamente. Aunque el siguiente ejemplo se asemeja a la evaluación de expresiones booleanas, el ejemplo compara patrones.

dart
// ...
x || y => 'matches true',
x || y && z => 'matches true',
x || (y && z) => 'matches true',
// `x || y && z` is the same thing as `x || (y && z)`.
(x || y) && z => 'matches nothing',
// ...

Dart comienza a comparar el patrón de izquierda a derecha.

  1. El primer patrón coincide con true ya que x coincide con true.

  2. El segundo patrón coincide con true ya que x coincide con true.

  3. El tercer patrón coincide con true ya que x coincide con true.

  4. El cuarto patrón (x || y) && z no tiene coincidencia.

    • El x coincide con true, por lo que Dart no intenta coincidir con y.
    • Aunque (x || y) coincide con true, z no coincide con true
    • Por lo tanto, el patrón (x || y) && z no coincide con true.
    • El subpatrón (x || y) no coincide con false, por lo que Dart no intenta coincidir con z.
    • Por lo tanto, el patrón (x || y) && z no coincide con false.
    • Como conclusión, (x || y) && z no tiene coincidencia.

Lista

#

[subpattern1, subpattern2]

Un patrón de lista coincide con valores que implementan List, y luego compara recursivamente sus subpatrones contra los elementos de la lista para desestructurarlos por posición:

dart
const a = 'a';
const b = 'b';
switch (obj) {
  // List pattern [a, b] matches obj first if obj is a list with two fields,
  // then if its fields match the constant subpatterns 'a' and 'b'.
  case [a, b]:
    print('$a, $b');
}

Los patrones de lista requieren que el número de elementos en el patrón coincida con la lista completa. Sin embargo, puedes usar un elemento restante (rest element) como marcador de posición para representar cualquier número de elementos en una lista.

Elemento restante (Rest element)

#

Los patrones de lista pueden contener un elemento restante (...) lo que permite la coincidencia con listas de longitudes arbitrarias.

dart
var [a, b, ..., c, d] = [1, 2, 3, 4, 5, 6, 7];
// Prints "1 2 6 7".
print('$a $b $c $d');

Un elemento restante también puede tener un subpatrón que recopila los elementos que no coinciden con los otros subpatrones de la lista, en una nueva lista:

dart
var [a, b, ...rest, c, d] = [1, 2, 3, 4, 5, 6, 7];
// Prints "1 2 [3, 4, 5] 6 7".
print('$a $b $rest $c $d');

Mapa

#

{"key": subpattern1, someConst: subpattern2}

Los patrones de mapa coinciden con valores que implementan Map, y luego comparan recursivamente sus subpatrones contra las claves del mapa para desestructurarlos.

Los patrones de mapa no requieren que el patrón coincida con el mapa completo. Un patrón de mapa ignora cualquier clave que contenga el mapa que no coincida con el patrón. Intentar coincidir con una clave que no existe en el mapa lanzará un StateError:

dart
final {'foo': int? foo} = {};

Registro (Record)

#

(subpattern1, subpattern2)

(x: subpattern1, y: subpattern2)

Los patrones de registro (record) coinciden con un objeto registro (record) y desestructuran sus campos. Si el valor no es un registro con la misma forma (shape) que el patrón, la coincidencia falla. De lo contrario, los subpatrones de campo se comparan con los campos correspondientes en el registro.

Los patrones de registro requieren que el patrón coincida con el registro completo. Para desestructurar un registro con campos nombrados usando un patrón, incluye los nombres de los campos en el patrón:

dart
var (myString: foo, myNumber: bar) = (myString: 'string', myNumber: 1);

El nombre del getter se puede omitir e inferir a partir del patrón de variable o patrón de identificador en el subpatrón de campo. Cada uno de estos pares de patrones es equivalente:

dart
// Record pattern with variable subpatterns:
var (untyped: untyped, typed: int typed) = record;
var (:untyped, :int typed) = record;

switch (record) {
  case (untyped: var untyped, typed: int typed): // ...
  case (:var untyped, :int typed): // ...
}

// Record pattern with null-check and null-assert subpatterns:
switch (record) {
  case (checked: var checked?, asserted: var asserted!): // ...
  case (:var checked?, :var asserted!): // ...
}

// Record pattern with cast subpattern:
var (untyped: untyped as int, typed: typed as String) = record;
var (:untyped as int, :typed as String) = record;

Object

#

SomeClass(x: subpattern1, y: subpattern2)

Los patrones de objeto (Object patterns) comprueban el valor coincidente contra un tipo con nombre dado para desestructurar datos usando getters en las propiedades del objeto. Se refutanPatrón refutableUn patrón que se puede probar contra un valor. Más información si el valor no tiene el mismo tipo.

dart
switch (shape) {
  // Matches if shape is of type Rect, and then against the properties of Rect.
  case Rect(width: var w, height: var h): // ...
}

El nombre del getter se puede omitir e inferir del patrón de variable o patrón de identificador en el subpatrón de campo:

dart
// Binds new variables x and y to the values of Point's x and y properties.
var Point(:x, :y) = Point(1, 2);

Los patrones de objeto no requieren que el patrón coincida con todo el objeto. Si un objeto tiene campos adicionales que el patrón no desestructura, aún puede coincidir.

Comodín (Wildcard)

#

_

Un patrón llamado _ es un comodín (wildcard), ya sea un patrón de variable o patrón de identificador, que no vincula ni asigna a ninguna variable.

Es útil como marcador de posición en lugares donde necesitas un subpatrón para desestructurar valores posicionales posteriores:

dart
var list = [1, 2, 3];
var [_, two, _] = list;

Un nombre comodín con una anotación de tipo es útil cuando deseas probar el tipo de un valor pero no vincular el valor a un nombre:

dart
switch (record) {
  case (int _, String _):
    print('First field is int and second is String.');
}