Skip to content

add hasValue for null parameters #1064

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

Closed
tbm98 opened this issue Jul 3, 2020 · 7 comments
Closed

add hasValue for null parameters #1064

tbm98 opened this issue Jul 3, 2020 · 7 comments
Labels
feature Proposed language feature that solves one or more problems

Comments

@tbm98
Copy link

tbm98 commented Jul 3, 2020

currently in the copyWith function, if I intentionally want to copy a content with null it will retrieve the old data instead. For example:

ThemeData copyWith(headline){
    return ThemeData(headline ?? old.headline); // can't copy with null value
}

I would like to have an additional method to check if that parameter is passed or not. For example:

ThemeData copyWith (headline) {
     return ThemeData (headline.hasValue ? headline : old.headline); // can copy with null if it was passed, or not if wasn't passed.
} 
@tbm98 tbm98 added the feature Proposed language feature that solves one or more problems label Jul 3, 2020
@tbm98
Copy link
Author

tbm98 commented Jul 4, 2020

I have been fix this by use optional parameters

@lrhn
Copy link
Member

lrhn commented Jul 4, 2020

Checking for whether an optional parameter was passed or not is actually a feature Dart has already had.
It was attempted prior to Dart 1.0, and it turned out that it added more complications than it removed.

Currently, to forward a call to another function with the same signature, you can simply pass the parameter values. The second function will not be able to distinguish whether it got the default value because it was passed deliberately or because it wasn't passed at all.

int forward({int a, int b, int c}) => forwardee(a: a, b: b, c: c);

If functions has that ability, then forwarding will require passing the exact same parameters, not just the same values. Then forwarding three optional named parameters would require eight different calls:

int forward({int a, int b, int c}) =>
  if (?a) {
    if (?b) {
      if (?c) return forwardee(a: a, b: b, c: c);
      return forwardee(a: a, b: b);
    }
    if (?c) return forwardee(a: a, c: c);
    return forwardee(a: a);
  }
  if (?b) {
    if (?c) return forwardee(b: b, c: c);
    return forwardee(b: b);
  }
  if (?c) return forwardee(c: c);
  return forwardee();
}

The solution to that would be an ability to conditionally pass an argument:

int forward({int a, int b, int c}) =>
  forwardee(if (?a) a: a, if (?b) b: b, if (?c) c: c);

That increases the complexity. Even with that, users are likely to forget and make mistakes.

It's not impossible, but it's probably not worth it. (At least, I'd make some more changes to parameters in the same change).

@Cat-sushi
Copy link

cf. dart-lang/sdk#9097

@tbm98
Copy link
Author

tbm98 commented Jul 5, 2020

@lrhn So how can I assign a field to null inside the copyWith function?

@lrhn
Copy link
Member

lrhn commented Jul 5, 2020

You can't. That is annoying, but not something we have considered a big enough pain to make it worth a language change.

What you can do is to have two parameters:

  Foo copyWith({Bar? bar, bool clearBar = false}) => Foo(bar: clearBar ? null : (bar ?? this.bar));

The two parameters are really mutually exclusive, but there is no good way to ensure that you pass only one of them.

Another option (pun intended) is to use Optional for the paramter:

 Foo copyWith({Optional<Bar>? bar}) => Foo(bar: bar == null ? this.bar : bar.isPresent ? bar.value : null);`

If you are already using Optional (perhaps the one from package:quiver), then that might make sense.

The real problem here is that Dart's nullability is not nested. You can't have Bar?? as a type, and distinguish the two null values, which is really what this would require.
Allowing the "was parameter set?" query is introducing a second level of null-ness, but they say, you should either have zero, one or an infinite amount of any feature. Introducing a second nullability is non-great design. The real goal would be an infinte number of them, where you could do Bar?? and pass either Bar, null<Bar> or null<Bar?> to the function in a reasonable way. I don't see that happening, though. It's too big a change to the type system for too little gain.

Another option would be to twiddle with default values, and allow a parameter to have a different type inside the function than it has in the parameter list. Say:

// Signature: Foo Function({Bar? bar}),  internal type of `bar` is `Object?` and default value is not `Bar?`
Foo copyWith({Object? bar as Bar? = const _Marker()}) => Foo(bar: identical(const _Marker(), bar) ? this.bar : bar as Bar?);

But, to be honest, I'd personally prefer to make the distinction between passing null and omitting a parameter go away, so null always means the same as "absent"). The default values would apply to a null value passed explicitly too, and then that approach wouldn't work.

@tbm98
Copy link
Author

tbm98 commented Jul 5, 2020

Yes. Thank you!
Hopefully soon there is a solution for future copyWith cases.

@tbm98 tbm98 closed this as completed Jul 5, 2020
@mateusfccp
Copy link
Contributor

Common Lisp does something like this and it's pretty nice:

(defun foo (&optional (bar 0 is-bar-supplied))
  (if is-bar-supplied
    (format t "Bar supplied with value ~A" bar)
    (format t "Bar defaulted to ~A" bar))

This gives results like this:

* (foo 10)
Bar supplied with value 10
NIL
* (foo)
Bar defaulted to 0
NIL```

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
Projects
None yet
Development

No branches or pull requests

4 participants