GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Implement closures · Issue #798 · AssemblyScript/assemblyscript

Implement closures #798

New issue
Copy link
New issue
Copy link
Open
btc-vision/assemblyscript
#1
btc-vision/assemblyscript#1
Open
Implement closures#798
btc-vision/assemblyscript
#1
Copy link
Labels
enhancement

Description

@MaxGraey
MaxGraey
opened on Aug 28, 2019
Issue body actions

I decide to start discussion about simple closure implementations.

Some obvious (and may be naive) implementation is using generation class context which I prefer to demonstrate in following example:

declare function externalCall(a: i32, b: i32): void;

function range(a: i32, b: i32, fn: (n: i32) => void): void {
  if (a < b) {
    fn(a);
    range(a + 1, b, fn);
  }
}

export function test(n: i32): void {
  range(0, n, (i: i32) => {
    externalCall(i, n); // capture n
  });
}

which transform to:

// generated
class ClosureContext {
  fn: (ctx: usize, i: i32) => void;
  n: i32; // captured var
  // optinal "self: usize;" is closure instantiate inside instance class method
  parent: ClosureContext | null = null;
}

// generated
function lambdaFn(ctx: usize, i: i32): void {
  externalCall(i, changetype<ClosureContext>(ctx).n);
}

function range(a: i32, b: i32, ctx: usize): void {
  if (a < b) {
    changetype<ClosureContext>(ctx).fn(ctx, a); // replaced from "fn(a)";
    range(a + 1, b, ctx);
  }
}

export function test(n: i32): void {
  // insert
  let ctx = new ClosureContext();
  ctx.fn = lambdaFn;
  ctx.n = n;
  //
  range(0, n, changetype<usize>(ctx));
}

Closure and ClosureContext will not generated when no one variable was captured and use usual anonym functions.
ClosureContext will not created when only this was captured. In this case ctx param use to pass this reference.

Other discussions: #563

@dcodeIO @jtenner @willemneal Let me know what you think about this?

Reacted by Sven Allers, Evgeny, Samrat Bhandari, Matthieu, Jairus, Joachim Wester, LeeHo, stephen, bbb651 🇮🇱, Alex Michaud and 2 more
Reacted by Jordan Last, Tommy D. Rossi, Iddan Aaronsohn, Amin Ya, Josh Junon, Artem Fokin, Filippo Neysofu Costa, Alaa Saleh, Mieszko Kycermann, Samrat Bhandari and 3 more

Activity

Next
  1. jtenner commented on Aug 28, 2019

    @jtenner
    jtenner
    on Aug 28, 2019
    Contributor
    More actions

    I think we need to override the meaning of call indirect.

  2. jtenner commented on Aug 28, 2019

    @jtenner
    jtenner
    on Aug 28, 2019
    Contributor
    More actions

    How would this transform?

    function add(a, b): callback {
      return () => a + b;
    }
  3. MaxGraey commented on Aug 28, 2019

    @MaxGraey
    MaxGraey
    on Aug 28, 2019
    MemberAuthor
    More actions

    @jtenner

    class ClosureContext {
      fn: (ctx: usize) => i32;
      a: i32;
      b: i32;
      parent: ClosureContext | null = null;
    }
    
    function lambdaAdd(ctx: usize): i32 {
      return changetype<ClosureContext>(ctx).a + changetype<ClosureContext>(ctx).b;
    }
    
    function add(a: i32, b: i32): ClosureContext {
      let ctx = new ClosureContext();
      ctx.fn = lambdaAdd;
      ctx.a = a;
      ctx.b = b;
      return ctx;
    }
  4. MaxGraey commented on Aug 28, 2019

    @MaxGraey
    MaxGraey
    on Aug 28, 2019
    MemberAuthor
    More actions

    Instead

    class ClosureContext {
      fn: (ctx: usize) => i32;
      b: i32;
      ...
    }

    we could just store index for indirect function table

    class ClosureContext {
      fnIdx: usize;
      a: i32;
      ...
    }
    
    ... 
    
    call_indirect(ctx.fnIdx, ...args)
  5. jtenner commented on Aug 28, 2019

    @jtenner
    jtenner
    on Aug 28, 2019
    Contributor
    More actions

    How does this work with function dispatch?

    For instance, let's say I pass the Closure Context out to js as a pointer. How can I re-call it?

  6. jtenner commented on Aug 28, 2019

    @jtenner
    jtenner
    on Aug 28, 2019
    Contributor
    More actions

    Is there a way to make a table and add entries to it?

  7. MaxGraey commented on Aug 28, 2019

    @MaxGraey
    MaxGraey
    on Aug 28, 2019
    MemberAuthor
    More actions

    @jtenner actually you need pass only fn / fnIdx. After that just use same approach as for anonymus indirect function calls.

    EDIT No you need unpack fn from returned ClosureContext object. Just provide another util for loader

    Reacted by Joshua Tenner
  8. MaxGraey commented on Aug 28, 2019

    @MaxGraey
    MaxGraey
    on Aug 28, 2019
    MemberAuthor
    More actions

    Next example:

    function test(fn: (x: i32) => void): i32 {
      let n = 0;
      fn(x => { n = x });
      return n;
    }

    should generate:

    class ClosureContext {
      fn: (ctx: usize, x: i32) => void;
      n: i32;
      parent: ClosureContext | null = null;
    }
    
    function lambdaFn(ctx: usize, x: i32): void {
       changetype<ClosureContext>(ctx).n = x;
    }
    
    function test(fn: (x: i32) => void): i32 {
      let n = 0;
      let ctx = new ClosureContext();
      ctx.fn = lambdaFn;
      ctx.n = n;
      fn(changetype<usize>(ctx));
      n = ctx.n;
      return n;
    }
  9. jtenner commented on Aug 28, 2019

    @jtenner
    jtenner
    on Aug 28, 2019
    Contributor
    More actions

    Well I'm thinking about aspect collecting function pointers. How will aspect need to work with the function pointers?

  10. jtenner commented on Aug 28, 2019

    @jtenner
    jtenner
    on Aug 28, 2019
    Contributor
    More actions

    My guess is that passing around the closure context will cause problems with manually using call_indirect like aspect does.

    Also, this closure method doesn't handle multiple references to the same local.

    let a = 1;
    let b = () => a;
    let c = () => a += 1;

    B and c will get different versions of a.

  11. MaxGraey commented on Aug 28, 2019

    @MaxGraey
    MaxGraey
    on Aug 28, 2019
    MemberAuthor
    More actions

    @jtenner
    You could get table and get function by index / ref. For example you have wasm:

    (func $foo (result i32) (i32.const 1234))
    (table (export "tbl") anyfunc (elem $foo))

    than you js part:

    WebAssembly.instantiateStreaming(fetch('main.wasm')).then(({ instance }) => {
      const table = instance.exports.tbl;
      console.log(table.get(0)());  // 1234
    });
    Reacted by Joshua Tenner
  12. jtenner commented on Aug 28, 2019

    @jtenner
    jtenner
    on Aug 28, 2019
    Contributor
    More actions

    It's likely we will need to allocate enclosed local values in a table or on the heap. Each pointer to those values will need to be stored in a table pointing to the heap.

    This idea is Naive because the variables can no longer be treated like local variables because it's possible to modify local values before the function finishes executing.

  13. MaxGraey commented on Aug 28, 2019

    @MaxGraey
    MaxGraey
    on Aug 28, 2019
    MemberAuthor
    More actions

    B and c will get different versions of a.

    Why?

    let a = 1;
    let b = () => a;
    let c = () => a += 1;
    
    let br = b(); // 1
    let cr = c(); // 2
    // assert(a == 2)

    Will generate:

    class ClosureContextB { fn; a; }
    class ClosureContextC { fn; a; }
    
    function lambdaB(ctx: usize): i32 {
       return changetype<ClosureContextB>(ctx).a;
    }
    
    function lambdaC(ctx: usize): i32 {
       return changetype<ClosureContextC>(ctx).a += 1;
    }
    
    let a = 1;
    
    let ctxB = new ClosureContextB(lambdaB, a);
    let br = b(ctxB); // 1
    // a = ctxB.a; // 1 unmodified so unnecessary
    
    let ctxC = new ClosureContextB(lambdaC, a);
    let cr = c(ctxC); // 2
    a = ctxC.a; // 2
  14. jtenner commented on Aug 28, 2019

    @jtenner
    jtenner
    on Aug 28, 2019
    Contributor
    More actions

    I'm saying a in that example needs to exist on the heap for both b and c to access it, and the closure class needs to contain a Box<i32> that points to the heap location.

  15. MaxGraey commented on Aug 28, 2019

    @MaxGraey
    MaxGraey
    on Aug 28, 2019
    MemberAuthor
    More actions

    No, we don't need pass plain types by boxed references. Also found pretty clear article. So ClosureContext (LexicalEnvironment) should be little bit modifier and also store reference to it's parent LexicalEnvironment.

    Reacted by Joshua Tenner and Iddan Aaronsohn
  16. 71 remaining items

  17. luffyfly commented on Oct 27, 2022

    @luffyfly
    luffyfly
    on Oct 27, 2022
    More actions

    I planned to write a game engine use AS and I have programmed for a month, but now I give up AS and choose Rust because lack of supports for closures. Just let you know, It's not reasonable to not support closures in any case.

  18. gabriel-fallen commented on Oct 27, 2022

    @gabriel-fallen
    gabriel-fallen
    on Oct 27, 2022
    More actions

    @luffyfly as a Functional Programming enthusiast I totally agree that closures are super useful and handy. But are you sure closures in Rust are implemented for Wasm efficient enough for your game engine?

    As a general observation, in the current state Wasm presents a poor target for Functional Programming: doesn't efficiently support heavy use of function pointers, closures and GC. Moreover AssemblyScript is not really a Functional Language. And if you're programming in OO style it's not hard to implement purpose-built closures out of objects (going as far as Strategy Pattern if really needed).

  19. luffyfly commented on Oct 27, 2022

    @luffyfly
    luffyfly
    on Oct 27, 2022
    More actions

    @gabriel-fallen I'm pretty sure about that, because When I call the fetch() function of the web host from Wasm, I have to use closures as callbacks to avoid blocking the main thread.

  20. MaxGraey commented on Oct 27, 2022

    @MaxGraey
    MaxGraey
    on Oct 27, 2022
    MemberAuthor
    More actions

    I have to use closures as callbacks to avoid blocking the main thread.

    Just to note. Closures and async (cooperative multitasking) are two independent things

    Reacted by Alexander Chichigin and Oncelock
  21. CryZe commented on Oct 27, 2022

    @CryZe
    CryZe
    on Oct 27, 2022
    More actions

    doesn't efficiently support heavy use of function pointers

    Closures in Rust are rarely represented as function pointers and instead as anonymous structs that have a "call" method that is statically dispatched rather than dynamically in almost all cases. The method is almost always inlined too, to the point where almost always you can't even tell it ever was a closure to begin with. It's very strongly a zero cost abstraction there.

    Though I'm also wondering why you say function pointers aren't well supported by Wasm. As far as I know they are just functions stored in the function table and then called via an call_indirect and an index into the table. I don't see a problem with that approach, other than having to know all the functions that are going to be called indirectly ahead of time, but that's not a problem for a compiled language.

    GC though is definitely a problem with closures for languages that don't have an ownership system.

    Reacted by Sʜɪᴍᴜʀᴀ Yū
  22. luffyfly commented on Oct 27, 2022

    @luffyfly
    luffyfly
    on Oct 27, 2022
    More actions

    @CryZe I have tried function pointers, and It worked but It is so weird and difficult to use.

  23. luffyfly commented on Oct 27, 2022

    @luffyfly
    luffyfly
    on Oct 27, 2022
    More actions

    @MaxGraey If AS has aysnc implementations, I will not take closures.

  24. MaxGraey commented on Oct 27, 2022

    @MaxGraey
    MaxGraey
    on Oct 27, 2022
    MemberAuthor
    More actions

    @luffyfly Again closures and aysnc is absolutely different things. Dsynchrony without calling "then" in returned Promise but through "await" does not use dispatch at all. You decide what you want.

  25. luffyfly commented on Oct 27, 2022

    @luffyfly
    luffyfly
    on Oct 27, 2022
    More actions

    @MaxGraey See #376 (comment).
    Actually, I want both.

  26. butterunderflow commented on Dec 14, 2022

    @butterunderflow
    butterunderflow
    on Dec 14, 2022
    More actions

    What is the current state of the closure implementation? I've seen several PRs related to closures, but none of them have been merged. I'm curious about what is the biggest block of closure implementation.

  27. pchasco commented on Jan 25, 2023

    @pchasco
    pchasco
    on Jan 25, 2023
    More actions

    I am interested in using AssemblyScript, but must admit I am reluctant to use until closures are implemented. Yes, obviously there are workarounds, but they are inconvenient and add to code bloat. All TypeScript developers whom I assume this project hopes to pilfer are accustomed to the convenience of closures and to some degree of functional programming.

    Many languages that target WASM already support closures, so I am confused as to what it is about AssemblyScript specifically that prevents closures from being implemented. I would be surprised if some limitation in WASM were a real roadblock. I saw some mentions of concerns around performance and allocations, which are reasonable, but I wonder if this is one of those situations where a bit of runtime efficiency is sacrificed for developer convenience? And the implementation wouldn't necessarily be the perfect implementation out the gate, so long as it is functionally correct. It could be refined or replaced later when new WASM features are introduced, or if a better implementation is worked out.

    Reacted by Michael, William Raiford, Matias O, Troy, 맛카치, Wolfgang Steiner, felixroos, Samrat Bhandari, Congcong Cai, Dan Kersten and 1 more
    Reacted by butterunderflow, Luo Gang, Troy and 맛카치
  28. alienself commented on Mar 15, 2024

    @alienself
    alienself
    on Mar 15, 2024
    More actions

    Any update on this? Another year without closures 😢

    Will 2024 the year of closures and thread support in AssemblyScript?

    Reacted by jsoldi, George Phillips and Chase Carlson
    Reacted by Michael, 맛카치, Dmitry and Artem Simonenko
  29. LeXXik commented on Aug 23, 2024

    @LeXXik
    LeXXik
    on Aug 23, 2024
    More actions

    Upvoting this. Array find functions, like findIndex are pretty much useless without closures, as you can't compare an object property with some passed variable.

    Reacted by Michael, Dmitry, Christophe Deveaux, 맛카치, George Phillips, Laurence 'GreenReaper' Parry, Artem Simonenko, Rodrigo Faria, Joachim Wester, LeeHo and 1 more
  30. leeho0108 commented on May 22, 2025

    @leeho0108
    leeho0108
    on May 22, 2025
    More actions

    any news for closures? Or is there any plan or time we can expected?

    Reacted by Rene Leonhardt and Laurence 'GreenReaper' Parry
  31. GreenReaper commented on Dec 12, 2025

    @GreenReaper
    GreenReaper
    on Dec 12, 2025
    More actions

    Thanks so much for working on this, @BlobMaster41; I was literally telling someone at Wolfery how closures weren't possible in room scripts yet and dropped by to see you were working on it. Will be keen to see it in action, assuming it lands...

    Reacted by Anakun and Abdiel Lopez
    Reacted by Rene Leonhardt and Abdiel Lopez
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancement

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      You are visitor 01402503 — GeezerVison does not store the pages you look up.
      Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.