Skip to content
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

handle functions with empty scope as top-level/static #981

Open
2ZeroSix opened this issue May 24, 2020 · 3 comments
Open

handle functions with empty scope as top-level/static #981

2ZeroSix opened this issue May 24, 2020 · 3 comments
Labels
feature Proposed language feature that solves one or more problems

Comments

@2ZeroSix
Copy link

2ZeroSix commented May 24, 2020

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, worker_manager, 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)

class MyFutureWrapper<T> implements Future<T> {
  final Future<T> future;

  MyFutureWrapper(this.future);

  @override
  Future<R> then<R>(FutureOr<R> 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<T> asStream() {
    return future.asStream();
  }

  @override
  Future<T> 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<T> timeout(Duration timeLimit, {FutureOr<T> 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<T> 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

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<List<dynamic>> 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
@2ZeroSix 2ZeroSix added the feature Proposed language feature that solves one or more problems label May 24, 2020
@2ZeroSix 2ZeroSix changed the title handle functions with empty scope as as top-level/static handle functions with empty scope as top-level/static May 24, 2020
@lrhn
Copy link
Member

lrhn commented May 27, 2020

I would have no problem with local functions that do not close over local variables being sendable to other (same-source) isolates.
There is nothing in the language which prohibits this, it's entirely a restriction on the serialization implementation used by isolate communication.

The language based solution would be something like a "static function" or "const function" declaration which is even canonicalized. Say, allowing a local function declaration to be preceded by static and then the function body cannot refer to any non-static name from the context.
Or a static expression which is evaluated only once, then retains its value, and cannot refer to local names (effectively sugar for a static late final variable).

@mraleph
Copy link
Member

mraleph commented May 27, 2020

I would have no problem with local functions that do not close over local variables being sendable to other (same-source) isolates.

related issue dart-lang/sdk#40370

@2ZeroSix
Copy link
Author

link related issues: #758, #1048

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature Proposed language feature that solves one or more problems
Projects
None yet
Development

No branches or pull requests

3 participants