Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Slowly but surely WASM is turning into Common Lisp! (:

I kid, sort of. Greenspun and all that.

(Common Lisp has support for multiple values built in. They’re not a “data structure” like a tuple, but rather an extension to data flow and control. Returning a tuple vs returning multiple values are different things, especially when the consumer of the values-produced only actually uses a single value, as is usually the case with e.g. multiple-value-returning functions like GETHASH or FLOOR. FLOOR returns the quotient of floor(x/y), as well as the remainder as a second value. Most of the time you don’t need the second value, and there’s no need to “opt-out”.)



Please don't tell anyone. WASM can be better, but they need to not think it's Lisp.


I was reminded of Go and Forth :-)

It's not really Forth because in WASM, each block of code needs to have a statically determined stack effect. I wonder if there might be a nice Forth-like syntax for WASM, though?


At https://github.com/darius/idel is a Wasm-like VM I made in the early 2000s with a Forth-like source syntax and static stack effects. (Tail-call optimization too.) I regret making it a little too simple and then abandoning it -- it seemed too hard to try to get traction with such a thing.


It's kinda unrealistic to endow a Forth-like syntax with things like types and pre-determined stack effects. You end up with something not too different from the usual lambda syntax aside from postfix application, which in this context would simply be De Bruijn notation for lambda-like terms.


I think Factor is an existence proof that you're mistaken about this.

Check out the section of the docs labeled "The stack": https://docs.factorcode.org/content/article-handbook-languag...

Note that, in particular, there is full support for stack-effect declarations and that there is a static static-effect checker.


IIUC, Lua also handles multiple return values the way you're advocating. I like the way Lua does it.


"Turning into" is the wrong phrasing. It's enabling Lisp.

And also Python, Go, Rust, Haskell...


pretty hard to enable lisp without eval or garbage collection


You might find these interesting:

- https://github.com/WebAssembly/gc

- https://github.com/WebAssembly/reference-types

- https://github.com/WebAssembly/funclets

As for the eval thing, you surely can compile and execute code on the fly. Such features would be enabled by the aforementioned proposals, but should be possible today as well (through shared memory).


you mean you can compile code into a separate module, right? Which could then access the original module only via marshaled calls through the javascript layer? I guess anything can be compiled into anything but I wouldn't prefer to target lisp to such an environment.


Yes, what you are saying, but that can be optimized very well after the proposals come through.


My Intel CPU also doesn't have garbage collection or eval opcodes and yet it runs Lisp...


your intel CPU allows you to iterate through the stack and jump to a location in the heap though (as long as you don't mark the page NX). These features are used by most gc and jit implementations respectively. wasm requires all functions to be specified at module creation time and gives no access to the stack.


There are plenty of GC algorithms to chose from, and several examples how to implement them in WebAssembly already.

https://github.com/appcypher/awesome-wasm-langs/blob/master/...

No need to get everything served on a plate.


Those have suboptimal GC implementations because of the lack of primitives to do better. Some just straight up use reference counting and leak anything with circular references.


So what?

Yes some have buggy implementations, others could do better, yet they exist.

I rather have a half working bicycle that takes me to town than none at all.


What is a "half working bicycle that takes me to town". Like by definition it fulfills it purpose.

A garbage collector that breaks down on circular references is crazy town for real work. Like there's even AI Lab Koan about that specifically. http://catb.org/jargon/html/koans.html

A better example would be a bicycle without the front wheel. Yeah, if you're real good and real careful you can probably treat it as a unicycle and get to where you're going, but you'd be real pissed if someone sold it to you as a working bicycle, no?


Not all GC enabled languages that currently target WebAssembly are buggy as you make it to be.

PNaCL also did not have direct GC support, yet it got released with working OCaml support.


Did anyone actually use that OCaml support? Like a single production application?


Maybe yes, maybe no.

Enjoying moving goalposts?


It's not moving goalposts. If nobody is using it, not even the developers who put in the effort to make it work, it's a pretty good sign it's not a production ready solution.

Like "technically works enough for a demo" isn't what I'd call full support.


The whole point of this thread was that implementing a GC is possible with what WebAssembly provides today, no need to wait for an API that might never come.

Yes there are bugs in some implementations and others could have been better done, it happens.

Discussing technicalities of who is using what in production is moving goalposts.

Not a big fan of having lost PNaCL in favour of WebAssembly craziness, but that is what we have to work with, GC might come in 5 years time or even never, who knows.

What I know is that despite the flaws, many developers are eager to implement something with what we have now and kudos to them for trying it out.

“They did not know it was impossible so they did it” ― Mark Twain


Like, a no-op GC is a valid implementation as long as your code doesn't allocate too much in the first place. If you ran into that would you consider their implementation buggy and just give them time? Similarly reference counting based GCs _can't_ do anything other than leaking object graphs with circular references, fundamentally. It's not a buggy implementation, it's a fundamental limitation of the technique.

Also, I looked into it. PNaCl couldn't run OCaml, only NaCl. PNaCl (and wasm) doesn't really let you scan stacks, but NaCl does, so they had to drop a real GC with the semantics you'd expect on the transition to PNaCl.

"They did know it was impossible, so they didn't bother" ~ monocasa

And once again, explaining why your examples aren't valid and refuting them isn't "moving goalposts".


Sure it is, because you keep focusing on the few buggy implementations, instead of those that actually work.

There are plenty of leaking object graphs with circular references implementations in native CPUs, and yet people do use them, starting with C++ standard library collection types or Objective-C ARC.

So your point on refuting whatever I present instead of appreciating the work of the implementations that actually work is kind of moving goalposts.

Because apparently people bother with native CPU implementations, even with those bugs, so why should WebAssembly be any different?!?

You don't want to bother, fine go watch tv, play football, whatever is your fancy.

Me, I will be supporting anyone that with the WebAssembly we get to play with, decides to implement their favourite language.

https://docs.assemblyscript.org/details/runtime#collecting-g...

https://docs.microsoft.com/en-us/aspnet/core/blazor/hosting-...

https://fsbolero.io/

https://swiftwasm.org/

https://tinygo.org/lang-support/

Like they say back home "Those that don't have dogs hunt with cats".


AFAICT this is very different from lisp's multiple values because, unlike lisp, you need to explicitly handle any extra values.


I feel like you could do the same thing with tuples and "splats", like in Ruby. Just wrap your values in thunks, and call it the same.


You can't because in CL if you only want first return value, you can pretend that the function only returns one value.

For example, the CL integer division functions (it has each of floor, truncate, and round) all return the remainder as a second value, but you can ignore that fact if all you wanted was division:

  (floor 5 3) -> 1 ; 2

  (let ((x (floor 5 3)) x) -> 1

  (setf (values x y) (floor 5 3)) (list x y) -> (1 2)


Well seeing as I can't find an optimization in CLISP for changing the underlying div instruction, I'm assuming this is going to run the same. Is this correct?

My only point here is, I guess, that a thunk (0 argument function) acts a similar way. If we had need to call two instructions (or increase bitwidth) this might suffice:

    def floor(n, d = 1)
      [-> { n.div(d) }, -> { n.modulo(d) }]
    end

    p *floor(10.3)
    #<Proc:[email protected]:4 (lambda)>
    #<Proc:[email protected]:4 (lambda)>

    p floor(10.3).first.call
    # 10

    p *floor(10.3).map(&:call)
    # 10
    # 0.3000000000000007


I guess my point is that multiple values are transparent; lots of times there are functions for which it's sometimes useful to have auxiliary values, but most of the time the primary value is all that is needed.

There are certainly arguments for why this might be a bad idea (if your the sort of person who likes rich type systems, you're unlikely to like this), but there is no way to do this in WASM efficiently, while there is for pretty much all real machines.

FWIW, I'm not sure about CLISP; I haven't used it for almost 20 years. Sbcl and ccl will both omit the calculation of the modulo when it is not needed. The ergonomics of a thunk or a tuple are different.




Consider applying for YC's Winter 2026 batch! Applications are open till Nov 10

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: