I am new to dart and puzzled with null_closures.
For example:
void _test1({VoidCallback callback}) {
callback();
}
Would crash when I used as:
_test1(callback: null);
But work well as:
_test1(callback: () => null);
Why?
_test1(callback: null);
In this case, callback is null. Attempting to dereference null by accessing properties (with some exceptions) or by calling methods on it will result in an exception. (In most other languages, this would be a null pointer exception. In Dart, null is a special object (of type Null), so attempting to call methods on it instead usually will throw a NoSuchMethod error.)
_test1(callback: () => null);
In this case, callback is a function (an object of type Function) that returns null when that function is invoked.
Related
Consider the following code:
void printInt(int i) => print(i);
void printString(String s) => print(s);
void printSomething(Object o) {
final printer = {
int: printInt,
String: printString,
}[o.runtimeType];
print('Printer is $printer');
printer(o);
}
void main() => printSomething('Hello');
It prints the correct printString function and then crashes with the following exception:
TypeError: "Hello": type 'String' is not a subtype of type 'Null'
Why does that happen?
The error comes from the fact that your map has been given the type Map<Type, void Function(Null)> since that is the only type it can use based on the content of you list.
The problem is that Dart cannot give your map another type since anything else would not be valid from the perspective of the type system. Let's say the type was Map<Type, void Function(Object)>. Well, we are then allowed to send any object into a method from this map. But that is not allowed since your two methods in the map clearly are defined as accepting int and String and not Object.
We can either give it the type Map<Type, void Function(int)> since we have a method taking a String.
Also, Map<Type, void Function(dynamic)> has the same problem as Object since both methods are clearly defined to taking a precise type.
So Dart will instead use Null as the type of the parameter since the only thing we know is valid to give both methods are the null value.
And since you are then trying to give a String as parameter to a method with the signature of void Function(Null) you are getting an error from the type system.
If you want you code to run, you need to tell the type system to keep quite about what you are doing. To do that, you should use dynamic for the type of method you are receiving from the map:
void printInt(int i) => print(i);
void printString(String s) => print(s);
void printSomething(Object o) {
final dynamic printer = {
int: printInt,
String: printString,
}[o.runtimeType];
print('Printer is $printer');
printer(o);
}
void main() => printSomething('Hello');
My code is pretty simple:
typedef GenericCallback = void Function<T>(T);
void main() {
GenericCallback callback = <String>(String message) => printMessage(message);
}
void printMessage([String errorMessageReason]) {
print(errorMessageReason ?? '');
}
And DartPad gives me this error at the word message in printMessage(message):
Error: The argument type 'String/*1*/' can't be assigned to the parameter type 'String/*2*/'.
- 'String/*1*/' is from 'unknown'.
- 'String/*2*/' is from 'dart:core'.
It looks like Dart is getting the reference from one String and not the other, how's this even possible?
Since you've done typedef GenericCallback = void Function<T>(T) you need to provide a generic method which matches the signature as a callback. The tricky part here is, you are doing that but not in the way you think.
In this line it looks like you're trying to specify the type for the closure you've created:
GenericCallback callback = <String>(String message) => printMessage(message);
However, Dart's rules for naming generic type parameters are strange in that you can use the names of existing types as the name of the type parameter. In other words, the following lines are all functionally identical and will provide a similar error:
GenericCallback callback = <String>(String message) => printMessage(message);
GenericCallback callback = <T>(T message) => printMessage(message);
GenericCallback callback = <int>(int message) => printMessage(message);
These generic closures are all completely valid and even built-in types like int and String will be treated as the names for type parameters in the scope of the closure.
In order to fix your error, you'll want to change the type parameter String to have a different name that doesn't collide with a core type, and do one of the following:
Update your invocation of printMessage to cast message to a String, although this will fail if T isn't of type String when the closure is called.
GenericCallback callback = <T>(T message) => printMessage(message as String);
Update your typedef to expect a String parameter
typedef GenericCallback = void Function<T extends String>(T);
GenericCallback callback = <T extends String>(T message) => printMessage(message);
This is an easy mistake to make if you've come from a language which allows for template/generic specialization like C++. Keep in mind that, at least currently, you can't specialize a generic method or object and the generic type isn't assigned until the method is actually called or object is created.
I am trying to create a Dart function that essentially wraps other functions with some boilerplate error handling code, and otherwise returns the value returned by the original function. A key requirement is that it should accept functions with multiple different return types, while avoiding duplicating the common error handling logic across multiple different functions. I found one approach that seems to work by using the dynamic type, except that the compiler is not able to detect type mismatches, so they are only caught at runtime.
Is there a better way to accomplish what I'm aiming for here, and particularly in a way that catches type mismatches at compile time?
Below is a simplified example of my code, where the functions compile fine, but at runtime getAString will raise an error Dart Error: Unhandled exception: type 'List<String>' is not a subtype of type 'String'
/// Signature of API function calls
typedef APIFunctionCall = dynamic Function();
dynamic doWithErrorHandling(APIFunctionCall fn, {retries: 2}) async {
for (int attempts = 0; attempts < retries + 1; attempts++) {
try {
return await fn();
}
on Exception catch (e) {
print(
"This is just an example; actual function does a bunch of more specific error handling.");
}
}
}
Future<String> getAString() async {
// Want a function that can support multiple return types but detect type errors
String doesReturnAString = await doWithErrorHandling(() async => 'hello world'); // This runs fine
String doesntReturnAString = await doWithErrorHandling(() async => <String>['hello', 'world']); // This throws an Error
return doesntReturnAString;
}
You can abstract over the return type using a type parameter:
Future<T> doWithErrorHandling<T>(Future<T> fn(), {int retries = 2}) async {
do {
try {
return await fn();
} catch (e) {
// record error.
}
retries--;
} while (retries >= 0);
return null; // or whatever.
}
With that, you can call with any function. In most cases, the type argument can be inferred from the static type of the argument function, or from the type expected by the surrounding context, but if not, you can write it yourself.
Future<String> getAString() async {
String doesReturnAString = await doWithErrorHandling(() async => 'hello world');
// The next line has a compile-time type error!
String doesntReturnAString = await doWithErrorHandling(() async => <String>['hello', 'world']);
return doesntReturnAString;
}
(As an unrelated hint, you should never catch Exception. Dart errors do not implement Exception, they implement Error. Exception is a meaningless marker interface used by some thrown objects that the user is intended to catch and handle, but in that case, you should be catching the particular exception, like on FormatException, not the plain Exception. So, general rule: Never write on Exception).
I am trying to create a wrapper for lunr.js (http://lunrjs.com/) in Dart, however, I can find no documentation on how to use this with the Dart js interop.
This is the object I am trying to create:
var index = lunr(function () {
this.field('title', {boost: 10})
this.field('body')
this.ref('id')
})
Currently this is all that I have.
JsObject index = new JsObject(context['lunr'], [()
{
}]);
How am I able to access this from an anonymous function?
Also where do I put the actual lunr.js? I am simply making a wrapper for it so I don't see any reason to have it in a HTML file unless necessary.
EDIT:
I have also tried:
Create a function to allow for using this keyword. (still not sure if this syntax is correct)
_f = new JsFunction.withThis( (t) {
t.callMethod('field', ['title', {boost: 10}])
t.callMethod('field', ['body'])
t.callMethod('ref', ['id'])
});
Then create a JsObject using that function:
JsObject index = new JsObject(context['lunr'], [_f]);
This will give me this error:
Exception: Unhandled exception:
Closure call with mismatched arguments: function 'call'
NoSuchMethodError: incorrect number of arguments passed to method named 'call'
Receiver: Closure: (dynamic) => dynamic
Tried calling: call(Instance of 'JsObject', Instance of 'JsObject')
Found: call(t)
#0 Object.noSuchMethod (dart:core-patch/object_patch.dart:45)
Next I tried this:
JsObject index =new JsObject.fromBrowserObject(context['lunr']);
That gives me a different error: Exception: Illegal argument(s): object cannot be a num, string, bool, or null
This may be because I do not have a way to call the _f function when creating the JsObject that way.
You have to use :
context.callMethod('lunr', [_f]);
new JsObject(context['lunr'], [_f]); is the same as new lunr(f) in JS.
I think if you just use it in the function it will be closurized with the function. Otherwise you can try using an emulated function where you pass the this when you instantiate an instance of the emulated function.
class SomeFunction implements Function {
ThisType thisVal;
SomeFunction(this.thisVal);
call(args) {
thisVal.field(...);
}
}
I don't know dart at all, but perhaps you can sidestep the issue of trying to use this entirely. The function you pass to the lunr function has the created index yielded as the first param as well as being the context of the function.
var index = lunr(function (idx) {
idx.field('title', { boost: 10 })
idx.field('body')
idx.ref('id')
})
The above uses the yielded index object rather than relying on the context being set as the index, so you don't need to use this.
Assume such conditions:
Some operation does not provide possibility of returning the result.
This operation declared as callback
Using typedef not recommended
Some operation provide of returning the result.
This operation declared as callback
Using typedef not recommended
Assume such scenario:
void main() {
executeVoidOperation(methodNonVoid); // Must throw if method void?
executeNonVoidOperation(methodVoid); // Must throw if method non-void?
}
int methodNonVoid() {
return 0;
}
void methodVoid() {
}
void executeVoidOperation(void operation()) {
operation(); // Must throw if method non-void?
}
void executeNonVoidOperation(dynamic operation()) {
var result = operation(); // Must throw if method void?
print(result); // Result of void operation? (if such passed as argument)
}
Displayed results:
null
Questions (where I wrong?):
Null is object. From where this null appeared (as result) if void function cannot return result (even null)?
Functions with different return types in Dart assumed as the same (not conflicting) types?
How in Dart called this function transformations?
executeNonVoidOperation(methodVoid); works because the callback is defined as dynamic operation(). dynamic can be anything, including void. It's the same as if you just don't specify a type.
The null value stems from a simple rule in Dart. Quoted from the Dart Language Tour:
All functions return a value. If no return value is specified, the statement return null; is implicitly appended to the function body.
That means that every void method always returns null. If you try to return something else, you'll get a runtime error (in checked mode).
executeVoidOperation(methodNonVoid); is a bit more tricky - I'd expect it to throw a runtime error, but it seems the callback is treated as dynamic operation() instead of void operation(). Dart Editor's analyzer seems to think that, too. This may be either a bug or a design choice by the Dart team.