Fenner
A linguagem

Tuplas e desestruturação

Devolver e agrupar vários valores sem declarar um record, e desmontá-los com var (A, B) := F.

O que é e por que existe#

Uma função que precisa devolver dois valores (quociente e resto, valor e erro) costumava obrigar a declarar um record só para isso, ou a usar parâmetros out. Uma tupla agrupa de 2 a 8 valores, de tipos quaisquer, sem declaração prévia.

Fenner
function DivMod(A, B: Int32): (Int32, Int32);       // tipo tupla anônimo
begin
  Exit((A div B, A mod B));                         // literal de tupla
end function;

type
  TEntry = tuple<Int32, UString>;                   // tupla com nome

Usando#

FormaSignifica
LPair.Item1, .Item2…acesso por posição (também T[0] e T.0, base 0)
var (LQ, LR) := DivMod(17, 5);declara e preenche duas variáveis de uma vez
var (_, LRem) := DivMod(9, 4);_ descarta um componente
(LQuot, LRem) := DivMod(9, 4);desestrutura em variáveis que já existem
(X, Y) := (Y, X);troca sem temporária (o lado direito é lido inteiro antes)
A = B, A <> Bcompara componente a componente
A < B, A <= B…ordem lexicográfica, em tuplas do mesmo tipo cujos elementos têm ordem
rc-tuples.fxp
program Tuples;
// Demo: tuplas e desestruturação. Uma tupla agrupa valores sem declarar um record.

type
  TEntry = tuple<Int32, UString>;        // apelido para um tipo tupla

// Uma função pode devolver vários valores de uma vez: o tipo de retorno é (Int32, Int32).
function DivMod(A, B: Int32): (Int32, Int32);
begin
  Exit((A div B, A mod B));              // literal de tupla entre parênteses
end function;

function Lookup(AKey: Int32): TEntry;
begin
  Result := (AKey, 'chave ' + IntToUStr(AKey));
end function;

// Declarações `var (..)` e literais soltos só valem dentro de rotinas.
procedure Run;
var
  LPair: (Int32, Int32);
  LQuot, LRem, X, Y: Int32;
  LEntry: TEntry;
begin
  LPair := DivMod(7, 2);
  WriteLn('Item1=', LPair.Item1, ' Item2=', LPair.Item2);   // acesso por Item1..ItemN

  var (LQ2, LR2) := DivMod(17, 5);       // declara e preenche duas variáveis de uma vez
  WriteLn('17 div 5 = ', LQ2, ', resto ', LR2);

  var (_, LOnlyRem) := DivMod(9, 4);     // `_` descarta o componente
  WriteLn('só o resto de 9/4: ', LOnlyRem);

  (LQuot, LRem) := DivMod(9, 4);         // desestrutura em variáveis que já existem
  WriteLn(LQuot, ' ', LRem);

  X := 1;
  Y := 2;
  (X, Y) := (Y, X);                      // troca sem variável temporária
  WriteLn('depois da troca: X=', X, ' Y=', Y);

  LEntry := Lookup(5);
  WriteLn(LEntry.Item1, ' -> ', LEntry.Item2);

  // tuplas se comparam componente a componente
  WriteLn('iguais? ', LPair = DivMod(7, 2), '  diferentes? ', LPair <> DivMod(8, 3));
end procedure;

begin
  Run;
end.
Saída
Item1=3 Item2=1
17 div 5 = 3, resto 2
só o resto de 9/4: 1
2 1
depois da troca: X=2 Y=1
5 -> chave 5
iguais? TRUE  diferentes? TRUE

Demo 41. Retorno múltiplo, desestruturação, descarte com _, troca e comparação.

Itens nomeados#

Como em C#, os itens podem ter nome. Os nomes são apelidos de ItemK, ligados ao tipo escrito na declaração:

Fenner
function Search(AId: Int32): (Name: UString, Age: Int32);
begin
  Exit((Name: 'Ana', Age: 30));
end function;

var LPerson := Search(1);              // herda os nomes do retorno
WriteLn(LPerson.Name, LPerson.Age, LPerson.Item1);

Os nomes não fazem parte do tipo: (Name: UString, Age: Int32) e (UString, Int32) são o mesmo tipo. A desestruturação continua posicional.

Armadilhas e erros típicos#

  • Atribuir uma tupla a um escalar é erro de tipos (FE1302: "tipos incompatíveis (Int32 esperado, (Int32, Int32) encontrado)").
  • type X = (Int32, UString); continua sendo um enumerado. Para dar nome a uma tupla, escreva tuple<…>.
  • var (A, B) := só vale dentro de rotina. No corpo do programa use variáveis declaradas; o literal (A, B) := (B, A) vale em qualquer comando, mas não no inicializador de uma const.
  • Sem tupla de 1 elemento nem de mais de 8.
  • O elemento precisa de um tipo com nome: array of X pede um apelido antes.
  • Os nomes de item não atravessam argumento de genérico (TList<TPessoa>: use .Item1), property, o .fxd de um package nem ponteiro.
  • T.0.1 (índice duplo numa tupla aninhada) lê e grava, mas o elemento 0 tem de ser tupla.
  • O aviso opt-in TupleNames (ligado pelo Lint strict) acusa nomes de item que não casam entre dois tipos.

Veja também#