Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

try <pattern> #3179

Open
ds84182 opened this issue Jun 30, 2023 · 4 comments
Open

try <pattern> #3179

ds84182 opened this issue Jun 30, 2023 · 4 comments
Labels
feature Proposed language feature that solves one or more problems patterns Issues related to pattern matching.

Comments

@ds84182
Copy link

ds84182 commented Jun 30, 2023

Supports statement-level refutable variable bindings with optional else clause, which may exit.

try <refutable pat> = <expr> [else <statement>]?;

The pattern is always run to completion, binding as many variables as possible.

On failure, variable bindings that failed to match are bound to their default or null.

If the else clause exits, flow analysis determines variable bindings are non-null/default.

To make it symmetric, introduce patterns that behave like their statement counterparts:

  • try <refutable pat> prefix: Turns a refutable pattern irrefutable. Is equivalent to rewriting variable bindings to var <ident> else null.
  • <refutable pat> else <expr> suffix: On fail, irrefutably match against a compile-time constant value. Issue a compile time error if the constant does not irrefutably match.
  • [var | final | Type] <ident> else <expr> suffix: A special case of the else suffix that relaxes typing. On the happy path the original or inferred type is used. On the sad path the type of the default value is used. If either path is taken, the resulting type is UP(happy ^^, sad q_q).

❤ Behavior change wishlist ❤:

  • <pat> || <pat> becomes irrefutable if the RHS is irrefutable. Not needed but would be neat.

Syntax examples:

// If the list is null or does not have enough elements, the variables are null.
try var [first, second, third, ...] = nullableList;

// If the list is null or does not have enough elements, match a constant list instead.
// Pattern is irrefutable so it does not need the try prefix.
var [first, second, third, ...] else [1, 2, 3] = nullableList;

// Parses a JSON map.
// Latitude and longitude are bound to null if the location isn't a list of two doubles.
// Journey name is bound to null if malformed.
// Label is bound to "No label" if it isn't a String.
// Index is required, otherwise matching fails & the FormatException is thrown.
try var {
  "location": try [double lat, double lon],
  "journey": try {"name": String journeyName},
  "label": try String label else "No label",
  "index": int index,
} = json else throw FormatException(...);

// Tries to extract width and height from nullable size.
// Binds NaN on failure.
// Pattern is irrefutable so it does not need the try prefix.
var Size(:width else double.nan, :height else double.nan) = nullableSize;

// Checks a json map for two values, and otherwise produces an error message
// with details on which entries were absent.
// This demonstrates using a value with a different type in the case where it does
// not match.
try var {
  "name": String name,
  "age": int age,
  "entry_should_exist_but_nullable": List? shouldExist else #notFound,
} = json else throw FormatException([
    if (name == null) 'Invalid name',
    if (age == null) 'Invalid age',
    // is (Symbol | List?) here. flow analysis understands the union type.
    if (shouldExist == #notFound) 'Nullable entry does not exist',
].join('\n'));

// is List? here
shouldExist?.forEach(print);
@ds84182 ds84182 added the feature Proposed language feature that solves one or more problems label Jun 30, 2023
@ds84182
Copy link
Author

ds84182 commented Jun 30, 2023

I should have come up with better examples, but I was trying to communicate how the various pieces operate at their maximum (not that everything here should be used all at once!)

Some more down to earth examples:

// Emptying a linked list
while (true) {
  try var item? = list.first else break;
  item.unlink();
}

// Delay rendering a ListTile in a Stateful Flutter Widget until all data has loaded.
try var title? = this.title else return buildLoadingWidget();
try var description? = this.description else return buildLoadingWidget();

return ListTile(title: Text(title), subtitle: Text(description));

The portion to provide symmetry within patterns itself could be skipped, but I wanted to present the idea of a refutable -> irrefutable escape hatch.

@munificent
Copy link
Member

There are a lot of interesting ideas here!

I don't find automatically using null when any kind of match failure occurs very compelling. That seems like it could make it too hard to realize that the match failed for some reason you didn't expect.

Your examples around map patterns are a pain point I have noticed where you'd like to use a map destructuring in an irrefutable context and fail gracefully on absent keys. I think I like your proposal on #2496 better for that, though.

This example:

  try var item? = list.first else break;

Looks very similar to #2537 to me. Using try is pretty clever because it does the right job of implying "may not succeed". But, at the same time, that keyword has a very strong association with "throw an exception and unwind", which is the exact opposite of what's going on here. The point of this keyword would be to turn match failure (which throws in an irrefutable context) and make it not throw.

@munificent munificent added the patterns Issues related to pattern matching. label Jun 30, 2023
@munificent
Copy link
Member

  • <pat> || <pat> becomes irrefutable if the RHS is irrefutable. Not needed but would be neat.

Can you show an example of how this is useful? None are coming to mind.

@ds84182
Copy link
Author

ds84182 commented Jul 5, 2023

Can you show an example of how this is useful? None are coming to mind.

I can't remember the specific place where I encountered this,. I think it was selecting between two fields for a variable binding where one field was optional (but not null) and the other was not.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Proposed language feature that solves one or more problems patterns Issues related to pattern matching.
Projects
None yet
Development

No branches or pull requests

2 participants