I'm learning Dart, and I'd like to have a method similar to let in Kotlin.
I'd like to use it as:
var variable = ...;// nullable type, for example MyClass?
var test1 = let(variable, (it) => 'non null: ${it.safeAccess()}');
// test1 type is String?
var test2 = let(variable, (it) => 'non null: ${it.safeAccess()}', or: () => 'Default value');
// test2 type is String since either way we return a String
In this example, the variable is a nullable instance of MyClass and the output is a nullable String if no fallback is provided, or a non-null String if a non-null fallback is provided.
Here's the prototype I have written:
typedef O LetCallback<I, O>(I value);
typedef O OrCallback<O>();
O let<I, O>(I? value, LetCallback<I, O> cb, {OrCallback<O>? or}) {
if (value != null) {
return cb(value);
}
if (or != null) {
return or();
}
if (null is O) {
return null;
}
throw Exception("Please provide a default non-null value");
}
Dart complains I can't return null, but I don't understand why it's illegal. I had expected this (in explicit syntax):
var variable = ...;// nullable type, for example MyClass?
var test1 = let<MyClass, String?>(variable, (it) => 'non null: ${it.safeAccess()}');
// I=MyClass, O=String?
var test2 = let<MyClass, String>(variable, (it) => 'non null: ${it.safeAccess()}', or: () => 'Default value');
// I=MyClass, O=String
In my expectation, the compiler would infer the type O as either String? or String, so that return null is legal only if O is nullable.
It seems that using the generic syntax, the types referenced are always non-nullable. Is that so? Is it a limitation of the language? Is it possible to write what I want to achieve, or am I forced to have two implementations? (like let and letNotNull for example)
edit: after writing this, I tried the two implementations route. Here's what I have written:
typedef O LetCallback<I, O>(I value);
typedef O OrCallback<O>();
O letNonNull<I, O>(I? value, LetCallback<I, O> cb, OrCallback<O> or) {
if (value != null) {
return cb(value);
}
return or();
}
O? let<I, O>(I? value, LetCallback<I, O> cb, {OrCallback<O>? or}) {
if (value != null) {
return cb(value);
}
if (or != null) {
return or();
}
}
For some reason, this is legal:
var test = letNonNull(null, (it) => "whatever", () => null)
I had expected that the () => null callback would be a compiler error, since O can't be null (as per my initial observation: I can't return null).
It seems like the null-safety is not fully enforced.
edit2: it seems it's legal only if the type is inferred. E.g.:
letNonNull(null, (it) => "bogus", () => null); // legal
letNonNull<String, String>(null, (it) => "bogus", () => null); // illegal
I had hoped that the inferred types would be non-null...
You can't return null because O can, potentially, be bound to a non-nullable type.
Type variables are not "always non-nullable", but they are always potentially non-nullable. What you return from a function with return type O must be valid for all possible bindings of O, even when it's bound to non-nullable types. Or when it's bound to Never. That means that the only type that can possibly be valid to return is O itself, and null does not have type O.
If you want to always be able to return null, you must make the return type of let be O?. That makes it always nullable, even when O itself is not nullable.
In that case, I'd restrict O to be non-nullable by giving it a bound of extends Object.
The alternative, as you then do, is to return null if null is a valid return value, and throw if it's not (and thereby avoid having to return anything, since you have nothing to return), but your approach doesn't work with the type system.
Try changing
if (null is O) {
return null;
}
to
O? nullReturn = null;
if (nullReturn is O) {
return nullReturn;
}
As stated above, the only type you can return is O, so you want the value null to have type O. You can either do if (null is O) return null as O; (or even just return null as O; and rely on the TypeError from the cast istead of throwing yourself), or you can use type promotion like this example to avoid the extra as.
You probably also want to restrict I to be non-nullable using a bound, and then use I? for the argument to let, but not to cb. That ensures that the inferred I type is always non-nullable.
O let<I extends Object, O>(
I? value,
O Function(I) cb,
{O Function()? or}) {
if (value != null) {
return cb(value);
}
if (or != null) {
return or();
}
O? returnNull = null;
if (returnNull is O) {
return returnNull;
}
throw ArgumentError.value(null, "or",
"Please provide a default non-null value");
}
The letNotNull would also need to put a bound on the type variables:
O letNonNull<I extends Object, O>(
I? value, O Function(I) cb, O Function() or) {
if (value != null) {
return cb(value);
}
return or();
}
The reason
var test = letNonNull(null, (it) => "whatever", () => null)
is valid is that it infers letNotNull<Object, String?>, and
letNonNull<String, String>(null, (it) => "bogus", () => null); // illegal
is invalid because the type for O is not nullable.
The type system doesn't known how that or function argument is going to be used, it just checks that its a proper subtype of the O Function() parameter type for the actual value of O that is supplied.
The type checking of the function body ensures it can only be used in positions where the result is acceptable. That's the type checking which disallowed returning null above because that check has to work for all types that O can be bound to.
I'd consider defining the let operation as an extension method instead, because then it does type inference on the value first, before looking at the callback. Something like:
extension Let<T extends Object> on T? {
R let<R>(R Function(T) callback, {R Function()? or}) {
var self = this;
if (self != null) return callback(self);
if (or != null) return or();
R? nullReturn = null;
if (nullReturn is R) return nullReturn;
throw ArgumentError.notNull("or");
}
}
Just
return null as <The type you want>
Related
This question already has answers here:
"The operator can’t be unconditionally invoked because the receiver can be null" error after migrating to Dart null-safety
(3 answers)
Closed 12 months ago.
I have migrated my Dart code to NNBD / Null Safety. Some of it looks like this:
class Foo {
String? _a;
void foo() {
if (_a != null) {
_a += 'a';
}
}
}
class Bar {
Bar() {
_a = 'a';
}
String _a;
}
This causes two analysis errors. For _a += 'a';:
An expression whose value can be 'null' must be null-checked before it can be dereferenced.
Try checking that the value isn't 'null' before dereferencing it.
For Bar() {:
Non-nullable instance field '_a' must be initialized.
Try adding an initializer expression, or add a field initializer in this constructor, or mark it 'late'.
In both cases I have already done exactly what the error suggests! What's up with that?
I'm using Dart 2.12.0-133.2.beta (Tue Dec 15).
Edit: I found this page which says:
The analyzer can’t model the flow of your whole application, so it can’t predict the values of global variables or class fields.
But that doesn't make sense to me - there's only one possible flow control path from if (_a != null) to _a += 'a'; in this case - there's no async code and Dart is single-threaded - so it doesn't matter that _a isn't local.
And the error message for Bar() explicitly states the possibility of initialising the field in the constructor.
The problem is that class fields can be overridden even if it is marked as final. The following example illustrates the problem:
class A {
final String? text = 'hello';
String? getText() {
if (text != null) {
return text;
} else {
return 'WAS NULL!';
}
}
}
class B extends A {
bool first = true;
#override
String? get text {
if (first) {
first = false;
return 'world';
} else {
return null;
}
}
}
void main() {
print(A().getText()); // hello
print(B().getText()); // null
}
The B class overrides the text final field so it returns a value the first time it is asked but returns null after this. You cannot write your A class in such a way that you can prevent this form of overrides from being allowed.
So we cannot change the return value of getText from String? to String even if it looks like we checks the text field for null before returning it.
An expression whose value can be 'null' must be null-checked before it can be dereferenced. Try checking that the value isn't 'null' before dereferencing it.
It seems like this really does only work for local variables. This code has no errors:
class Foo {
String? _a;
void foo() {
final a = _a;
if (a != null) {
a += 'a';
_a = a;
}
}
}
It kind of sucks though. My code is now filled with code that just copies class members to local variables and back again. :-/
Non-nullable instance field '_a' must be initialized. Try adding an initializer expression, or add a field initializer in this constructor, or mark it 'late'.
Ah so it turns out a "field initializer" is actually like this:
class Bar {
Bar() : _a = 'a';
String _a;
}
There are few ways to deal with this situation. I've given a detailed answer here so I'm only writing the solutions from it:
Use local variable (Recommended)
void foo() {
var a = this.a; // <-- Local variable
if (a != null) {
a += 'a';
this.a = a;
}
}
Use ??
void foo() {
var a = (this.a ?? '') + 'a';
this.a = a;
}
Use Bang operator (!)
You should only use this solution when you're 100% sure that the variable (a) is not null at the time you're using it.
void foo() {
a = a! + 'a'; // <-- Bang operator
}
To answer your second question:
Non-nullable fields should always be initialized. There are generally three ways of initializing them:
In the declaration:
class Bar {
String a = 'a';
}
In the initializing formal
class Bar {
String a;
Bar({required this.a});
}
In the initializer list:
class Bar {
String a;
Bar(String b) : a = b;
}
You can create your classes in null-safety like this
class JobDoc {
File? docCam1;
File? docCam2;
File? docBarcode;
File? docSignature;
JobDoc({this.docCam1, this.docCam2, this.docBarcode, this.docSignature});
JobDoc.fromJson(Map<String, dynamic> json) {
docCam1 = json['docCam1'] ?? null;
docCam2 = json['docCam2'] ?? null;
docBarcode = json['docBarcode'] ?? null;
docSignature = json['docSignature'] ?? null;
}
}
I want to check, if my variable k has a type calles T.
My approach was
int k=1;
Type T=int;
if(k is T) print('same type');
But it is not working. It works, if I write
if(k is int)
but I want to change the type in a variable.
Thank you for an answer
You could store the type in a string, and then use runtimeType and toString() to compare the variable's type with the type stored in the string:
int k = 1;
String type = "int";
if (k.runtimeType.toString() == type){
print("k is an integer");
}
You can't do type checks using Type objects in Dart.
A Type object is not the type, it's just a token representing the type which can be used with the dart:mirrorsreflection library. It cannot, really, be used for anything else.
If you need to do type checking, you need to store the type as a type variable, which means you need something generic, or store it in plain code as a closure.
The closure approach is simpler, but less readable:
int k = 1;
var typeChecker = (o) => o is int;
if (typeChecker(o)) print("k has the right type");
Using a generic helper class is more general:
class Typer<T> {
bool isType(Object o) => o is T;
bool operator >=(Typer other) => other is Typer<T>;
bool operator <=(Typer other) => other >= this;
}
...
var k = 1;
var type = Typer<int>();
if (type.isType(k)) print("k is integer");
In short, don't use Type for anything except dart:mirrors because it isn't really useful for anything else.
Some Type in the Dart returns a different kind of Type when using .runtimeType.
For example:
void main() async {
List value = [];
print(value.runtimeType); // print -> JSArray<dynamic>
}
I am using:
void main() async {
List value = [];
print(isSameType(target: value, reference: <Object>[])); // print -> false
value = [Object()];
print(isSameType(target: value, reference: <Object>[])); // print -> false
value = <Object>[];
print(isSameType(target: value, reference: <Object>[])); // print -> true
}
bool isSameType({required target, required reference}) =>
target.runtimeType == reference.runtimeType;
class Object {}
But I saw many comments saying the .runtimeType is for debugging and some comments said it will be not available in the future. So I am using this instead of the code above:
void main() async {
var value;
value = [];
print(value.runtimeType); // print -> JSArray<dynamic>
print(isSameType<List>(value)); // print -> true
value = [Test];
print(value.runtimeType); // print -> JSArray<Type>
print(isSameType<List<Test>>(value)); // print -> false
print(isSameType<List>(value)); // print -> true
value = [Test()];
print(value.runtimeType); // print -> JSArray<Test>
print(isSameType<List<Test>>(value)); // print -> true
print(isSameType<List>(value)); // print -> true
value = <Test>[];
print(value.runtimeType); // print -> JSArray<Test>
print(isSameType<List<Test>>(value)); // print -> true
print(isSameType<List>(value)); // print -> true
}
bool isSameType<type>(target) => target is type;
class Test {}
Basic example for using:
void main() async {
MyObject phoneNumber = MyObject<int>();
phoneNumber = await getDataFromUser();
if (phoneNumber.isSameType()) await uploadData(phoneNumber);
}
class MyObject<type> {
MyObject({this.data});
dynamic data;
bool isSameType() => data is type;
}
Future<dynamic> getDataFromUser() async {
return null;
}
Future<bool> uploadData(data) async {
return false;
}
I trying make the following code but T only can be int, double or a custom class. I couldn't find how to restrict the type in Dart or something that work like where from C#. How can I do that in Dart?
class Array3dG<T> extends ListBase<T> {
List<T> l = List<T>();
Array3dG(List<T> list) {
l = list;
}
set length(int newLength) { l.length = newLength; }
int get length => l.length;
T operator [](int index) => l[index];
void operator []=(int index, T value) { l[index] = value; }
}
There is no way to constrain the type variable at compile-time. You can only have one bound on a type variable, and the only bound satisfying both int and your custom class is Object.
As suggested by #Mattia, you can check at run-time and throw in the constructor if the type parameter is not one of the ones you supprt:
Array3dG(this.list) {
if (this is! Array3dG<int> &&
this is! Array3dG<double> &&
this is! Array3dG<MyClass>) {
throw ArgumentError('Unsupported element type $T');
}
}
This prevents creating an instance of something wrong, but doesn't catch it at compile-time.
Another option is to have factory methods instead of constructors:
class Array3dG<T> {
List<T> list;
Array3dG._(this.list);
static Array3dG<int> fromInt(List<int> list) => Array3dG<int>._(list);
static Array3dG<int> fromDouble(List<double> list) => Array3dG<double>._(list);
static Array3dG<MyClass> fromMyClass(List<MyClass> list) => Array3dG<MyClass>._(list);
...
}
which you then use as Array3dG.fromInt(listOfInt). It looks like a named constructor, but it is just a static factory method (so no using new in front).
You can check at runtime the type with the is keyword:
Array3dG(List<T> list) {
if (list is List<int>) {
//Handle int
}
else if (list is List<double>) {
//Handle double
}
else if (list is List<MyClass>) {
//Handle MyClass
}
else {
throw ArgumentError('Unsupported $T type');
}
}
Note that if you are handling int and double in the same way you can just check for num
You can check the progress of the Union types here: https://github.com/dart-lang/sdk/issues/4938
I reproduced the issue I am having in a brand new MVC Web API project.
This is the default code with a slight modification.
public string Get(int? id, int? something = null)
{
var isValid = ModelState.IsValid;
return "value";
}
If you go to http://localhost/api/values/5?something=123 then this works fine, and isValid is true.
If you go to http://localhost/api/values/5?something= then isValid is false.
The issue I am having is that if you provide a null or omitted value for an item that is nullable, the ModelState.IsValid flags a validation error saying "A value is required but was not present in the request."
The ModelState dictionary also looks like this:
with two entries for something, one nullable, which I am not sure if it is significant or not.
Any idea how I can fix this so that the model is valid when nullable parameters are omitted or provided as null? I am using model validation within my web api and it breaks it if every method with a nullable parameter generates model errors.
It appears that the default binding model doesn't fully understand nullable types. As seen in the question, it gives three parameter errors rather than the expected two.
You can get around this with a custom nullable model binder:
Model Binder
public class NullableIntModelBinder : IModelBinder
{
public bool BindModel(System.Web.Http.Controllers.HttpActionContext actionContext, ModelBindingContext bindingContext)
{
if (bindingContext.ModelType != typeof(int?))
{
return false;
}
ValueProviderResult val = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (val == null)
{
return false;
}
string rawvalue = val.RawValue as string;
// Not supplied : /test/5
if (rawvalue == null)
{
bindingContext.Model = null;
return true;
}
// Provided but with no value : /test/5?something=
if (rawvalue == string.Empty)
{
bindingContext.Model = null;
return true;
}
// Provided with a value : /test/5?something=1
int result;
if (int.TryParse(rawvalue, out result))
{
bindingContext.Model = result;
return true;
}
bindingContext.ModelState.AddModelError(bindingContext.ModelName, "Cannot convert value to int");
return false;
}
}
Usage
public ModelStateDictionary Get(
int? id,
[ModelBinder(typeof(NullableIntModelBinder))]int? something = null)
{
var isValid = ModelState.IsValid;
return ModelState;
}
Adapted from the asp.net page: http://www.asp.net/web-api/overview/formats-and-model-binding/parameter-binding-in-aspnet-web-api for further reading and an alternative method to set it at the class(controller) level rather than per parameter.
This handles the 3 valid scenarios:
/test/5
/test/5?something=
/test/5?something=2
this first give "something" as null. Anything else (eg ?something=x) gives an error.
If you change the signature to
int? somthing
(ie remove = null) then you must explicitly provide the parameter, ie /test/5 will not be a valid route unless you tweak your routes as well.
You'll have to register a custom model-binder for nullable types as the default binder is calling the validator for nullable parameters as well, and the latter considers those empty values as invalid.
The Model Binder:
public class NullableModelBinder<T> : System.Web.Http.ModelBinding.IModelBinder where T : struct
{
private static readonly TypeConverter converter = TypeDescriptor.GetConverter( typeof( T ) );
public bool BindModel( HttpActionContext actionContext, System.Web.Http.ModelBinding.ModelBindingContext bindingContext )
{
var val = bindingContext.ValueProvider.GetValue( bindingContext.ModelName );
// Cast value to string but when it fails we must not suppress the validation
if ( !( val?.RawValue is string rawVal ) ) return false;
// If the string contains a valid value we can convert it and complete the binding
if ( converter.IsValid( rawVal ) )
{
bindingContext.Model = converter.ConvertFromString( rawVal );
return true;
}
// If the string does contain data it cannot be nullable T and we must not suppress this error
if ( !string.IsNullOrWhiteSpace( rawVal ) ) return false;
// String is empty and allowed due to it being a nullable type
bindingContext.ValidationNode.SuppressValidation = true;
return false;
}
}
Registration:
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// ...
var provider = new SimpleModelBinderProvider(typeof(int?), new NullableModelBinder<int>());
config.Services.Insert(typeof(ModelBinderProvider), 0, provider);
// ...
}
}
Remove the default null value from the second parameter. The model binder will set it to null if it's something other than int.
I've found a working workaround for me (just exclude null values from data being sent - as opposed to sending values as nulls).
See https://stackoverflow.com/a/66712465/908608
On Dart 1.0.0, I just tried:
class MyClass {
int x;
bool b;
MyClass(int x, [bool b = true]) {
if(?b) {
// ...
}
}
}
And am getting a compiler error on the ?b part:
The argument definition test ('?' operator) has been deprecated
So what's the "new" way of testing for whether or not an argument was supplied?
There is no way to test if an argument was provided or not. The main-reason for its removal was, that it was very complex to forward calls this way.
The generally preferred way is to use null as "not given". This doesn't always work (for example if null is a valid value), and won't catch bad arguments. If null is used, then the parameter must not have a default-value. Otherwise the parameter is not null but takes the default-value:
foo([x = true, y]) => print("$x, $y");
foo(); // prints "true, null"
So in your case you should probably do:
class MyClass {
int x;
bool b;
MyClass(int x, [bool b]) {
if(b == null) { // treat as if not given.
// ...
}
}
}
This makes new MyClass(5, null) and new MyClass(5) identical. If you really need to catch the first case, you have to work around the type-system:
class _Sentinel { const _Sentinel(); }
...
MyClass(int x, [b = const _Sentinel()]) {
if (b == const _Sentinel()) b = true;
...
}
This way you can check if an argument has been provided. In return you lose the type on b.
The argument definition test operator was deprecated because it was redundant with checking for null; an optional parameter that was omitted would get the value null, and the caller could've passed null explicitly anyway. So instead use == null:
class MyClass {
int x;
bool b;
MyClass(int x, [bool b]) {
if (b == null) {
// throw exception or assign default value for b
}
}
}