Hoot: Scheme on WebAssembly

1 day ago (spritely.institute)

It's fascinating how much development's occurring in Guile recently. Unfortunately, a lot seems to be ex-Racketers moving over. The splitting of community effort's sad (particularly as Guile e.g. greatly lags Racket performance or lacks nice libraries like Gauche).

  • > particularly as Guile e.g. greatly lags Racket performance

    Huh. This has not been my experience, rather the other way around.

    I do see a whole heap of benchmarks that contradict it, so I probably should put in some effort to find out why, or if I'm just plain wrong... But at a guess? Guile doesn't have Racket's startup time penalty, and most of what I do is IO bound - and Guile's IO story is better than Racket's.

  • I don't really follow Racket, but I recall that few years ago one apparently fairly significant contributor within their community wrote a blog post about Racket having a Missing Stair problem involving another even more significant (possibly foundational) contributor.

    I'm a complete outsider, cannot find that blog post any more, and also just not invested in the language at all, so I'm hesitant draw conclusions about the validity of any of the accusations that were thrown back and forth at the time, but it seems pretty obvious to me that it's inevitable that the community will end up being split after an event like that.

    https://en.wikipedia.org/wiki/Missing_stair

  • Guile, being a bytecode VM with JIT currently, loses to Chez/Racket overall but it's honestly quite fast. I can make games that run at a smooth 60fps with infrequent GC pauses. Plenty of room to grow but Guile isn't slow by any means. I've never been a Gauche user but Guile has lots of nice libraries these days.

  • I'm happy for Guile to be getting more attention, but wouldn't write off Racket. A few quick thoughts...

    * The recent Guile work on WASM is promising. (Note also Jens Axel Soegaard's recent work on WASM with a Racket-related compiler.)

    * Racket's rehosting atop Chez seems like a good idea, and I'd guess that the Racket internals are now easier to work with than Guile's.

    * Racket has done a lot of great work, and is a nice platform for people who can choose their tools without worrying about employability keywords for their resume. It made some missteps for broader adoption when it had a chance, and several of the most prominent industry practitioner contributors left.

    * Racket still has the best metaprogramming facilities, AFAIK. But even more important than `syntax-parse` and `#lang`, one thing I'd really like from Guile and other Schemes is to support Racket's module system.

    (I really like the ability to define Racket submodules inline, in fragments, for things like embedded unit tests <https://docs.racket-lang.org/overeasy/> and embedded API docs <https://docs.racket-lang.org/mcfly/>.)

    (I also wanted to play with Racket's module system for PL research compilers: having early compiler implementation for a new language first expand into Scheme code, and then later (with submodules) also do native/VM code generation, while keeping the option to still expand to Scheme code (for better development tools, or for when changing the language). For example, imagine targeting a microcontroller or a GPU.)

    * Right now, any Scheme is for people who don't have to do techbro/brogrammer interviews. The field has been in a bad place for awhile, professionalism-wise, and the troubled economy (and post-ZIRP disruption of the familiar VC growth investment scams) and the push to "AI" robo-plagiarism (albeit with attendant new investment scams) are suddenly making the field worse for ICs.

    • > I'd guess that the Racket internals are now easier to work with than Guile's

      Maybe, I couldn't say, but I find Chez's source code very cryptic and hard to read. More so than any other Scheme implementation.

  • > greatly lags Racket performance

    This is a different implementation of Guile, though. Has Hoot (on, say, V8) been benchmarked?

  • I didn't know there was a schism in Racket. Is this about the CS vs BC backend? I thought that was water under the bridge.

It's such an amazing project, I wish it used something other than Guile but you can't have everything.

  • Guile has lots of libraries though, and is the language of Guix. This makes it more likely for people to package their stuff via Guix. Guix itself enriches the ecosystem, and Guile projects can use Guix to make them reproducible.

    A few problems remain though. A good debugger, a good macro expander (geiser in Emacs is able to expand somehow), and solving the issues with R6RS library syntax and standard library bindings, are what comes to mind.

    Racket's multi-core abilities for a long time were mostly heavy weight (places, starting whole new Racket VMs), except for their implementation of futures, but that one was not always useful. I think recently the situation in Racket has improved, but I don't know, whether it is as good as Guile fibers and futures (which are different from futures in Racket).

    • If Guile had more of the "batteries" from the Gauche standard library it might be the perfect all-purpose Scheme. But for just writing a simple application quickly and easily it's been impossible to beat Gauche for me, it's on par with Python and Ruby in that regard IMO.

      1 reply →

    • > Guix itself enriches the ecosystem

      Except that ecosystem Guix provides is not on Windows nor MacOS, making a serious limitation to anyone who wants to develop guile on those platforms.

      I support GNU's mission in general, but I find it ironic that when they push freedom of choice you're forced to make the "right" choice or you're left twisting in the wind.

      1 reply →

    • Aside from the debugging & testing I already mentioned, going only through guix is a tough sell considering the very tiny amount of people that use it. It's also incredibly slow and doesn't have many packages available. Even for Emacs, it has this weird way of going around Emacs-installed packages (GNU or MELPA channels). Just like Guile docs, it also doesn't tell you of a good way to do things, it only says "here is what exists" with too little guidance imo. It means people have to figure out how to setup things properly on top of all the rest. It makes for a terrible onboarding.

    • Guile' backtraces are useless to the point it baffles me how the community can work on anything else, or how it's not the first item on any such lists as above.

  • Other than Guile as in different Scheme implementations? It's usually not too difficult to port things between Schemes. Especially if you use standard R6RS or R7RS library syntax.

    • Guiles debugging has been a nightmare in the 3.x series. Which is rather surprising since it was probably the easiest scheme to debug in the 1.x days.

      It got so bad I moved to racket as my daily driver.

      5 replies →

Glad to see stuff like this, it seemed like the hype for compiling to wasm died out a while ago. Hope we keep seeing more wasm languages so I can continue to avoid using javascript, ha!

I love this so much! It got me thinking about the future we’re heading towards, that took me down a rabbit hole.

As agents become the dominant code writers, the top concerns for a “working class” programming language would become reducing errors and improving clarity. I think that will lead to languages becoming more explicit and less fun for humans to write, but great for producing code that has a clear intent and can be easily modified without breaking. Rust in its rawest form with lifetimes and the rigmarole will IMO top the charts.

The big question that I still ponder over: will languages like Hoot have a place in the professional world? Or will they be relegated to hobbyists, who still hand-type code for the love of the craft. It could be the difference between having a kitchen gardening hobby vs modern farming…

  • I have been wondering what an AI first programming language might look like and my closest guess is something like Scheme/Lisp. Maybe they get more popular in the long run.

    • I think the bitter lesson has an answer to that question. The best AI language is whichever one has the largest corpus of high quality training data. Perhaps new language designers will come up with new ways to create large, high quality corpi in the future, but for the foreseeable future it looks like the big incumbents have an unassailable advantage.

      1 reply →

    • I'm working on what I hope is an AI-first language now, but I'm taking the opposite approach: something like Swift/DartTypeScript with plenty of high level constructs that compactly describe intent.

      I'm focusing on very high-quality feedback from the compiler, and sandboxing via WASM to be able to safely iterate without human intervention - which Hoot has as well.

    • Smalltalk offers several excellent features for LLM agents:

      - Very small methods that function as standalone compilation units, enabling extremely fast compilation.

      - Built-in, fast, and effective code browsing capabilities (e.g., listing senders, implementors, and instance variable users...). This makes it easy for the agent to extract only the required context from the system.

      - Powerful runtime reflectivity and easily accessible debugging capabilities.

      - A simple grammar with a more natural, language-like feel compared to Lisp.

      - Natural sandboxing

      1 reply →

    • LLM's are mainly trained on English natural language text, so you'll want a language that looks as much as possible like English. COBOL is it, then.

repl.wasm is 1.6 MiB

Seems a bit much. How large would the todo example be?

  • This is not the smallest REPL binary out there but it's far from the largest I've seen. My anecdotal evidence suggests it's somewhere in the middle or maybe smaller than average. We haven't configured our web server to gzip these assets (we should). Gzipped repl.wasm is 339K. This binary hasn't been run through wasm-opt, either.

    Hoot is primarily an ahead-of-time compiler, not an interpreter, so this REPL demo has a lot of stuff that a production binary wouldn't have like a macro expander, runtime module system, and an interpreter. A significant chunk of extra code. We have a todo list example [0] in a separate repo, btw. On my machine, todo.wasm is 566K uncompressed, 143K gzipped. And that includes a virtual DOM diffing algorithm implemented in Scheme.

    That said, there is quite some room to optimize binary size. For example, we know we're generating too many local variables in functions [1], many of which could be optimized away with an additional compilation pass. We expect that to have a significant impact on binary size. There's also certain workarounds we're doing due to features missing from Wasm at the time of implementation that add to total binary size. Lack of native stack switching for continuations is one example. Adopting the stack switching proposal should shave off some bytes.

    [0] https://codeberg.org/spritely/hoot-ffi-demo/

    [1] https://codeberg.org/spritely/hoot/issues/193

This is what JavaScript was supposed to be until Netscape forced the dude to use a C/Java-like syntax.