-
Notifications
You must be signed in to change notification settings - Fork 207
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
Should Object be nullable by default? #141
Comments
From an end-user perspective, having As an experiment, In a world without nullable |
|
Oh! So, So put me in the against camp. 😄 |
A minor concern that I have with making foo(Object nullable) {
if (nullable != null) {
var x = nullable;
}
} On the other hand, there is essentially no difference between |
My general preference is to let
Right, to me that's the main reason. But there's a technical as well as a conceptual side to it. Technically, we'd otherwise need a special exception to even denote the set 'all objects other than the null object'. As mentioned, we'd use Technically, we are also well positioned to claim that But, even more importantly, I think it is useful to support the concept of having an object "which is actually there", as opposed to the null object which is hardly ever used because it is useful in its own right, it is always used in order to indicate that "something else could be here, but it was omitted or it is not yet in place". In contrast, I think it makes sense to be maximally permissive with We might want to say that |
One user input is that it would be surprising if For consistency, it would be nice if If we go that way, then we need to define what Or, as Erik suggested, we could say that there exists a class which is represented by |
I think we should have a class and a reified representation of that class corresponding to the top type, such that we have a simple story about why it is possible to say It may look like an exception, but I think it's OK for With this firm notion of a top type, we can maintain a simple conceptual model about what it means to be an "object", and we also have the simple model that being an |
I think part of the question here is, why would we need this? I haven't really seen an argument for it yet. |
I think the notion of 'all objects other than the null object' is conceptually useful, because the null object is almost always used to indicate that something is not available (or not yet available), so we may wish to indicate that we won't encounter any "missing objects". For instance, a If you buy the idea that it is useful, it seems more straightforward to me if (And the special exception is that we wouldn't otherwise ever use |
But is it practically useful? I'm fairly confident a more practically useful type would be
But we wouldn't do that for One way to resolve this would be to go through the core libraries and see if we can find any places where we'd use |
In Dart, The only place where you would need I doubt there is any SDK code that we will actually change to use I don't think making |
This may be a little tied up with the discussion of optional parameters and default values. I think there's been at least one proposal to have |
I wrote:
@munificent wrote:
I do think so, yes. It is used by developers to write code with fewer bugs, because they can rely on a specific property to be maintained consistently. The relevant property here is "the null object indicates that some object could be provided here, but it's absent". In other words, the null object is never provided for its own sake. This is also a built-in implication of the association of nullable types with optional types: If a nullable type This interpretation is used by @leafpetersen just mentioned another situation where the ability to express the type of "all objects except null" is useful (or even essential), namely as a type variable bound, in connection with the type of an optional parameter. This makes me think that we do need to allow developers to express the type of all objects except null; and in that case I find it more readable and consistent to let |
For The function itself should not care whether its argument is required, if the body starts running, then the invocation was successful, it doesn't matter why it succeeded. |
Which is the reason why I proposed that we could outlaw such a declaration entirely: It is an error for the type annotation on a named parameter with no default to have unknown nullability. You would then have to use So we don't actually have to handle unknown nullability, because we don't have to introduce it.
That would be ensured with
I think it should be part of the type of the function that it accepts a named argument which is required, such that we can maintain the requirement also for first class functions. I'm not sure whether this is the same thing as "the function itself cares". ;-) |
So then is |
If it is disallowed to even declare a named parameter like |
I think the fundamental conceit of "nullable types" versus a real, nestable Option type is that, yes, the former is less expressive, but its convenience (and performance?) in common cases makes up for the lack of expressiveness. You basically only get one sentinel value. That's enough most of the time, and then the simplicity of only having one means you can represent a potentially-absent reference using a NULL pointer. It means users don't have to worry about explicitly flattening unwrapping multiple layers when merging and composing collections containing potentially absent stuff. But it leads to ambiguity in some cases like It's not a perfect solution, but it's the one we have. I don't think we'll do our users a better service by committing to applying that solution consistently then we would by giving them an ecosystem that uses null for "absent" in some APIs and I'd rather give users a consistent good experience working with |
That .... would probably be disallowed, yes. It's not statically known that So, a call site with no argument is statically invalid if an optional argument is not passed for a parameter with a type that is not known to be nullable and which has no default value. That means that we will need three different function types In the |
@munificent wrote:
+1! So let's return to null and optional parameters; @lrhn wrote:
This is basically the same thing as proposing that a named parameter with unknown nullability should be treated (with respect to its optionality) as if it were non-null. I think that might work, but I'd still prefer the approach where we simply outlaw a named parameter with no default that has a type with unknown nullability (so you must write
I'm just saying that I'd prefer to avoid the need to explain that we also have the third case. ;-) That said, we'd need syntax for required named parameters in function types anyway (say, |
That's an excellent point. |
I'd like to know the difference between function declarations and function types. |
A function declaration creates an actual executable unit of code that has a A function type is just a type annotation, a thing the type checker can reason about. There's no concept of a default value in the static type system, because the type checker doesn't care about them. (There are "static checks" around default values, but they aren't part of the type system properly.) That means there's no way to write a default value in a function type annotation, because it wouldn't mean or do anything. |
Why is that a problem? The fact that the type system doesn't know if it contains Further, allowing
Maybe, but JSON does allow When reading JSON, the object's type would have to be |
@eernstg wrote:
I'm not sure we do need the int foo({int x = 42}) => x; then the function type of With type variables, we might not know whether a parameter accepts Still, this all hinges on making |
I feel it's very confusing that |
I do fear that it will be confusing that In practice, the API docs will write the external type, and not the default value, so users shouldn't see the non-nullable type. It's only the API author who can get confused, and they are the ones relying on the variable being non-null internally in the function. I actually think it can work, but I would definitely want a usability study of it before making a final decision. |
I think,
Consequently, non-nullable named parameter without default must be required, with/ without |
@lrhn wrote
I think Besides, your examples focus on the case where the parameter is actually not required, because there is a default value. We could of course just require a default value for all named parameters with a non-null type, but I would expect the notion of a required named parameter to be useful in practice, which means that we might as well consider supporting it, now where it seems to arise naturally anyway. |
I think whether or not a null means default value should be the choice at the call site. I find the proposed behavior confusing. Thinking about something like We can also have something more intuitive, like |
I'm assuming you refer to the example above (so how did you know that I'm against it? 👿 :). My opposition to that use of As I read it, using And that still means that you can only use it locally, you cannot abstract over it. No int|Nothing helper(condition, value) => condition ? value : Nothing; // or more complex, Otherwise it goes everywhere in the type system, and we'll have Notingable types like we have nullable types, and need syntax to describe them. That's a very, very big impact for such a specialized feature. I am opposed to very specialized features that are not also very specifically scoped, because they are typically a sign of digging yourself deeper into an existing a bad design. It's "one more feature, and then things will be good", but in practice there is always one more corner case that needs handling, and now you have n+1 features that interact badly. If it doesn't generalize, then it's not worth it. Take a step back and see if there is a simpler thing that solves more problems instead. (Sometimes you need to go so far back that it's not realistic, sadly). If it does generalize ... then it's still a second So, lets look another design for the same thing; optionally passing a non-null value (like @ds84182 just wrote above).
which is equivalent to That's also a very specific feature, but it is localized. It doesn't affect just any expression, it only affects one named parameter being passed. The syntax only applies to named parameters, so it doesn't bleed anywhere. It uses the existing |
Sure, lots of features have some overlap like this. Orthogonality is a goal but it's never perfectly attained. A conditional expression can be used inside a key or value in a map: {
(condA ? "key1" : "key2") : (condB ? "value1" : "value2")
} Which is something an if element cannot express. But an if element can do: [
if (condA) ...someList,
if (condA) noElseClause
] Both of which can't easily be expressed using
It is syntactic sugar on top of the same Iterator protocol used by Iterable<int> countToTen() sync* {
for (var i = 1; i <= 10; i++) yield i;
}
void main() {
var list = [...countToTen()];
for (var i in list) print(i);
} Here we use a sync* method to produce an Iterator which gets consumed by a spread to produce a list which in turn is iterated over using a for-in loop. All of these can be freely mixed and matched because they all use the same Iterator protocol. |
I suspect that at this point we could conclude that |
My inclination is that if we have the |
@leafpetersen wrote:
OK, but that would also give us a quite nice and consistent setup: We can express both the top type (with no special treatment like |
I think I would want
|
@lrhn, @leafpetersen, are we gravitating toward the conclusion that |
If Object is non-nullable (which I'm fine with) and there's no name for the top type above Object and Null, then what does the error message look like here: class Foo<T> {
method(T x) {
x.oops(); // <--
}
} I'm worried by the idea of a type that has a method set, and that is used by the static type checker for producing errors, but that doesn't have a meaningful name that we can show users. Does this say something like "'Object?' does not have a method 'oops()'."? My suspicion is that the tools will have to keep coming up with their own weird ad hoc ways to refer to this thing. Maybe we should just name it. |
|
Have you considered letting the omitted bound mean |
I don't believe we'll ever get "migration for free", so doing using (I'd totally go for a model where |
Makes sense, but the rationale could still be "if you want your local variable And here's an argument against my proposal (letting the omitted bound be I think, now, that the omitted bound should mean |
@leafpetersen, @munificent, @lrhn, do we have a decision that " |
Decided, Object is non-null. |
This issue is to discuss the question of whether the
Object
type should be nullable by default when we enable NNBD types.Arguments I've hard so far:
For:
null
supports all of the operations that are defined onObject
, it works perfectly well anywhere anObject
is expected.Against:
Object
.cc @lrhn @munificent @eernstg
The text was updated successfully, but these errors were encountered: