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

Request: calling a function only when a parameter is not null. #360

Open
MichaelRFairhurst opened this issue May 17, 2019 · 14 comments
Open
Labels
request Requests to resolve a particular developer problem

Comments

@MichaelRFairhurst
Copy link

MichaelRFairhurst commented May 17, 2019

It's awkward to call a function when a parameter is not null, such as:

String toPrint;

if (toPrint != null) {
  print(toPrint);
}

This is significantly more work than calling a method when the target is not null:

Printer printer;

printer?.print(toPrint);

So naturally we developers have gotten privileged and want both :)

@MichaelRFairhurst MichaelRFairhurst added the request Requests to resolve a particular developer problem label May 17, 2019
@MichaelRFairhurst
Copy link
Author

I wish Dart's null were the Option type, and we had a single-parameter-closure shorthand.

Then you could just write

toPrint.exists(=> print(_))

@MichaelRFairhurst
Copy link
Author

MichaelRFairhurst commented May 17, 2019

This could also be easily solved by macros, and the release of macros could include a macro for this.

Rust style macro (I think):

exists!(toPrint, print(_))

@MarvinHannott
Copy link

MarvinHannott commented May 19, 2019

This could also be easily solved by macros, and the release of macros could include a macro for this.

Rust style macro (I think):

exists!(toPrint, print(_))

But Rust can do macro expansion and analysis at compile time. That is not a good approach for a language running on a virtual machine. Also, Dart's appeal stems from its simplicity. Complex meta programming is the opposite of simplistic. Only a hand full of Rust gurus even touch macros.

I wish Dart's null were the Option type, and we had a single-parameter-closure shorthand.

Then you could just write

toPrint.exists(=> print(_))

And that gets unwieldy pretty quickly. I have written my Rust. While it might be sensible to have Options and Results in a systems programming language, on a higher level they are clutter. And in the real world you only have to perform few null checks.

But to your original problem: With the null aware operator you could just wirte toPrint ?? print(toPrint).

@lrhn
Copy link
Member

lrhn commented May 20, 2019

Sadly toPrint ?? print(toPrint) does the opposite of what was asked for - it calls printonly when toPrint is null.

A shorter null guard, like the one requested in #361, would make the existing test + call shorter: toPrint !! print(toPrint). It will even work well with typing, the static type of e1 !! e2 would be the static type of e2 made nullable.

Another option is to make an operator which only applies to arguments, and which short-circuit the surrounding call if the value is null, say: o.foo(bar, ??baz, qux). (It's prefix rather than infix, so it's not the normal null-guard).
This would then evaluate o.foo, evaluate bar, evaluate baz, check that value, and if it is null, then the function invocation evaluates to null.
I don't like that, for a number of reasons:

  • It only works for function parameters (not operator operands).
  • It only goes one deep, so foo(bar(??baz)) would not be able to short-circuit the foo invocation, and foo(??bar(??baz)) is a different thing which also depends on the bar return value.
  • It's non-local. I can't see that foo(..........., ??bar........) might be null unless I notice the embedded ??.

So, I'd prefer a proper delimited null-escape. Say, something like (? foo(bar(?:baz))). Here the ?: evaluates its operand, and if null, it short-circuits to the closest enclosing (?...) expressin and makes that evaluate to null. (Might need named braces and null-breaks too, so you can skip further out than the closest enclosing (?....).)
The syntax is obviously bad. Perhaps the (?...) delimiter can be postifx (easier to write, harder to read).
Consider foo(bar(baz?!))!? The ?! takes an expression that may be null, then makes it locally non-null by shortcircuiting the null to the !? suffix which takes something and makes it nullable. (Syntax doesn't needs to be reserved because foo!?.bar() will otherwise be valid).

@MichaelRFairhurst
Copy link
Author

But Rust can do macro expansion and analysis at compile time. That is not a good approach for a language running on a virtual machine.

Dart already has compile-time constants. I don't think that proves that macros wouldn't have problems, but, I think many people would have made the same arguments against const and that const is actually very beneficial to dart (people constantly want it expanded to do more things!)

Also, Dart's appeal stems from its simplicity....Only a hand full of Rust gurus even touch macros.

I think ideally we have a handful of gurus making simple macros. But you're right, once we release something, its hard to know how it will be abused.

@MichaelRFairhurst
Copy link
Author

Any solution we have here that's syntax based will likely get really soupy really quick (even !!).

Named operations are much better as the number of operations increases. If we want to do names, a la

ifNotNull(foo, expr(_))`

then we either have to add macros or built-ins like assert (which is basically a macro built into dart).

One more thought, if we add pattern matching, then it might be best to do (no idea on proposed syntaxes)

toPrint =>{ !null => print(toPrint) }

This would be soupy but at least lean on existing concepts.

@leafpetersen
Copy link
Member

leafpetersen commented May 20, 2019

toPrint.exists(=> print(_))

Just a quick plug that extension methods will allow you to write this (well, except for the implicit parameter lambda):

extension Exists<S, T> on T? {
  S? exists(S Function(T) f) => (this == null) ? null : f(this);
}
void test() {
  toPrint.exists(print); // calls print on toPrint iff toPrint is non-null
}

@MichaelRFairhurst
Copy link
Author

@leafpetersen

that is fantastic!

@jamesderlin
Copy link

Even without extensions, I think you could do:

R applyIfExists<R, T>(R Function(T) f, T arg) => (arg == null) ? null : f(arg);

which I personally think is more readable/familiar than toPrint.exists(print), which inverts typical order.

@lrhn
Copy link
Member

lrhn commented Jun 17, 2019

Another extension method option is to extend a unary function:

extension Exists<T, R> on R Function(T) {
  R? ifArg(T? argument) => argument == null ? null : this(argument);
}

void test(Object? toPrint) {
  print.ifArg(toPrint);
}

@Wikiwix
Copy link

Wikiwix commented Oct 31, 2024

@leafpetersen

extension Exists<S, T> on T? {
  S? exists(S Function(T) f) => (this == null) ? null : f(this);
}

In Dart 3.5.3 as well as in the Beta and Main channels this leads to the following compile time error:

The argument type 'T?' can't be assigned to the parameter type 'T'.

@jakemac53
Copy link
Contributor

jakemac53 commented Oct 31, 2024

In Dart 3.5.3 as well as in the Beta and Main channels this leads to the following compile time error:

The argument type 'T?' can't be assigned to the parameter type 'T'.

this can't be promoted so you would need to either use this! or introduce a new variable using for instance a switch expression:

extension Exists<S, T> on T? {
  S? exists(S Function(T) f) => switch(this) {
    null => null,
    T self => f(self),
  };
}

@FMorschel
Copy link

Related to #190

@tatumizer
Copy link

Pipe operator would help here: toPrint? |> print

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
request Requests to resolve a particular developer problem
Projects
None yet
Development

No branches or pull requests

9 participants