experiment: allow non-shared `async*` types (only)
So that we can write parametric abstractions, in the absence of a shared type constraint.
By popular demand. Fixes (part of) #3892.
Example:
import Debug "mo:base/Debug";
import Array "mo:base/Array";
actor a {
func expect<T>(a : () -> async* T) : async* T {
await* a();
};
func mapPar<A,B>(args : [A], f : A -> async* B) : async* [B] {
let vs = Array.init<?B>(args.size(), null);
for (i in args.keys()) {
vs[i] := ?(await* f(args[i]));
};
Array.tabulate<B>(args.size(), func i { let ?v = vs[i]; v });
};
public func f(a : Nat) : async Text { debug_show(a) };
public func go() : async () {
let x = await* expect<Nat>(func () : async* Nat {await async 1});
assert x == 1;
let vs = await* mapPar([0,1,2], func (a : Nat) : async* Text { await f(a) });
Debug.print(debug_show vs);
}
};
Pros: just a minor tweak
Cons:
- still requires ugly eta-expansion of
sharedfunctions calls. - can't just abstract over
async* T(must be a functionU -> async* T) to avoid scope violations. (Perhapsasync* Tshould be implicitly scope-polymorphic (since the computation is delayed anyway)). - can't use implicit lambda arguments because we don't insert scope parameters for those (a separate, but annoying, issue: our sugar to do so is (syntactically) type driven, which obviously doesn't work when types are omitted.
TODO
- [ ] test run-drun/do-async-poly.mo needs revisiting
- [ ] actually implement
mapParwith parallel waiting. This version ain't that.
Comparing from c33c06cb85874b10ac0c70c82702beda9d6b0cfa to c99bf6b1be8d13093bd48167966953083dc92346: In terms of gas, no changes are observed in 4 tests. In terms of size, no changes are observed in 4 tests.
Oh crap, that's actually mapSeq (sequential map), not mapPar (parallel map), due to lazy semantics of async*. Maybe this doesn't give us what we want unless we also do non-shared async types after all. Will need to experiment some more first.
That's annoying.
Does it have to be lazy?
Does it have to be lazy?
I think it does, if you want await* to demarcate the effects. But I'm open to better suggestions. I think the way to solve this is to lift the restriction on vanilla async t. Still scratching my head here.
I’m not thinking this through, but could async* { e } eagerly start evaluating e until the first actual await? A bit like async { e } “eagerly” (but in a separate threat) starts evaluating e?
I’m not thinking this through, but could
async* { e }eagerly start evaluatingeuntil the first actualawait? A bit likeasync { e }“eagerly” (but in a separate threat) starts evaluatinge?
That would be the semantics of the 'do async e' I originally proposed, except with a separate type. I guess the problem then is that the effects happen even without 'await*'. With 'async e' nothing happens unless you 'await',
Ah, right
Just doing some repo cleanup for better focussing on active work: Closing PR as it seems inactive for a longer time. Please re-open if it is still relevant and after it has been updated with latest master changes.