-
Notifications
You must be signed in to change notification settings - Fork 205
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
[Feature] Add support for an Undefined data type #877
Comments
The second use-case emerges also when I try to extend a constructor with optional default values. I haven't figure out how to form the |
While I have had the same issue, I'm not sure adding a new type is a good solution in a statically typed language like Dart. If we add an I see much more promise in other approaches. For example:
|
IMO this would be properly fixed by union types This would allow people to make their own class _Undefined {
const _Undefined();
}
SomeClass copyWith({
int | _Undefined someProperty = const _Undefined(),
}) {
return SomeClass(someProperty: someProperty is int ? someProperty : this.someProperty);
} |
General union types would indeed solve this too. Then you can use any class you choose as placeholder. The current |
If we use Union types, won't this have to be explicitly created for each case that I want to check? Seems like a lot of boilerplate |
Well, if we truly want to talk about boilerplate, we could talk about data classes or spread on classes, to make a In any case, union types are much more flexible. They don't benefit almost exclusively a And we can simplify it a bit: // Part that can be extracted into a separate file
class Undefined {
const Undefined();
}
const undefined = Undefined();
T _fallback<T>(T | Undefined value, T fallback) => value is T ? value : fallback;
// Actual usage
SomeClass copyWith({
int | Undefined someProperty = undefined,
String | Undefined another = undefined,
}) {
return SomeClass(
someProperty: _fallback(someProperty, this.someProperty),
another: _fallback(another, this.another),
);
} |
This looks great and looks like what I would expect. I'm just used to seeing it work like this in typescript without having to manually define the undefined class. |
A better name would be uninitialized |
Please, don't. This will only screw with the semantics of the type system. |
or only the dart would indicate if the variable was uninitialized int n;
int m= null;
int p= 2;
print(n); // output: null (uninitialized)
print(m); // output: null
print(o); // output: 2 would continue to be null, there is no need to create a separate type, dart only needs to indicate whether the variable has been initialized or not |
@cindRoberta, you will get a lot of support for tracking the initialization state of variables with the upcoming null-safety feature. If you just declare variables as usual (without putting a void main() {
int i;
i = 42; // If you comment this out there is an error in the next line.
if (i > 0) print(i);
} This is because You can use a dynamic check as well: void main() {
late int i;
bool b = true; // Assume initialization is complex, we don't know the value.
if (b) i = 42;
if (i > 0) print(i);
} In this case you will get a dynamic error (an exception) when The story is a lot longer than this, but as you can see there will be support for tracking initialization when null-safety is released. |
@eernstg, would this be useful as the default value for |
An implementation of Dart could very well have a special object (denoted by, say, Based on that kind of consideration, I'd prefer to rely on language concepts like So why aren't we just happy with |
I agree that having
|
If you are fan of redirected constructor then you have two options to fake First create your own const _undefined = _Undefined();
class _Undefined {
const _Undefined();
} This is what I'm going to copy: class B {
final int b1;
const B({
required this.b1,
});
@override
String toString() => 'B(b1: $b1)';
} First option: abstract class A {
final B a1;
final B? a2;
const A._({
required this.a1,
this.a2,
});
const factory A({
required B a1,
B? a2,
}) = _A;
A copyWith({B a1, B? a2});
@override
String toString() => 'A(a1: $a1, a2: $a2)';
}
class _A extends A {
const _A({
required B a1,
B? a2,
}) : super._(a1: a1, a2: a2);
@override
A copyWith({Object a1 = _undefined, Object? a2 = _undefined}) {
return _A(
a1: a1 == _undefined ? this.a1 : a1 as B,
a2: a2 == _undefined ? this.a2 : a2 as B?,
);
}
} Second option: abstract class A {
const A._();
const factory A({
required B a1,
B? a2,
}) = _A;
B get a1;
B? get a2;
A copyWith({B a1, B? a2});
@override
String toString() => 'A(a1: $a1, a2: $a2)';
}
class _A extends A {
@override
final B a1;
@override
final B? a2;
const _A({
required this.a1,
this.a2,
}) : super._();
@override
A copyWith({Object a1 = _undefined, Object? a2 = _undefined}) {
return _A(
a1: a1 == _undefined ? this.a1 : a1 as B,
a2: a2 == _undefined ? this.a2 : a2 as B?,
);
}
} Output: void main() {
var a = const A(a1: B(b1: 1), a2: B(b1: 2));
print(a); // A(a1: B(b1: 1), a2: B(b1: 2))
// undefined won't modify
a = a.copyWith();
print(a); // A(a1: B(b1: 1), a2: B(b1: 2))
// explicit null
a = a.copyWith(a2: null);
print(a); // A(a1: B(b1: 1), a2: null)
print(a is A); // true
print(a.runtimeType == A); // false
} |
The problem with your workaround is that the parameters are defined as |
@Levi-Lesches you won't loose type safety since compiler sees: A copyWith({B a1, B? a2}); class C {}
a.copyWith(a1: C()); // compile time so you cannot run code: The argument type 'C' can't be assigned to the parameter type 'B'. You are right about type safety only if (a as _A).copyWith(a1: C()); // Unhandled exception: type 'C' is not a subtype of type 'B' in type cast |
That's pretty much how Freezed works. The real issue is that this doesn't work with static methods |
@rrousselGit in case of static method you can store const _undefined = _Undefined();
class _Undefined {
const _Undefined();
}
class A {
static int _counter = 0;
static int get counter => _counter;
static void Function({int? c}) myFunction = _myFunction;
/// if [c] is undefined [_counter] will be decreased by one
/// if [c] is null [_counter] remains the same
/// if [c] is int [_counter] will be increased by it
static void _myFunction({Object? c = _undefined}) {
if (c == _undefined) {
_counter--;
} else if (c != null) {
_counter += c as int;
}
}
} If Freezed generator wants this behavior then part 'a.freezed.dart';
@freezed
class A with $A {
static int _counter = 0;
static int get counter => _counter;
@freezedStaticFunction(A._myFunction)
static void Function({int? c}) myFunction = $A.myFunction;
/// if [c] is undefined [_counter] will be decreased by one
/// if [c] is null [_counter] will be the same
/// if [c] is int [_counter] will be increased by it
static void _myFunction({Object? c = _undefined}) {
if (c == _undefined) {
_counter--;
} else if (c != null) {
_counter += c as int;
}
}
} Output: void main(List<String> arguments) {
A.myFunction(c: null);
print(A.counter); // 0
A.myFunction();
print(A.counter); // -1
A.myFunction(c: 6);
print(A.counter); // 5
} |
Is this being addressed? |
No concrete plans yet, but we're definitely aware of the issue with trying to implement |
Since we have this type in other languages, it would be nice to have it in Dart. It'll greatly strengthen the usefulness of
copyWith
functions, so thatnull
values can be differentiated from values that have not been defined.Use case:
If I decide to set the
activeCategory
to null, the newly created object will have the same values as the old object. I cannot figure out a reasonable workaround for this.The second use-case is more common in widgets with optional named parameters with default values. If someone passes in that value using a variable and that variable turns out to be null, we don't get the default value, and there's no way to pass in a value sometimes and leave it undefined at others without creating 2 separate widgets.
The text was updated successfully, but these errors were encountered: