dart-lang / dart-lang/language
handle functions with empty scope as top-level/static
- Dominant language
- TeX
- Stars
- 2.9k
- Forks
- 239
- Avg merge
- 2d 18h
- Merged PRs (30d)
- 14
Description
It would be really useful to be able to use functions that doesn't use anything from enclosing scope as top-level/static functions.
There is many packages that simplify scheduling tasks in other isolates, such as: [isolate](https://pub.dev/packages/isolate), [worker_manager](https://pub.dev/packages/worker_manager), [computer](https://pub.dev/packages/computer), etc...
But they are restricted to use top-level functions.
This behavior will make it much easier to offload work to isolates.
There is also a large field of solutions that would become practical to use, for example "smart" `Future`/`Stream` that would execute sequences of computations in isolate until we pass a function with non-empty scope.
In this code all 3 computations are effectively the same.
But only first can be passed to another isolate or converted to callback handle to be used outside of the dart code. (I used `PluginUtilities.getCallbackHandle`, because I couldn't find another way to determine whether it is possible to transfer the function to another isolate)
```dart
class MyFutureWrapper implements Future {
final Future future;
MyFutureWrapper(this.future);
@override
Future then(FutureOr Function(T value) onValue, {Function onError}) {
final handle = PluginUtilities.getCallbackHandle(onValue)?.toRawHandle();
final handleOnError = onError == null
? onError
: PluginUtilities.getCallbackHandle(onError)?.toRawHandle();
print('then onValue: $onValue $handle; onError: $onError $handleOnError');
return MyFutureWrapper(future.then(onValue, onError: onError));
}
@override
Stream asStream() {
return future.asStream();
}
@override
Future catchError(Function onError, {bool Function(Object error) test}) {
final handleOnError =
PluginUtilities.getCallbackHandle(onError)?.toRawHandle();
final handleTest = test == null
? null
: PluginUtilities.getCallbackHandle(test)?.toRawHandle();
print('catchError onError: $onError $handleOnError; test: $test $handleTest');
return MyFutureWrapper(future.catchError(onError, test: test));
}
@override
Future timeout(Duration timeLimit, {FutureOr Function() onTimeout}) {
final handleOnTimeout = onTimeout == null
? null
: PluginUtilities.getCallbackHandle(onTimeout)?.toRawHandle();
print('timeout onTimeout: $onTimeout $handleOnTimeout');
return MyFutureWrapper(future.timeout(timeLimit, onTimeout: onTimeout));
}
@override
Future whenComplete(FutureOr Function() action) {
final handleAction =
PluginUtilities.getCallbackHandle(action)?.toRawHandle();
print('whenComplete action: $action $handleAction');
return MyFutureWrapper(future.whenComplete(action));
}
}
staticComputation(s) {
print(s);
return s;
}
Future plain() async {
final result1 =
await MyFutureWrapper(Future.value('static')).then(staticComputation);
final result2 = await MyFutureWrapper(Future.value('closure')).then((s) {
print(s);
return s;
});
final result3 = await MyFutureWrapper(Future.value('await'));
print(result3);
print('$result1 $result2');
}
void main() async {
await plain();
}
```
output:
```
then onValue: Closure: (dynamic) => dynamic from Function 'staticComputation': static. -1113980921106075405; onError: null null
then onValue: Closure: (dynamic) => dynamic null; onError: Closure: (Object, StackTrace) => void null
static
then onValue: Closure: (String) => String null; onError: null null
then onValue: Closure: (dynamic) => dynamic null; onError: Closure: (Object, StackTrace) => void null
closure
then onValue: Closure: (dynamic) => dynamic null; onError: Closure: (Object, StackTrace) => void null
await
static closure
```
sample code with behavior close to expected
```dart
void main() async {
await wrapped();
}
Future wrapped() {
final result1Future =
MyFutureWrapper(Future.value('static')).then(staticComputation);
final result2Future = result1Future.then(main$1).then(main$1closure$1);
final result3Future = result2Future.then(main$2);
final main$3ContextFuture = result3Future.then(main$2closure$1);
/// apply async and sync context to main$3
main$3withContext(asyncContext) {
final syncContext = [];
return Function.apply(main$3, [...syncContext, ...asyncContext]);
}
/// inject async context
main$3injectAsyncContext(_) {
final asyncContext = Future.wait([
result1Future,
result2Future,
]);
return asyncContext;
}
return main$3ContextFuture
.then(main$3injectAsyncContext)
.then(main$3withContext);
}
main$1(_) {
return MyFutureWrapper(Future.value('closure'));
}
main$1closure$1(s) {
print(s);
return s;
}
main$2(_) {
return MyFutureWrapper(Future.value('await'));
}
/// controversial part:
/// depends only on returned value from the last closure,
/// but this might be too hard to implement or affect performance
main$2closure$1(s) {
print(s);
return s;
}
/// last part of the function with injected context
main$3(result1, result2) {
print('$result1 $result2');
}
```
output:
```
then onValue: Closure: (dynamic) => dynamic from Function 'staticComputation': static. -1113980921106075405; onError: null null
then onValue: Closure: (dynamic) => dynamic from Function 'main$1': static. 5544770122214873047; onError: null null
then onValue: Closure: (dynamic) => dynamic from Function 'main$1closure$1': static. 4854342079658418062; onError: null null
then onValue: Closure: (dynamic) => dynamic from Function 'main$2': static. 5529825966233543903; onError: null null
then onValue: Closure: (dynamic) => dynamic from Function 'main$2closure$1': static. -4387027789645933195; onError: null null
then onValue: Closure: (dynamic) => Future> null; onError: null null
then onValue: Closure: (dynamic) => dynamic null; onError: null null
then onValue: Closure: (dynamic) => dynamic null; onError: Closure: (Object, StackTrace) => void null
static
then onValue: Closure: (dynamic) => Null null; onError: Closure: (dynamic, [StackTrace]) => Null null
closure
then onValue: Closure: (dynamic) => Null null; onError: Closure: (dynamic, [StackTrace]) => Null null
await
then onValue: Closure: (dynamic) => Null null; onError: Closure: (Object, StackTrace) => Null null
then onValue: Closure: (dynamic) => Null null; onError: Closure: (Object, StackTrace) => Null null
static closure
```
Contributor guide
Assessment
This issue has not been assessed yet.