dart-lang / dart-lang/language

handle functions with empty scope as top-level/static

Open
#981 3 comments 0 reactions 0 assignees View on GitHub
feature
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.