New features: Tuples and match statementTwo bigger features are now merged into
main - anonymous tuples and the
match statement. Both are on by default in
{$mode unleashed}, and Lazarus Unleashed already knows about them (autocomplete, parameter hints). Quick tour of each below.
TuplesThe tuple preview from post
#186 is now live. Modeswitch
TUPLES, on by default in
{$mode unleashed}. Short recap: a tuple is an anonymous record written in parentheses. Under the hood it really is a record, so copy semantics, managed types, pass-by-value/var/const, all work unchanged.
Positional and named{$mode unleashed}
program tuples_basic;
var
p: (Integer, Integer);
c: (x, y: Integer);
begin
p := (10, 20); // positional literal
c := (x: 1, y: 2); // named literal
c := (y: 2, x: 1); // fields may be written in any order
WriteLn(p._1, ' ', p._2); // access by _N (1-based)
WriteLn(p[0], ' ', p[1]); // or by constant index (0-based)
WriteLn(c.x, ' ', c.y);
ReadLn;
end.
Constant-index access (
p[0],
p[1]) is the same thing as
p._1,
p._2. The index has to be a compile-time constant - tuple field types may differ, so
t[i] with a runtime
i has no resolvable type and is rejected at parse time.
Destructuring, swap, wildcard{$mode unleashed}
program tuples_destructuring;
function GetPair: (Integer, Integer);
begin
Result := (10, 20);
end;
function GetTriple: (Integer, Integer, Integer);
begin
Result := (1, 2, 3);
end;
var
count: Integer;
pairs: array of (String, Integer);
begin
var (x, y) := GetPair; // inline var destructuring
var (a, _, c) := GetTriple; // wildcard _ skips that field
WriteLn('pair: ', x, ' ', y);
WriteLn('triple: ', a, ' ', c);
(x, y) := (y, x); // swap via multi-assignment
WriteLn('swap: ', x, ' ', y);
(count, _) := GetPair; // multi-assign to existing vars
WriteLn('count=', count);
pairs := [('one', 1), ('two', 2), ('three', 3)];
for var (key, val) in pairs do // for-in destructuring
WriteLn(key, '=', val);
ReadLn;
end.
Output:
pair: 10 20
triple: 1 3
swap: 20 10
count=10
one=1
two=2
three=3
Parameters and tuple-aware Exit{$mode unleashed}
program tuples_params;
procedure ShowNamed(c: (x, y: Integer));
begin
WriteLn(c.x, '/', c.y);
end;
procedure ShowDestructured((first, last): (String, String));
begin
WriteLn(first, ' ', last);
end;
// shorthand: field names and types in one bracket
procedure ShowInline((x, y: Integer; name: String));
begin
WriteLn(x, ' ', y, ' ', name);
end;
function FindOrDefault(ok: Boolean): (Integer, String);
begin
if ok then Exit(42, 'found'); // no extra parens around the tuple
Result := (-1, 'missing');
end;
var
r: (Integer, String);
begin
ShowNamed((x: 1, y: 2));
ShowDestructured(('John', 'Doe'));
ShowInline((10, 20, 'point'));
r := FindOrDefault(True);
WriteLn(r._1, ' ', r._2);
r := FindOrDefault(False);
WriteLn(r._1, ' ', r._2);
ReadLn;
end.
Output:
1/2
John Doe
10 20 point
42 found
-1 missing
Equality, ordering, Write auto-expandTwo tuples of the same shape compare with
=,
<> and (lexicographically)
<,
<=,
>,
>=.
Write/
WriteLn auto-expand a tuple into its fields so you don't have to unpack it by hand.
{$mode unleashed}
program tuples_compare;
var
a, b: (Integer, Integer);
begin
a := (1, 2);
b := (1, 3);
if a < b then WriteLn('a is smaller'); // 1 = 1, then 2 < 3
WriteLn(a); // auto-expand: "1, 2"
ReadLn;
end.
Output:
Structural compatibilityPositional and named tuples of the same shape are compatible with each other and with plain named records, so a positional literal can be fed into a named tuple (or into a regular record) with no boilerplate:
{$mode unleashed}
program tuples_compat;
type
TPoint = record
x, y: Integer;
end;
var
t: (Integer, Integer);
r: TPoint;
begin
r := (10, 20); // positional literal -> record
t := r; // record -> positional tuple
WriteLn('r: ', r.x, ' ', r.y);
WriteLn('t: ', t._1, ' ', t._2);
ReadLn;
end.
Output:
Arrays, generics, nesting, typed constants{$mode unleashed}
program tuples_misc;
generic function MakePair<A, B>(x: A; y: B): (A, B);
begin
Result._1 := x;
Result._2 := y;
end;
const
origin: (Integer, Integer) = (0, 0);
point: (x, y: Integer) = (x: 10, y: 20);
var
pairs: array of (Integer, String);
n: (Integer, (String, Integer));
mp: (Integer, String);
begin
pairs := [(1, 'a'), (2, 'b'), (3, 'c')];
WriteLn('pairs[0]: ', pairs[0]._1, ' ', pairs[0]._2);
mp := specialize MakePair<Integer, String>(42, 'hi');
WriteLn('makepair: ', mp._1, ' ', mp._2);
n := (5, ('label', 42));
WriteLn('nested: ', n._1, ' ', n._2._1, ' ', n._2._2);
WriteLn('origin: ', origin._1, ' ', origin._2);
WriteLn('point: ', point.x, ' ', point.y);
ReadLn;
end.
Output:
pairs[0]: 1 a
makepair: 42 hi
nested: 5 label 42
origin: 0 0
point: 10 20
match statement (and expression)The long fallthrough debate eventually landed on a dedicated keyword -
match. Modeswitch
MATCH, on by default in
{$mode unleashed}. Think of it as
case that accepts non-constant labels, strings, tuple patterns and expressions, with explicit first-match semantics and an opt-in fallthrough mode.
Basic form - first-match{$mode unleashed}
program match_basic;
var
dir: Integer;
name: String;
begin
dir := 2;
match dir of
0: name := 'north';
1: name := 'east';
2: name := 'south';
3: name := 'west';
_: name := 'unknown'; // catch-all; 'else' / 'otherwise' also work
end;
WriteLn(name);
ReadLn;
end.
Output:
The subject is evaluated once and compared against each branch with
=. First match wins, the rest is skipped.
_ is a catch-all;
else and
otherwise work too.
Strings as subjects and labels{$mode unleashed}
program match_strings;
var
s: String;
begin
s := 'hello';
match s of
'hello': WriteLn('greeting');
'bye': WriteLn('farewell');
_: WriteLn('unknown');
end;
ReadLn;
end.
Output:
Comma-separated patterns (OR){$mode unleashed}
program match_comma;
var
x: Integer;
begin
x := 3;
match x of
1, 2, 3: WriteLn('small');
4, 5, 6: WriteLn('medium');
_: WriteLn('big');
end;
ReadLn;
end.
Output:
Condition-based: match without "of"No subject - every branch is just a boolean. A clean replacement for
if/else if ladders:
{$mode unleashed}
program match_condition;
var
x: Integer;
begin
x := 50;
match
x > 100: WriteLn('huge');
x > 10: WriteLn('medium');
x > 0: WriteLn('small');
_: WriteLn('non-positive');
end;
ReadLn;
end.
Output:
match all - fallthrough modematch all is the opt-in fallthrough variant. Every branch whose condition matches is executed, in source order.
leave exits the
match all early without touching the enclosing loop's
break:
{$mode unleashed}
program match_all;
procedure Log(const s: String);
begin
WriteLn(s);
end;
var
x: Integer;
hits, totalFives, threes, other: Integer;
begin
hits := 0; totalFives := 0; threes := 0; other := 0;
x := 5;
match all x of
5: Inc(hits);
5: Inc(totalFives);
3: Inc(threes);
_: Inc(other); // always runs
end;
WriteLn('hits=', hits, ' totalFives=', totalFives, ' threes=', threes, ' other=', other);
x := 50;
match all
x > 100: Log('big');
x > 10: begin Log('medium'); leave; end;
x > 0: Log('positive'); // never reached due to leave
end;
ReadLn;
end.
Output:
hits=1 totalFives=1 threes=0 other=1
medium
Tuple patterns with wildcardsWhen the subject is a tuple, branches can match on tuple shape.
_ inside a pattern is a per-field wildcard - non-wildcard fields are compared with
=, results AND'd together:
{$mode unleashed}
program match_tuples;
var
p: (Integer, Integer);
begin
p := (0, 5);
match p of
(0, 0): WriteLn('origin');
(0, _): WriteLn('on Y axis');
(_, 0): WriteLn('on X axis');
_: WriteLn('somewhere else');
end;
ReadLn;
end.
Output:
match as expressionBranches can also produce values, so
match is usable on the RHS of an assignment. Expression form requires exhaustive coverage (
_ or
else):
{$mode unleashed}
program match_expr;
var
dir, x: Integer;
name, category: String;
begin
dir := 2;
name := match dir of
0: 'north';
1: 'east';
2: 'south';
3: 'west';
_: 'unknown';
end;
WriteLn(name);
x := 50;
category := match
x > 100: 'huge';
x > 10: 'medium';
_: 'small';
end;
WriteLn(category);
ReadLn;
end.
Output:
IDE supportLazarus Unleashed understands both features. Inline
var declarations show their inferred type in the identifier completion list - scalars, tuple literals (including nested and named), and tuple-returning function calls all render with a readable type. Dot-completion on a tuple variable lists its fields by name and by
_N.
match is treated as a scope like
case, so branches indent and are searched the same way.