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

Non null coalescing operator #2165

Closed
hamishjohnson opened this issue Mar 23, 2022 · 8 comments
Closed

Non null coalescing operator #2165

hamishjohnson opened this issue Mar 23, 2022 · 8 comments
Labels
feature Proposed language feature that solves one or more problems state-duplicate This issue or pull request already exists

Comments

@hamishjohnson
Copy link

hamishjohnson commented Mar 23, 2022

I find myself having to write this type of code quite often when dealing with nullable values:

TypeX != null ? TypeY : null;

A quick example I found, in with firebase auth:

Future<UserClaims?> getClaims(User firebaseUser) async {
  final idTokenResult = await firebaseUser.getIdTokenResult();

  return idTokenResult.claims != null
      ? UserClaims.fromJson(idTokenResult.claims!)
      : null;
}

I thought it would be nice to have a non-null coalescing operator to deal with this type of scenario. So, opposed to the null coalescing operator, if it evaluates the item on the left to be null, it returns null. But if it evaluates the left side to be non-null, then it continues on and returns the right side. Perhaps it would look something like this:

TypeX !? TypeY

Or, with my example:

Future<UserClaims?> getClaims(User firebaseUser) async {
  final idTokenResult = await firebaseUser.getIdTokenResult();

  return idTokenResult.claims !? UserClaims.fromJson(idTokenResult.claims!)
}

n.b. ?! could also be the operator. For some reason, it looks better and feels nicer to type, though it's less clear

@hamishjohnson hamishjohnson added the feature Proposed language feature that solves one or more problems label Mar 23, 2022
@ykmnkmi
Copy link

ykmnkmi commented Mar 23, 2022

Will conflict with ?: syntax: expr!? expr // : expr;.

@hamishjohnson
Copy link
Author

Would ?! conflict with anything?

@eernstg
Copy link
Member

eernstg commented Mar 24, 2022

We could do the following in current Dart:

// An identity method on all non-null objects.
extension Id on Object {
  X id<X>(X x) => x;
}

// Example
void main() {
  var i = 1 as int?; // Obtain an expression whose type is nullable.
  String? s = i?.id('Whatever');
  // Compare: i == null ? null : 'Whatever';
}

It's a bit verbose (using 6 chars, ?.id(), rather than two), but it is shorter than the original ?: expression, and it does have some useful properties:

  • If the receiver (corresponding to the left hand operand of an operator like ?!) is not nullable then using ?. is a warning, so we won't use this construct when it is not needed. Conversely, e1.id(e2) is a compile-time error when e1 is nullable, so we won't forget the ? when the construct is actually needed.
  • The type of the expression is T? where T is the type of the actual argument (the right hand operand), as it should be.
  • The actual argument will not be evaluated in the case where the receiver evaluates to null, so we get the side effects if and only if we should have them.
  • id will surely be inlined, so there's no extra cost at run time.

@mateusfccp
Copy link
Contributor

@eernstg Not related to the issue, but to your last comment. You say that id will be inlined. Is it because it is an extension method and thus static?

@eernstg
Copy link
Member

eernstg commented Mar 24, 2022

Yes, extension methods are resolved statically, and this means that the compiler knows exactly which snippet of code the invocation will execute, so we can soundly inline it (if the given function is simple enough, and this one is ;-).

@Schwusch
Copy link

@hamishjohnson Taking inspiration from Kotlin scope functions, I usually solve this kind of problems this way, applied to your example:

extension ScopeFunctions<T> on T {
  /// Calls the specified function [block] with `this` value
  /// as its argument and returns its result.
  R let<R>(R Function(T) block) => block(this);
}

Future<UserClaims?> getClaims(User firebaseUser) async {
  final idTokenResult = await firebaseUser.getIdTokenResult();

  return idTokenResult.claims?.let(UserClaims.fromJson);
}

@hamishjohnson
Copy link
Author

Those are nice solutions, didn't think to try to solve it that way.

@lrhn
Copy link
Member

lrhn commented Apr 11, 2022

There is a number of similar requests, the earliest probably #360, so I'll close this as a duplicate.
(It's not a bad idea. just also not a trivial one, so we are keeping some of the issues open.)

@lrhn lrhn closed this as completed Apr 11, 2022
@lrhn lrhn added the state-duplicate This issue or pull request already exists label Apr 11, 2022
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 state-duplicate This issue or pull request already exists
Projects
None yet
Development

No branches or pull requests

6 participants