Recent

Author Topic: FPC Unleashed (inline vars, statement expr, tuples, match, indexed/lazy labels)  (Read 35647 times)

creaothceann

  • Sr. Member
  • ****
  • Posts: 361
I was just commenting on 440bx's post:

The keyword, whatever it may end up being, can be used to create sections that consist of a single identifier.  IOW, it is still a section identifier that just like "var", "type" and "const" may be repeated as many times as desired/necessary and may consist of one or more declarations but, when inline, I suppose it would be forced to consist of 1 and only 1 declaration, just like "var" though, strictly speaking, I don't see why it should be limited to a single declaration, there doesn't seem to be a syntactically valid reason for that limitation.  Can someone with Delphi check if it supports an inline "var" section or is it limited to a single declaration per "var" ?

Fibonacci

  • Hero Member
  • *****
  • Posts: 950
  • Behold, I bring salvation - FPC Unleashed
Oh okay :-X Then consider my post as an extension of your comment on @440bx's post :)
« Last Edit: April 13, 2026, 09:25:38 pm by Fibonacci »
FPC Unleashed - inline vars, tuples, statement expressions, array equality, compound assignments, indexed/lazy labels, no-RTTI & more. ⭐ Star it on GitHub!

440bx

  • Hero Member
  • *****
  • Posts: 6493
Here is another simple feature I'd like to see implemented:

As in Ada, use @ to represent the LHS in the RHS.  For instance:
Code: Pascal  [Select][+][-]
  1.   <statements>
  2.   (SomePointer + (SomeVar + AValue))^ := @ + SomeOtherValue;  { += in C }
  3.   <statements>
  4.   { or }
  5.   (SomePointer + (SomeVar + AValue))^ := SomeExpression div @ + SomeOtherValue;  { no C equivalent }
  6.  
in that expression @ stands for "(SomePointer + (SomeVar + AValue))^" which is the LHS.

Best of all, this is really easy to implement, it does everything the C operators do and it can be used with _any_ operator.  IOW, the generalization is:
VariableExpression := @ <operator> <expression>
Where a VariableExpression may be a simple variable reference, a pointer reference that identifies a variable address or a qualified field member (class or record)

The use of @ for this purpose does not collide with any of its present uses because @ immediately followed by an operator is curently syntactically invalid.
« Last Edit: April 14, 2026, 02:58:46 pm by 440bx »
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

creaothceann

  • Sr. Member
  • ****
  • Posts: 361
@ is already used though... Why not the tilde character ~ instead?

PS: Afaik these are not used by Free Pascal: ` ~ ! | \ " ?

440bx

  • Hero Member
  • *****
  • Posts: 6493
@ is already used though... Why not the tilde character ~ instead?

PS: Afaik these are not used by Free Pascal: ` ~ ! | \ " ?
Yes, it is already used but also using it to represent the LHS does not conflict with current use and it also means there would not be any need to change the scanner to recognize a new character.  In addition to that, it is familiar to anyone who has used Ada (admittedly not that many programmers know about it but, it's better than nothing.)  Lastly, it's also much more visible than the characters that are not currently in use and, visibility in this case is very desirable.

IOW, @ is tried and true ;)
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

Fibonacci

  • Hero Member
  • *****
  • Posts: 950
  • Behold, I bring salvation - FPC Unleashed
New features: Tuples and match statement

Two 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.

Tuples

The 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

Code: Pascal  [Select][+][-]
  1. {$mode unleashed}
  2.  
  3. program tuples_basic;
  4.  
  5. var
  6.   p: (Integer, Integer);
  7.   c: (x, y: Integer);
  8. begin
  9.   p := (10, 20);             // positional literal
  10.   c := (x: 1, y: 2);         // named literal
  11.   c := (y: 2, x: 1);         // fields may be written in any order
  12.  
  13.   WriteLn(p._1, ' ', p._2);  // access by _N (1-based)
  14.   WriteLn(p[0], ' ', p[1]);  // or by constant index (0-based)
  15.   WriteLn(c.x, ' ', c.y);
  16.  
  17.   ReadLn;
  18. end.

Code: Text  [Select][+][-]
  1. 10 20
  2. 10 20
  3. 1 2

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

Code: Pascal  [Select][+][-]
  1. {$mode unleashed}
  2.  
  3. program tuples_destructuring;
  4.  
  5. function GetPair: (Integer, Integer);
  6. begin
  7.   Result := (10, 20);
  8. end;
  9.  
  10. function GetTriple: (Integer, Integer, Integer);
  11. begin
  12.   Result := (1, 2, 3);
  13. end;
  14.  
  15. var
  16.   count: Integer;
  17.   pairs: array of (String, Integer);
  18. begin
  19.   var (x, y) := GetPair;            // inline var destructuring
  20.   var (a, _, c) := GetTriple;       // wildcard _ skips that field
  21.   WriteLn('pair:   ', x, ' ', y);
  22.   WriteLn('triple: ', a, ' ', c);
  23.  
  24.   (x, y) := (y, x);                 // swap via multi-assignment
  25.   WriteLn('swap:   ', x, ' ', y);
  26.  
  27.   (count, _) := GetPair;            // multi-assign to existing vars
  28.   WriteLn('count=', count);
  29.  
  30.   pairs := [('one', 1), ('two', 2), ('three', 3)];
  31.   for var (key, val) in pairs do    // for-in destructuring
  32.     WriteLn(key, '=', val);
  33.  
  34.   ReadLn;
  35. end.

Output:

Code: Text  [Select][+][-]
  1. pair:   10 20
  2. triple: 1 3
  3. swap:   20 10
  4. count=10
  5. one=1
  6. two=2
  7. three=3

Parameters and tuple-aware Exit

Code: Pascal  [Select][+][-]
  1. {$mode unleashed}
  2.  
  3. program tuples_params;
  4.  
  5. procedure ShowNamed(c: (x, y: Integer));
  6. begin
  7.   WriteLn(c.x, '/', c.y);
  8. end;
  9.  
  10. procedure ShowDestructured((first, last): (String, String));
  11. begin
  12.   WriteLn(first, ' ', last);
  13. end;
  14.  
  15. // shorthand: field names and types in one bracket
  16. procedure ShowInline((x, y: Integer; name: String));
  17. begin
  18.   WriteLn(x, ' ', y, ' ', name);
  19. end;
  20.  
  21. function FindOrDefault(ok: Boolean): (Integer, String);
  22. begin
  23.   if ok then Exit(42, 'found');   // no extra parens around the tuple
  24.   Result := (-1, 'missing');
  25. end;
  26.  
  27. var
  28.   r: (Integer, String);
  29. begin
  30.   ShowNamed((x: 1, y: 2));
  31.   ShowDestructured(('John', 'Doe'));
  32.   ShowInline((10, 20, 'point'));
  33.  
  34.   r := FindOrDefault(True);
  35.   WriteLn(r._1, ' ', r._2);
  36.   r := FindOrDefault(False);
  37.   WriteLn(r._1, ' ', r._2);
  38.  
  39.   ReadLn;
  40. end.

Output:

Code: Text  [Select][+][-]
  1. 1/2
  2. John Doe
  3. 10 20 point
  4. 42 found
  5. -1 missing

Equality, ordering, Write auto-expand

Two 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.

Code: Pascal  [Select][+][-]
  1. {$mode unleashed}
  2.  
  3. program tuples_compare;
  4.  
  5. var
  6.   a, b: (Integer, Integer);
  7. begin
  8.   a := (1, 2);
  9.   b := (1, 3);
  10.   if a < b then WriteLn('a is smaller'); // 1 = 1, then 2 < 3
  11.   WriteLn(a);                            // auto-expand: "1, 2"
  12.  
  13.   ReadLn;
  14. end.

Output:

Code: Text  [Select][+][-]
  1. a is smaller
  2. 1, 2

Structural compatibility

Positional 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:

Code: Pascal  [Select][+][-]
  1. {$mode unleashed}
  2.  
  3. program tuples_compat;
  4.  
  5. type
  6.   TPoint = record
  7.     x, y: Integer;
  8.   end;
  9.  
  10. var
  11.   t: (Integer, Integer);
  12.   r: TPoint;
  13. begin
  14.   r := (10, 20);   // positional literal -> record
  15.   t := r;          // record -> positional tuple
  16.  
  17.   WriteLn('r: ', r.x, ' ', r.y);
  18.   WriteLn('t: ', t._1, ' ', t._2);
  19.  
  20.   ReadLn;
  21. end.

Output:

Code: Text  [Select][+][-]
  1. r: 10 20
  2. t: 10 20

Arrays, generics, nesting, typed constants

Code: Pascal  [Select][+][-]
  1. {$mode unleashed}
  2.  
  3. program tuples_misc;
  4.  
  5. generic function MakePair<A, B>(x: A; y: B): (A, B);
  6. begin
  7.   Result._1 := x;
  8.   Result._2 := y;
  9. end;
  10.  
  11. const
  12.   origin: (Integer, Integer) = (0, 0);
  13.   point:  (x, y: Integer) = (x: 10, y: 20);
  14.  
  15. var
  16.   pairs: array of (Integer, String);
  17.   n: (Integer, (String, Integer));
  18.   mp: (Integer, String);
  19. begin
  20.   pairs := [(1, 'a'), (2, 'b'), (3, 'c')];
  21.   WriteLn('pairs[0]: ', pairs[0]._1, ' ', pairs[0]._2);
  22.  
  23.   mp := specialize MakePair<Integer, String>(42, 'hi');
  24.   WriteLn('makepair: ', mp._1, ' ', mp._2);
  25.  
  26.   n := (5, ('label', 42));
  27.   WriteLn('nested:   ', n._1, ' ', n._2._1, ' ', n._2._2);
  28.  
  29.   WriteLn('origin:   ', origin._1, ' ', origin._2);
  30.   WriteLn('point:    ', point.x, ' ', point.y);
  31.  
  32.   ReadLn;
  33. end.

Output:

Code: Text  [Select][+][-]
  1. pairs[0]: 1 a
  2. makepair: 42 hi
  3. nested:   5 label 42
  4. origin:   0 0
  5. 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

Code: Pascal  [Select][+][-]
  1. {$mode unleashed}
  2.  
  3. program match_basic;
  4.  
  5. var
  6.   dir: Integer;
  7.   name: String;
  8. begin
  9.   dir := 2;
  10.   match dir of
  11.     0: name := 'north';
  12.     1: name := 'east';
  13.     2: name := 'south';
  14.     3: name := 'west';
  15.     _: name := 'unknown';   // catch-all; 'else' / 'otherwise' also work
  16.   end;
  17.   WriteLn(name);
  18.  
  19.   ReadLn;
  20. end.

Output:

Code: Text  [Select][+][-]
  1. south

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

Code: Pascal  [Select][+][-]
  1. {$mode unleashed}
  2.  
  3. program match_strings;
  4.  
  5. var
  6.   s: String;
  7. begin
  8.   s := 'hello';
  9.   match s of
  10.     'hello': WriteLn('greeting');
  11.     'bye':   WriteLn('farewell');
  12.     _:       WriteLn('unknown');
  13.   end;
  14.  
  15.   ReadLn;
  16. end.

Output:

Code: Text  [Select][+][-]
  1. greeting

Comma-separated patterns (OR)

Code: Pascal  [Select][+][-]
  1. {$mode unleashed}
  2.  
  3. program match_comma;
  4.  
  5. var
  6.   x: Integer;
  7. begin
  8.   x := 3;
  9.   match x of
  10.     1, 2, 3: WriteLn('small');
  11.     4, 5, 6: WriteLn('medium');
  12.     _:       WriteLn('big');
  13.   end;
  14.  
  15.   ReadLn;
  16. end.

Output:

Code: Text  [Select][+][-]
  1. small

Condition-based: match without "of"

No subject - every branch is just a boolean. A clean replacement for if/else if ladders:

Code: Pascal  [Select][+][-]
  1. {$mode unleashed}
  2.  
  3. program match_condition;
  4.  
  5. var
  6.   x: Integer;
  7. begin
  8.   x := 50;
  9.   match
  10.     x > 100: WriteLn('huge');
  11.     x > 10:  WriteLn('medium');
  12.     x > 0:   WriteLn('small');
  13.     _:       WriteLn('non-positive');
  14.   end;
  15.  
  16.   ReadLn;
  17. end.

Output:

Code: Text  [Select][+][-]
  1. medium

match all - fallthrough mode

match 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:

Code: Pascal  [Select][+][-]
  1. {$mode unleashed}
  2.  
  3. program match_all;
  4.  
  5. procedure Log(const s: String);
  6. begin
  7.   WriteLn(s);
  8. end;
  9.  
  10. var
  11.   x: Integer;
  12.   hits, totalFives, threes, other: Integer;
  13. begin
  14.   hits := 0; totalFives := 0; threes := 0; other := 0;
  15.  
  16.   x := 5;
  17.   match all x of
  18.     5: Inc(hits);
  19.     5: Inc(totalFives);
  20.     3: Inc(threes);
  21.     _: Inc(other);      // always runs
  22.   end;
  23.   WriteLn('hits=', hits, ' totalFives=', totalFives, ' threes=', threes, ' other=', other);
  24.  
  25.   x := 50;
  26.   match all
  27.     x > 100: Log('big');
  28.     x > 10:  begin Log('medium'); leave; end;
  29.     x > 0:   Log('positive');  // never reached due to leave
  30.   end;
  31.  
  32.   ReadLn;
  33. end.

Output:

Code: Text  [Select][+][-]
  1. hits=1 totalFives=1 threes=0 other=1
  2. medium

Tuple patterns with wildcards

When 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:

Code: Pascal  [Select][+][-]
  1. {$mode unleashed}
  2.  
  3. program match_tuples;
  4.  
  5. var
  6.   p: (Integer, Integer);
  7. begin
  8.   p := (0, 5);
  9.   match p of
  10.     (0, 0): WriteLn('origin');
  11.     (0, _): WriteLn('on Y axis');
  12.     (_, 0): WriteLn('on X axis');
  13.     _:      WriteLn('somewhere else');
  14.   end;
  15.  
  16.   ReadLn;
  17. end.

Output:

Code: Text  [Select][+][-]
  1. on Y axis

match as expression

Branches can also produce values, so match is usable on the RHS of an assignment. Expression form requires exhaustive coverage (_ or else):

Code: Pascal  [Select][+][-]
  1. {$mode unleashed}
  2.  
  3. program match_expr;
  4.  
  5. var
  6.   dir, x: Integer;
  7.   name, category: String;
  8. begin
  9.   dir := 2;
  10.   name := match dir of
  11.     0: 'north';
  12.     1: 'east';
  13.     2: 'south';
  14.     3: 'west';
  15.     _: 'unknown';
  16.   end;
  17.   WriteLn(name);
  18.  
  19.   x := 50;
  20.   category := match
  21.     x > 100: 'huge';
  22.     x > 10:  'medium';
  23.     _:       'small';
  24.   end;
  25.   WriteLn(category);
  26.  
  27.   ReadLn;
  28. end.

Output:

Code: Text  [Select][+][-]
  1. south
  2. medium



IDE support

Lazarus 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.
« Last Edit: April 14, 2026, 06:41:47 pm by Fibonacci »
FPC Unleashed - inline vars, tuples, statement expressions, array equality, compound assignments, indexed/lazy labels, no-RTTI & more. ⭐ Star it on GitHub!

440bx

  • Hero Member
  • *****
  • Posts: 6493
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

Code: Pascal  [Select][+][-]
  1. {$mode unleashed}
  2.  
  3. program match_basic;
  4.  
  5. var
  6.   dir: Integer;
  7.   name: String;
  8. begin
  9.   dir := 2;
  10.   match dir of
  11.     0: name := 'north';
  12.     1: name := 'east';
  13.     2: name := 'south';
  14.     3: name := 'west';
  15.     _: name := 'unknown';   // catch-all; 'else' / 'otherwise' also work
  16.   end;
  17.   WriteLn(name);
  18.  
  19.   ReadLn;
  20. end.
In that construction, is it possible to make the code for "3:" execute too ?. IOW, is there a way to force execution to fall through the next case completely ignoring the condition the code is associated with (as in C) ?

The reason to have fall through is to have a stack of additive code where the one and only one entry point is determined by a matching condition.  That's what the C switch enables the programmer to build, unfortunately, that desirable feature comes at the expense of having to have explicit breaks in the executable groups in spite of the fact that additive code is not the most common case (but, still common enough that the feature should exist.)

Condition-based: match without "of"

No subject - every branch is just a boolean. A clean replacement for if/else if ladders:

Code: Pascal  [Select][+][-]
  1. {$mode unleashed}
  2.  
  3. program match_condition;
  4.  
  5. var
  6.   x: Integer;
  7. begin
  8.   x := 50;
  9.   match
  10.     x > 100: WriteLn('huge');
  11.     x > 10:  WriteLn('medium');
  12.     x > 0:   WriteLn('small');
  13.     _:       WriteLn('non-positive');
  14.   end;
  15.  
  16.   ReadLn;
  17. end.
Superb!! if else ladders are disgusting.  This "match" construction is infinitely cleaner.

ETA:

One concern though, the underscore is a valid character in an identifier, the "_:" should cause the _scanner_ to consider it (identify it as) an identifier when in that context, it is not.  For instance, presuming there is an identifier named "_" and the conditions are "_ > 100", "_ > 10", "_ > 0" and "_ { spaces or comment } :", the scanner can't decide what the underscore means.



« Last Edit: April 14, 2026, 07:02:34 pm by 440bx »
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

creaothceann

  • Sr. Member
  • ****
  • Posts: 361
In that construction, is it possible to make the code for "3:" execute too ?. IOW, is there a way to force execution to fall through the next case completely ignoring the condition the code is associated with (as in C) ?

The reason to have fall through is to have a stack of additive code where the one and only one entry point is determined by a matching condition.  That's what the C switch enables the programmer to build, unfortunately, that desirable feature comes at the expense of having to have explicit breaks in the executable groups in spite of the fact that additive code is not the most common case (but, still common enough that the feature should exist.)

You can use indexed goto for fallthrough. Silly example (could've been done with if):

Code: Pascal  [Select][+][-]
  1. var List : TStringList = NIL;
  2.  
  3. goto InitList[List = NIL];
  4. InitList[True ]:  List := TStringList.Create;
  5. InitList[False]:  List.Add('abcd');


One concern though, the underscore is a valid character in an identifier, the "_:" should cause the _scanner_ to consider it (identify it as) an identifier when in that context, it is not.  For instance, presuming there is an identifier named "_" and the conditions are "_ > 100", "_ > 10", "_ > 0" and "_ { spaces or comment } :", the scanner can't decide what the underscore means.

Yeah, I think '*' might be a better character.
« Last Edit: April 14, 2026, 08:02:19 pm by creaothceann »

440bx

  • Hero Member
  • *****
  • Posts: 6493
You can use indexed goto for fallthrough. Silly example (could've been done with if):
I would _never_ use a goto for that reason.  There are much better solutions than that.


Yeah, I think '*' might be a better character.
Personally, I would enforce the use of the "otherwise" keyword.  Not "else" because that could cause another conflict with the "if" statement (there is already a conflict with the "else" when the "if" occurs in a "case" statement, we don't need another problem like that one.)

All that said, using "*" seems possible.  I can't think of it creating an ambiguity.
FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

Fibonacci

  • Hero Member
  • *****
  • Posts: 950
  • Behold, I bring salvation - FPC Unleashed
In that construction, is it possible to make the code for "3:" execute too ?. IOW, is there a way to force execution to fall through the next case completely ignoring the condition the code is associated with (as in C) ?

The reason to have fall through is to have a stack of additive code where the one and only one entry point is determined by a matching condition.  That's what the C switch enables the programmer to build, unfortunately, that desirable feature comes at the expense of having to have explicit breaks in the executable groups in spite of the fact that additive code is not the most common case (but, still common enough that the feature should exist.)

No. match all runs every branch whose condition matches, and there's no way to force-enter one whose condition is false.

It could be added as a counterpart to leave - say fall - which would unconditionally jump into the next branch. leave exits the match early, fall would override the next branch's condition. Or it could live under a separate keyword instead of match altogether (e.g. fall instead of match).

But honestly, is it really that needed? You can glue the same effect together with labels and goto (and in unleashed you can either declare indexed labels, or skip the label section entirely and just use them - lazy labels), and IMHO it's not worth adding a language construct for it. That said - if you can convince me it's genuinely needed and not just nice-to-have, I'll add it :D



One concern though, the underscore is a valid character in an identifier, the "_:" should cause the _scanner_ to consider it (identify it as) an identifier when in that context, it is not.  For instance, presuming there is an identifier named "_" and the conditions are "_ > 100", "_ > 10", "_ > 0" and "_ { spaces or comment } :", the scanner can't decide what the underscore means.

Code: Pascal  [Select][+][-]
  1. var s  := 'test';
  2. var _s := 'test';
  3. var _  := 'foo';
  4.  
  5. match all s of
  6.   'test': writeln('match 1');
  7.       _s: writeln('match 2');
  8.        _: writeln('match anything');
  9. end;

Output:

Code: Text  [Select][+][-]
  1. match 1
  2. match 2
  3. match anything

The only thing you lose: a variable literally named _ can't be used as a label inside match - bare _ is the wildcard, period. Whoever names a variable _ and then wants to match against it - well, that's their problem. A programmer has to understand how a given syntax or keyword works - learn it, understand it, remember it, and then use it.
« Last Edit: April 14, 2026, 08:31:33 pm by Fibonacci »
FPC Unleashed - inline vars, tuples, statement expressions, array equality, compound assignments, indexed/lazy labels, no-RTTI & more. ⭐ Star it on GitHub!

440bx

  • Hero Member
  • *****
  • Posts: 6493
It could be added as a counterpart to leave - say fall - which would unconditionally jump into the next branch.
That would be the right way and, "fall" should not only be applicable to "match", it should also apply to "case" since the difference between match and case is that one accepts variables while the other one only constants but, they essentially have the same execution graph (except for match all.)

But honestly, is it really that needed? You can glue the same effect together with labels and goto (and in unleashed you can either declare indexed labels, or skip the label section entirely and just use them - lazy labels), and IMHO it's not worth adding a language construct for it. That said - if you can convince me it's genuinely needed and not just nice-to-have, I'll add it :D
It is most definitely needed.  gotos are likely the absolutely worst way of implementing that execution flow.  The reason goto(s) should _never_ be used is because the target of a goto can be anywhere whereas the target of "fall" is predetermined to be the code that is _immediately_ underneath it.  IOW and, to make it crystal clear, the target of "fall" isn't something the programmer has to search/hunt for and, in addition to that, its applicable domain is pre-established and very narrow unlike a goto which can go anywhere even to code in a different function several thousand lines away from where the goto appears.  There is only one thing that is worse than a goto and that is a goto whose destination varies at runtime, that's adding crime to insult and injury.  The Ted Bundy of programming, just what a programming language needs to be successful.

Succinctly, the reason it is needed is because it enforces locality, something the "goto" does not.



The only thing you lose: a variable literally named _ can't be used as a label inside match - bare _ is the wildcard, period. Whoever names a variable _ and then wants to match against it - well, that's their problem. A programmer has to understand how a given syntax or keyword works - learn it, understand it, remember it, and then use it.
That means you've essentially turned the "_" to be a keyword when used in a "match" statement but, in other places, it's not a keyword, it's just a variable name.  That's inconsistent and ambiguous. Honestly, it's appalling!  An identifier is either a keyword or it isn't.  PL/1 indulged in that non-sense and programmers didn't view it favorably and quite likely one of the many reasons PL/1 went nowhere (that in spite of being a very powerful language.)

Recommendation: get rid of that ambiguous use of "_".  the keyword "otherwise" would be a good choice as it is already in use in the language and using it in "match" would be consistent with its current usage and, it would not cause any ambiguities.  To make it crystal clear: allowing the use of "_" to mean "otherwise" is a terrible idea, among the worst possible ones.  Don't ruin a good feature with stuff like that.

FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

creaothceann

  • Sr. Member
  • ****
  • Posts: 361
[...] unlike a goto which can go anywhere even to code in a different function several thousand lines away from where the goto appears.

A goto that jumps "several thousand lines away" is the "fault" of the programmer, not of goto itself. (At the very least it should be accompanied with a comment.) Spaghetti code can be created with many languague features.

I don't think you can jump to other functions unless very specific modes / modeswitches are activated. FPC Unleashed's indexed goto also only allows jumps to the same block, afaik.

As for fallthrough, I agree that goto is a suboptimal, but only because it adds an instruction when there should be no instruction at all.

440bx

  • Hero Member
  • *****
  • Posts: 6493
A goto that jumps "several thousand lines away" is the "fault" of the programmer, not of goto itself. (At the very least it should be accompanied with a comment.) Spaghetti code can be created with many languague features.
A properly designed language should NOT allow that under any circumstances.  A language that allows that is simply stated very very poorly designed and, a programmer who uses gotos is in dire need of learning a few things.  I really mean that.

If a programmer wants a goto then he/she should drop to assembly, that way, the programmer who reads the code knows that everything goes, no limits because it's assembler.

FPC v3.2.2 and Lazarus v4.0rc3 on Windows 7 SP1 64bit.

Fibonacci

  • Hero Member
  • *****
  • Posts: 950
  • Behold, I bring salvation - FPC Unleashed
@440bx, quick one - what about this case?

Code: Pascal  [Select][+][-]
  1. {$mode unleashed}
  2.  
  3. function GetPixel: (Byte, Byte, Byte, Byte); // R, G, B, A
  4. begin
  5.   Result := (255, 128, 64, 200);
  6. end;
  7.  
  8. var
  9.   red, alpha: Byte;
  10. begin
  11.   (red, _, _, alpha) := GetPixel; // only red and alpha
  12.   WriteLn(red, ' ', alpha);
  13. end.
  14.  

Here _ is just a "skip this field" in tuple destructuring, nothing to do with match. Should we kill it here too? _ can be a variable name after all.

You're suggesting otherwise, but honestly - first, it's way too long a word, I don't like it, I'd rather have else. Second, if we had to pick something, I'd go with *.



Edit: ok, * is out. Forgot Pascal has (* ... *) comments, so (* already means "start of comment":

Code: Pascal  [Select][+][-]
  1. (*, y) := GetPair; // oops, this is a comment

So yeah, nevermind * :)
« Last Edit: April 14, 2026, 11:25:24 pm by Fibonacci »
FPC Unleashed - inline vars, tuples, statement expressions, array equality, compound assignments, indexed/lazy labels, no-RTTI & more. ⭐ Star it on GitHub!

Fibonacci

  • Hero Member
  • *****
  • Posts: 950
  • Behold, I bring salvation - FPC Unleashed
Choose (poll):

1) add fall as a statement usable inside a branch's begin..end - call it anywhere in the block to drop into the next branch. It would execute that next branch unconditionally, but only that one - the branch after it goes back to normal condition checking, unless fall is used again

Code: Pascal  [Select][+][-]
  1. match x of
  2.   1: begin
  3.        WriteLn('one');
  4.        fall; // drop into branch 2 unconditionally
  5.      end;
  6.   2: WriteLn('two');    // runs because of fall from branch 1
  7.   3: WriteLn('three');  // back to normal condition check
  8. end;
  9.  

2) add a whole new fall construct that works exactly like match, but automatically falls through to the next branch, C-style

Code: Pascal  [Select][+][-]
  1. fall x of
  2.   1: WriteLn('one');
  3.   2: WriteLn('two');
  4.   3: WriteLn('three');
  5. end;
  6. // for x=1 prints: one, two, three
  7. // leave exits early
  8.  

3) add fall as a modifier for match - it would look like this: match fall x of ... - meaning the whole match falls through by default

Code: Pascal  [Select][+][-]
  1. match fall x of
  2.   1: WriteLn('one');
  3.   2: WriteLn('two');
  4.   3: WriteLn('three');
  5. end;
  6. // for x=1 prints: one, two, three
  7. // leave exits early
  8.  
FPC Unleashed - inline vars, tuples, statement expressions, array equality, compound assignments, indexed/lazy labels, no-RTTI & more. ⭐ Star it on GitHub!

 

TinyPortal © 2005-2018