-
Notifications
You must be signed in to change notification settings - Fork 213
FutureOr, access value synchronously if possible. #3625
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
Comments
There's no way you can make a function run synchronously if it has a body which is marked So you need to write code that allows for a purely synchronous execution, and you may use the type import 'dart:async';
FutureOr<int> get valueA => 45;
FutureOr<int> get valueB => 5;
FutureOr<int> get differenceAB {
var a = valueA, b = valueB;
switch ((a, b)) {
case (int aInt, int bInt): return aInt - bInt;
}
return (() async => await valueA - await valueB)();
}
void main() {
print("Start");
var difference = differenceAB;
if (difference is int) {
print("The difference is $difference");
} else {
print("Oh, we'll have to wait for that!");
difference.then((difference) => print("Here it comes: $difference"));
}
print("Done");
} You can add |
FutureOr is just a special union type, there is no actual class associated with it, it has no instance methods other than what are on Object. However, you can make a The main issue is this won't work with async/await. |
One thing that was requested in a similar issue is to have either:
|
Ultimately the problem with FutureOr is that we lose the ability to use |
Fwiw, I like the idea of |
The I can't see how this request differs from that, other than possibly syntax. If we only make If we make an async function able to return a So everybody uses the feature everywhere, but the hoped-for performance improvement gets lost to the extra type checks, and it also increases code size. |
"Just" add |
I don't think this is an issue of performance. To me this is an issue of timing. |
Fwiw, the utility I have written in the past to handle this does incur a type check but still showed some appreciable speedup. I think in the case where you have a |
I'd consider making my own future wrapper, like: extension type EagerFuture<T>._(FutureOr<T> _) {
static final _cachedResults = Expando<_FutureResult>();
EagerFuture(FutureOr<T> futureOr) : _ = futureOr {
if (futureOr is Future<T>) {
_cachedResults[futureOr] ??= _FutureResult<T>.future(futureOr);
}
}
Future<T>? asFuture() => _ is Future<T> ? _ : null;
T? asValue() => _ is Future<T> ? null : _;
bool get hasResult =>
_ is Future<T> ? _cachedResults[_]?.result != null : true;
T get result => _ is Future<T>
? (_cachedResults[_]?.result?.value ?? (throw StateError("No result")))
: _;
T? get tryResult => _ is Future<T> ? (_cachedResults[_]?.result?.value) : _;
}
class _FutureResult<T> {
_FutureResult.future(Future<T> f) {
f.then((v) {
result = Result.value(v);
}, onError: (Object e, StackTrace s) {
result = Result.error(e, s);
});
}
Result<T>? result;
}
extension <T> on Result<T> {
T get value {
if (this case ValueResult(:var value)) return value;
var error = this.asError!;
Error.throwWithStackTrace(error.error, error.stackTrace);
}
}
void main() async {
var f = EagerFuture(Future.delayed(Duration(seconds: 1), () => 42));
while (!f.hasResult) {
print("...tick");
await Future.delayed(Duration(milliseconds: 250));
}
print("Result: ${f.result}");
} (Not tested much.) I am worried that you're talking frames here. Awaiting a |
We've already talked about this before. Your wrapper isn't representative of how folks want to use FutureOrs. And there's the issue of chaining too. To use The only real solutions out there are:
Microtasks are way too late already. If scheduled at the start of a frame, they'll trigger at the end of it. Yet we want those values immediately instead of at the end of the frame. |
The issue can be represented using the following Flutter app: import 'package:flutter/material.dart';
void main() {
runApp(MyWidget());
}
class MyWidget extends StatefulWidget {
const MyWidget({super.key});
@override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
int? a;
int? b;
int? c;
@override
void initState() {
super.initState();
Future<int> fn() async {
setState(() {
a = 42;
});
return 42;
}
Future<int> fn2() async {
final result = await fn();
setState(() {
b = result * 2;
});
return result * 2;
}
fn2().then((value) {
setState(() {
c = value * 2;
});
});
}
@override
Widget build(BuildContext context) {
print('a $a b $b c $c');
return const Placeholder();
}
} This app will print to console the following:
But that This has an impact on the rendering. Because when the widget renders, its content appears over multiple frames instead of all at once. |
If we only go with That change would be:
If we want a short way to introduce a delay, like That's doable. Heck, even preferable IMO. |
That has nothing to do with @override
void initState() {
super.initState();
setState(() {
a = 42;
});
Future.value(42).then((result) {
setState(() {
b = result * 2;
});
return result * 2;
}).then((value) {
setState(() {
c = value * 2;
});
});
}
There is another invariant: if propagation has started values will propagate through as far as they can go while microtask queue is being drained. You can see this on So I think the fundamental question we should be asking is: are invariants around value propagation guaranteed by TBH I am not really sure if they are worth it: I kinda understand that they are added so that it is easier to reason about interleaving of callbacks (e.g. " print('before');
(() async {
print(await SynchronousFuture(10));
});
print('after'); Will actually print
We probably can't break existing // library dart:async
final class SynchronousFuture<T> {
// ...
}
extension ToSync on Future<T> {
external SynchronousFuture<T> get synchronous;
}
// User code
SynchronousFuture<int> foo() async { // Return type serves as marker.
await bar(); // equivalent of `await bar().synchronous`.
} If we have a taste for something even more ambitious we could go even further and have a separate modifier and bring our async story closer to Kotlin and Swift with "auto awaiting": extension on Future<T> {
/// If the value available on the Future will return its value
/// synchronously without suspending the current coroutine.
T get value coro;
}
int bar() coro {
return fut.value + 1;
}
int foo() coro {
return bar() + 2;
} |
Indeed. But that's why this discussion is about FutureOr. I'm only showcasing how a delay impacts Flutter. The goal here is to be able to replace Futures in my snippet with FutureOrs somehow, keep an "await"-like syntax, while also removing that frame with Ultimately, this boils down to convenience. There are lots of possible workarounds, but none of them are very convenient. I've personally been playing around with implementing a custom extension<T> on FutureOr<T> {
Future<T> get asSync {
final that = this;
if (that is! Future<T>) return SynchronousFuture(that);
return that;
}
}
/// A custom `return` keywords that works by throwing an exception, later caught by [asyncOr]
typedef Returns<T> = Never Function(T value);
/// A custom `async` modifier that returns a FutureOr.
FutureOr<T> asyncOr<T>(
Future<T> Function(Returns<T> returns) cb,
) Then used as: FutureOr<int> getValue() => 42;
FutureOr<int> multiplyBy2() => asyncOr((returns) async {
final value = await getValue().asSync;
returns(value * 2);
}); But I think something's broken in Dart at the moment. Even though the return value appears to be correct, I am seemingly unable to do |
and
are probably related. We don't allow It's an accident when it works, even if that is more often than I'd have expected, but |
That's an invariant. That's pretty much the only invariant that we guarantee, and which we also assume ourselves.
That's not an invariant, that's just an implementation detail. We do not promise that it is so, and we don't promise not to change it. (We do know that changing timing can break bad code out there, with "bad code" being defined as code which relies on async timing that was never promised.) It's really just for efficiency and latency - when most futures have exactly one listener, it's efficient to propagate the completion eagerly through any callbacks that return a result synchronously. We can do that in one big loop, instead of scheduling another microtask, which introduces more latency and overhead. If something prevents us from continuing with such a propagation, we just give up, and continue later, with no promises broken. |
Way way back when @nex3 and I were first writing pub, I remember running into some issues around this. This was before In short, I think it's possible that there is a lot of code out there implicitly relying on async code essentially doing tail call optimization. Much of that could break if we start synchronously calling |
To be fair, the idea of making I think we all agree that the current behaviour is a good default. It's just that in some cases, we want the ability to have a function optionally execute synchronously ; and at the moment the syntax for that is horrifying compared to async/await |
Assume we had both Which new (But isn't that reason enough to give them that? Maybe. But see above for why that could end up more computationally expensive than today, because of all the extra |
It depends on the tradeoffs between async/asyncOr. I can see a few possible tradeoffs from this conversation:
So the balance is favours pretty strongly I would expect most This is what triggered me to request a synchronous
This effectively always linked to immediately updating the UI the very first frame where a UI element appears/disappears. In this case, we don't care about performance, we care about the correctness of the UI output. |
in #25, there's a proposal to introduce a suffix form of await, like var x = foo.bar().await.baz().await; This "suffix await" can be defined to optionally take the form of a function call: var x = foo.bar().await().baz().await(); Which gives a chance to pass extra parameters to await(). FutureOr<int> x = ...
int y = x.await(immediateIfTheValueIsReady: true); No need to introduce |
Are you saying: Future<int> fn() async => 42;
Future<void> fn2() async {
print(fn().await(sync: true));
}
void main() {
print('a');
fn2();
print('b');
} Would print: a
42
b ? |
Yes, this is exactly what I'm saying :-) Except that the returned value of fn should be |
That would conflict with |
How about To me, it looks more natural with the parens anyway. |
On a different thought: Folks could then do: extension<T> on Future<T> {
Future<T> get asSync {
if (value case final T value) return SynchronousFuture(value);
return this;
}
} |
For this, you don't need to expose "value" or write an extension. |
if (value case final T value) return SynchronousFuture(value); Won't work if If we have something like The one thing you can't do today is to have an |
Yes, but a We could have: Future<int> fn() async => 42;
void main() {
print(fn().value); // 42
} Since
No EagerFuture doesn't solve the problem like counter-argued above. It has a too poor usability when compared to async/await. |
Have you tried to return a tuple (Future, value)? The idea is to NOT mark the function as async. (Future<int>?, int?) foo(int n) /* NO "async"! */ {
return n==0? (null, 0) : (Future.value(n), null);
}
var x = switch (foo(42)) { // passing 0 will return an immediate value
(Future<int> f, _)=> await f,
(_, int? v) => v!
}; (I couldn't find a way to verify it works as expected though). EDIT: turns out, rust follows essentially the same template. The following code is produced by Gemini enum DataResult {
FutureVariant(futures::future::Ready<i32>),
ValueVariant(i32),
}
fn get_data() -> DataResult {
if some_condition() {
DataResult::FutureVariant(futures::future::ready(42))
} else {
DataResult::ValueVariant(100)
}
}
fn main() {
let result = get_data();
match result {
DataResult::FutureVariant(future) => {
// Need to wait for the future to complete
let value = future.await;
println!("Got value from Future: {}", value);
},
DataResult::ValueVariant(value) => {
println!("Got immediate value: {}", value);
},
}
} The problem is that these either-or functions are potentially contagious, leading to a third color. The same is true for "Result or error" functions. And combinations of those. The program turns into a mess where you can't see the forest for the trees. |
That's no different from using FutureOr, which is horrible in terms of usability. It's a problem users of my packages can commonly face. I want to offer a proper solution that's usable my many. |
I agree. On top of that, it doesn't fit in dart's laconic style IMO. |
I had a discussion about this issue with @lrhn and I have also looked a bit into Flutter sources to get the context. (@lrhn could you read this and tell me if I wrote something incorrectly?) Why the current choice is problematic for Flutter?Flutter intentionally builds the widget tree only once per frame. This is quite clearly stated in documentation for /// Marks the element as dirty and adds it to the global list of widgets to
/// rebuild in the next frame.
///
/// Since it is inefficient to build an element twice in one frame,
/// applications and widgets should be structured so as to only mark
/// widgets dirty during event handlers before the frame begins, not during
/// the build itself. This decision effectively means that you can only create widget tree from synchronously available data, and any data hidden within Flutter attempts to offer a work-around for this problem by providing /// This is similar to [Future.value], except that the value is available in
/// the same event-loop iteration. This documentation is misleading because the result of Could Flutter solve this issue itself?Yes, it probably could. One approach I could envision is recognizing that some elements have an asynchronous component and requiring microtask flush before finishing their build: rebuildDirtyElements();
do {
drainMicrotasks();
rebuildDirtyAsyncElements();
} while (microtasks.isNotEmpty || delayedAsyncElements.isNotEmpty); In this model This model does the same amount of work as the current synchronous build: you don't rebuild the tree multiple times, but simply delay building parts of it until after you drained the microtasks which produces data for these builds. It is more aligned with microtasks based The overall structure of widget tree building becomes much more complicated then it is now, and it will probably come with some amount of overhead which might be a strong enough argument against doing that. What could Dart do?Lets assume that solving this in Flutter is not going to happen. What could we do on the Dart level? Well, we could completely change the semantics of
We are open to changing semantics of The big open question is whether we will be able to roll such a change out: our previous experiences with touching async timing was that it requires somehow fixing hundreds if not thousands of poorly written tests in the internal monorepo. If breakage is too big and not toolable then we need an alternative rollout strategy. While I was writing this I realized that maybe we could package this together with some other changes into a new type of Future<int> foo() async {
return await bar() + await baz();
}
extension on Future<T> {
suspending T get value;
}
suspending int foo2() {
return bar().value + baz().value;
} I am more or less lifting suspending name from Kotlin, for the lack of better vocabulary. Footnotes
|
What if we just... added a Honestly, having synchronous access to future values could be super useful, as it would become trivial to determine if it is completed without needing to indirectly check though Hell, this might even make
That being said, it may limit the usefulness some - so we should see if we can just reuse This could be really neat for eager initialization of futures while keeping access to the future so that both sync and async tasks can use it in whatever manner makes sense. We could also choose to have Future<bool> get() async => await Future.value(true);
Future<void> doThing() async? {
final res = await? get(); // as if it were Future<bool> get() async? => await? Future.value(true);
assert(res.value == true) // or whatever the API is
} |
The problem is that as soon as |
Makes sense! perhaps this is a good point to apply versioning to. |
@mraleph That's the gist of the issue what what forces users to stop using async/await. And although arguably less important, async* has similar issues. The function always starts asynchronously instead of immediately when the stream is listened. And |
The main problem here is Flutter's behavior, where it renders something, and then changes it after a change happening during the same microtask queue. The Dart microtask queue is modeled after the browser microtask queue, because that's what it needed to be compatible with, in order to work with browser resources that have a lifetime of "one event" ... meaning the recent and the microtasks belonging to that event. The event isn't done until all the microtasks are done. In the browser, UI updates happen as top level events. Change events are microtasks. That means that a cascade of changes will have all completed and stabilized before the UI is updated. It Flutter followed that model, rather than choosing what to render before all microtasks had been run, the problem wouldn't exist here. Using a |
I want to point out that this applies to raw DOM APIs. Frameworks like React behave similar to Flutter (as far as I can tell in my quick experiments). If you update state as a result of microtasks running you will only see updated UI when event loop turns around, because VDOM is not going to be rebuilt immediately. |
I've been looking through the Future code, and... as far as I can tell, it looks like the value already exists as a private variable. I honestly don't see much reason why we couldn't add a alternatively, a It's incredibly odd to me that these are already available, and yet arent exposed. the utility of having access to those two alone is incredible Even if all we got was sync access to Future values when the future is known to be completed, it would be a major step forwards. Changing how async+await works could honestly wait till later (in the - ahem - Future). It would let us freely pass futures around, which would then let the callers handle the data how they please. Hell, it makes the FutureOr thing easier, as you can just do Honestly, what are we missing to make this much happen? it seems almost trivial. Frankly, unless I'm missing something, the only issue is that its technically a breaking change for anything that implements So... to reiterate
What are we actually missing? |
It has always been a non-goal to allow synchronous access to future results. If you have two ways to access the result, it encourages writing two code paths, where it's hard to ensure that both code paths are tested. Or writing code that assumes that a future is always already completed, which makes it harder to optimize the asynchronous implementation code in a way that may change timing. You should treat microtasks as being "nearly synchronous" and happening as a unit. Listening to a completed future gets you notified in the same microtask queue, that's part of the same synchronous execution of the microtask queue loop, no real asynchronous timing gap will happen. |
I'd argue that already completed futures dont need timing at all. But then, why cant futures just have a variable on them that gets set via .then? That way, timing becomes irrelevant, as its effectively just avoiding a wrapper object. Something like this: class Future<T> {
Future() {
then((v) {
isComplete = true;
valueOrNull = v;
);
}
bool isComplete = false;
T? valueOrNull;
} I don't think anything breaks this way. additionally, the future itself decides when that value is set. This effectively removes the need for indirection and wrapper objects in a number of cases, and still trivializes FutureOr usage. As far as I can tell, this effectively removes the timing concern doesn't it? It just makes Future more usable. -- we might want a lint that prefers |
It's definitely possible to wrap a future in something that stores the value when the future calls its callbacks. That wrapper can even implement By all means do that, if you really want to. It's effectively the same as caching the result and the future together. If someone gives you a plain future, you shouldn't be wondering whether it may already be completed. You should assume it's not, because futures are rarely shared, and should be listened to immediately when they are created, to ensure that errors are handled in time. Adding the caching to the future type itself is changing its intended use-cases, from being the eventual result of a, most likely ongoing, computation, to being a way to store that result in perpetuity. That is not its intent today, you want to store the result, you do that in the callback. Needing to store the result is a specific use-case that you can program for, but it shouldn't be baked into the Or just await the future. If it comes back within the same microtask queue, during the same event, that should be soon enough. I'll throw in a wrapper, for good measure: Copyright 2024 Google LLC.
SPDX-License-Identifier: BSD-3-Clause
import 'dart:async';
/// A future and its eventual result.
///
/// Contains a [future] and, when that future has completed,
/// the future's result.
///
/// The result can be accessed synchronously using [result], which must
/// only be used when the result is available as specified by [hasResult].
/// Do notice that reading [result] will throw an error if the future
/// completed with that error.
///
/// The result can be accessed optimistically using [tryResult] which provides
/// the synchronous result if it's available, and otherwise the [future] which
/// is still the most precise representation of the eventual result.
final class FutureCache<T> {
Future<T>? _future;
_Result<T>? _result;
/// Creates cache with existing result.
FutureCache._result(this._result);
/// Creates a cache for the eventual result of [future].
///
/// The cache will not be filled with a result immediately, even if the
/// future has already been completed. The result will only be available when
/// the future has responded to a [Future.then] call, which mush be done
/// asynchronously.
FutureCache(Future<T> future) : _future = future {
future.then<void>((T v) {
_result = _Value<T>(v);
}, onError: (Object error, StackTrace stack) {
_result = _Error(error, stack);
});
}
/// Creates a cache that is pre-filled with the given [value].
///
/// Also creates a [future] which complete with [value].
FutureCache.value(T value) : this._result(_Value<T>(value));
/// Creates a cache that is pre-filled with [error] and [stack] trace.
///
/// Also creates a [future] which completes with [error] and [stack]
/// as an error.
FutureCache.error(Object error, StackTrace stack)
: this._result(_Error(error, stack));
/// A future that completes with [result].
///
/// If no result is available yet, as reported by [hasResult], then
/// cache is waiting for this future to notify of its completion.
///
/// If a result is available, then the future will provide the same
/// result if awaited.
Future<T> get future => _future ??= switch (_result!) {
_Value<T>(:var value) => Future<T>.value(value),
_Error(:var error, :var stack) => Future<T>.error(error, stack)
..ignore(),
};
/// Creates a cache for the value or future of [result].
///
/// If [result] is a `Future<T>`, the created cache waits for that
/// future to complete before the result is valid.
///
/// Otherwise the created cache is already filled with the provided
/// value, and its [future] is created as a future completing with that
/// value if necessary.
factory FutureCache.of(FutureOr<T> result) {
if (result is Future<T>) {
return FutureCache<T>(result);
}
return FutureCache<T>.value(result);
}
/// Captures the result of a synchronous or asynchronous computation.
///
/// Runs [computation] and returns a cache that is either filled with
/// an available synchronous value or error results, or that waits
/// for the result of the future returned by [computation].
factory FutureCache.sync(FutureOr<T> Function() computation) {
try {
return FutureCache<T>.of(computation());
} catch (e, s) {
return FutureCache<T>.error(e, s);
}
}
/// Whether a result is available.
///
/// A result is available after the [future] has completed invoked its
/// listeners, which happens asynchronously at some point after the
/// future completed.
///
/// Until there is an available result, the [result] getter must not be
/// used, and the [tryResult] getter will return the [future].
///
/// After a result is available, both [result] and [tryResult]
/// synchronously provides that result, as a value or by throwing an error.
bool get hasResult => _result != null;
/// The result of [future] provided synchronously, when available.
///
/// Must not be used until a result is available, as reported by [hasResult].
///
/// When the result of [future] is available, evaluating `result`
/// will either return the result value, or (re-)throw an error result.
T get result => (_result ?? (throw StateError("Not completed"))).value;
/// The result of [future] provided as synchronously as possible.
///
/// If a result is available, as reported by [hasResult], this getter
/// provides that result, just like [result].
/// Until a result is available, it instead evaluates to [future].
/// Notice that in the former case it may synchronously throw an error.
///
/// Awaiting the returned value is equivalent to awaiting [future],
/// but can also be checked for whether the value is available synchronously.
FutureOr<T> get tryResult {
var result = _result;
if (result != null) return result.value;
return _future!;
}
}
/// The result of a computation.
sealed class _Result<T> {
/// Read to reify the result.
T get value;
}
/// A value result.
final class _Value<T> implements _Result<T> {
final T value;
_Value(this.value);
}
/// An error result.
final class _Error implements _Result<Never> {
final Object error;
final StackTrace stack;
_Error(this.error, this.stack);
Never get value {
Error.throwWithStackTrace(error, stack);
}
} |
Being able to synchronously access already resolved futures doesn't really help anyway. |
@rrousselGit Correct. That's a tangent for this issue's actual requests: A synchronous await of I'm very positive towards allowing an In practice, an So it's allowed by the spec, it's just an optimization. I'm possibly willing to allow I don't consider that breaking the API of I'm not at all positive about allowing synchronous interospective access to a completed future's result from just any code. |
I get that, but the problem is that; depending on the use case, we can end up forced to "stutter" with a loading value even though the value may already exist. Going from nothing to a loading state to a value state over the period of a microtask is jarring Especially since we may have "lost the opportunity" to extract the value "synchronously" by the time we're in a sync function. Hell, it's why SynchronousFuture exists at all, and it's not necessarily limited to Flutter use cases. That wrapper object would always throw if you tried to use it right away, even if the future completed, unless the inner implementation happened to be a SynchronousFuture that was already processed. I'm almost certain that people would be using FutureOr way more if it were first class, but then that still doesn't handle synchronizing the future at any point. After all, at some point, we aren't waiting anymore right? It doesn't make sense to me to wait for a value that's already here. Could we alternatively say that .then does act immediately if the future is completed instead, SynchronousFuture-style? Would that break anything? Maybe add a named parameter like then(sync: true) for that behavior? Doesn't break anything and let's us use it as we please Hell, depending on how you possibly handle synchronous await like you're describing, it could effectively be the same solution. |
Again, that wouldn't be visible if the UI didn't update in the middle of a microtask queue execution. If it just waits to the end, after all change events and their future propagations have stabilized, nobody would see the flash of loading.
Definitely. A lot of code has been written under the assumption that you can call The initial implementation of
That would be a better API. You have to opt in to the otherwise surprising behavior, so you wouldn't/shouldn't be surprised. It's hard to change the API of Those implementations would also have to understand the parameter, but I guess ignoring it is still valid behavior, the value might not have been available anyway. |
Sounds like we have a viable idea.
I wonder if there's some extension BS we could do? The most straightforward option is to extension type it such that only The fact that it was designated an interface instead of a mixin or something unfortunately does make it tricky. Its possible we could do some funky shit like allowing subtypes to not include the other option then would be to just... break it. then maybe force people to mix it in instead of implementing (or otherwise some compiler BS to special-case transform it...) eh. imo it seems like the most useful option is to permit "subtypes" of functions to exclude parameters they don't use (which, as an aside, can make the (x, y, _) thing a bit cleaner) currently, subtypes of Functions can only add optional parameters. Maybe we can permit them to exclude named parameters they don't need? The signature and type would still be the same - it would just effectively be syntax sugar for having the parameter, and just... not using it. effectively: typedef Fn = Function(int, String, {double percent})
final Fn fn = (i, n, {percent}) {}; // normal
final Fn fnPlus = (i, n, {percent, String additional = ''}) {}; // normal subtyping
final Fn fn2 = (i, n) {} // special syntax. effectively identical to (i, n, {percent}) {}
abstract interface class Future<F> {
T then<T>(T Function(F) cb, {bool sync = false});
}
class CustomFuture<T> implements Future<T> {
T then<T>(T Function(F) cb) {...} // lint warning: specify all parameters in function signature. still compiles.
} this would require language support in some way, but it could cleanly resolve it, and we could even add It could also make adding parameters to an interface no longer a breaking change, allowing for stagnant classes (like Just to be clear, I'm not necessarily asking for new subtyping rules - effectively the method could just have the parameter injected or something. Is that worth an issue? thoughts? |
Having "optional optional parameters" that an implementation doesn't have to understand, can be done in two ways:
The former is fairly simple to implement, but invasive and error prone. It makes a superclass and to change the implementation of a subclass method, and if actually acting on the argument is necessary to satisfy the method semantics, then the subclass silently gets a wrong implementation. (Don't use the feature then?) The latter is a big change to the type system. (Languages with overloading and interface default methods have it easier. Looking at you Java.) |
What if we just ignored extra parameters? void fn(String name) => print(name);
Function.apply(fn, ['name', 'extra']); Currently, this errors. This can be useful in a bunch of places. I admit to not knowing how this works internally, so please let me know if this is somehow unviable |
As per this request, I'm opening a new issue.
In Dart, the keywords async and await will convert any function in an asynchronous one.
It would be useful if for cases like FutureOr, it would be possible to access the value synchronously first if possible, and if not, the asynchronously.
I saw on this topic this and this but I haven't found a resolution yet.
Ex:
Solution A:
Introduce a new term for awaiting a FutureOr value or adapt async and await to allow to run code synchronously if possible
Solution B:
Why doesn't FutureOr allow a then method as Future? It won't be maybe the best solution out there as it would be requiring nesting in the above
differenceAB
function but at least it works :)The text was updated successfully, but these errors were encountered: