HN user

technomancy

3,634 karma

Hacker-errant, design and sell DIY keyboard kits: http://atreus.technomancy.us

Coding in Clojure, Fennel, and Lua.

Posts2
Comments1,356
View on HN

The docs for using QMK are pretty good, but trying to actually understand the code made my head spin; that's why I decided to write my own firmware. Right now it doesn't have any of the fancy features of QMK, but it currently clocks in at under 200 lines, so I have a suspicion it'd be easier to implement those features from scratch in Microscheme than understand their current implementation in QMK.

Hi; original author here.

That article goes into more background but it's based on a much earlier version of the firmware; as of last week I've gotten it to be feature-complete and stable, and I've been using it as my daily driver.

I have been meaning to follow up the article with a part 3 that covers the rest of the features, but in the mean time I have added copious comments to the source link above such that it covers almost everything you need to know. (This is probably the most heavily-commented piece of code I've ever written with a 1:1 code:comments ratio.)

I have also been working on a GUI configuration tool in Racket that emits Microscheme source files you can compile and upload to your keyboard: https://git.sr.ht/~technomancy/menelaus/tree/master/gui.rkt https://p.hagelb.org/menelaus.png

If you've looked at building an Atreus before but gotten intimidated by the soldering, we have a kickstarter for a non-DIY version that's significantly cheaper than the kit: https://www.kickstarter.com/projects/keyboardio/atreus

The Dactyl is definitely neat. It's designed for a different use case tho; you wouldn't take a Dactyl with you when you're out and about, while the Atreus is specifically designed for travel.

I'm working on a similar programmatic definition of the Atreus here: https://icosahedron.website/@technomancy/101809590723531930

It's still a work-in-progress, but being able to tweak the input parameters and recompile to get the board regenerated is really handy! The circuit board already uses programmatic alignment for placing the switches and diodes: https://github.com/technomancy/atreus/blob/master/atreus.rkt

Atreus creator here. It doesn't happen often, but occasionally I have customers run into trouble putting their kit together, and I'm committed to always stepping them thru the process of troubleshooting and walking thru the fix. The last thing I want is for you to buy a keyboard and never get it working properly.

Here's an instance where someone damaged their kit's microcontroller and was walked thru the repair process: https://old.reddit.com/r/MechanicalKeyboards/comments/5clvio...

There are a few factors at play that make a language good to learn: simplicity, learning materials, and suitability for learning projects.

On the first point, Fennel is simpler than any other lisp I know, beyond "my first interpreter" type Schemes. But you'll find much better learning materials for some other lisps; in particular Racket is exemplary in this regard.

So I would say it comes down to the learning projects you might want to build; this is always the trickiest part of learning any language. If you want to learn by creating a game, Fennel is a great choice. Likewise if you want to extend an existing program that already has Lua support, such as AwesomeWM or Redis. But for most "industrial" type projects that access external APIs or write to existing file formats, you'll have a better time in Racket due to the better library ecosystem.

why does fennel description insist on macros as compile time feature?

This just means that it's possible to ship the compiled code on its own and have no dependencies on Fennel at runtime. It doesn't mean you can't ship the compiler if you want to; the choice is up to you. In fact, I strongly recommend in most cases shipping the compiler so you can write and refine your code interactively as the program runs. It's helpful to think of "compile time" as a subset of runtime; it's just that portion of runtime when the function that's running happens to be the compiler!

Fennel is very well-suited for live-coding; I used this heavily when writing my entry to the Lisp Game Jam this spring. I blogged a bit about how live-reloads interact with the Lua module system here: https://technomancy.us/189

I've looked briefly at Lumen. I don't do any JS work, so cross-compiling to JS doesn't really appeal to me personally. I would expect that compiling to two targets would make it more difficult to implement clean, readable compiler output the way Fennel does, but I haven't checked.

The docs for Lumen are a bit sparse, but it looks like it doesn't make globals opt-in or make you flag locals as mutable the way Fennel does. IME calling these things out explicitly makes certain kinds of bugs easier to catch. Fennel also allows for optional arity checks on functions.

I would expect that Lumen's keyword arguments would cause some complication when interoperating with Lua, which doesn't have them. In addition, cross-compiling to both JS and Lua means that it can't seamlessly support multiple return values, which are used pervasively in 3rd-party Lua libraries.

As I mentioned elsewhere, Fennel can run in the browser with Fengari, but it's a very different approach because Fengari includes a full Lua VM. This means there's some overhead to load the VM (~220kb) but once it's loaded you get access to coroutines, which are a much nicer way to code non-blocking functions than callbacks.

But these are just my initial uninformed impressions.

So, since many mediawiki systems use lua, does this mean that you can code in Lisp (or Fennel), transpile to Lua, and then run the code as a module in a media wiki system?

Yes, easily.

With a little bit more work you could load the Fennel compiler up server-side so it could do the compilation for you and support Fennel "natively". I have done a similar process with TIC-80 despite knowing very little C: https://github.com/nesbox/TIC-80/pull/597

What about Common Lisp libraries? would they be transpiled too?

No, Common Lisp is a completely different language with little in common with Fennel.

The creators of Fennel and Urn both chimed in to describe the differences between the two here: https://news.ycombinator.com/item?id=16567763

tl;dr Urn is a much bigger language that happens to compile to Lua as an implementation detail. Fennel is much closer to Lua and is dramatically simpler, only diverging from Lua semantics when the changes can be implemented purely at compile time. (immutable locals, blocking accidental global references/setting, etc) The compiler output from Fennel tends to be pretty readable and looks a lot like the input.

If you need to write code that runs in the browser, Lumen is one choice, but you can also run Fennel in the browser using Fengari: https://fengari.io/ I know next to nothing about frontend development but was able to integrate Fengari into https://fennel-lang.org to get the live REPL working with ease.

Fennel is continually tested against mainline Lua 5.1, 5.2, and 5.3 as well as LuaJIT: https://github.com/bakpakin/Fennel/blob/master/Makefile#L6

It works great with Fengari for client-side scripting; in fact this is what we use on the web site for the live REPL. We have run some tests against other obscure implementations such as Luerl and Rembulan but have encountered some bugs in those compilers that need to get addressed before they get integrated into the test suit.

This was my game, and I've contributed significantly to newer features in the Fennel compiler; happy to oblige if you have any questions about either.

Interesting you should mention Mandlebrot:

https://old.reddit.com/r/programming/comments/6eg0x/where_ar...

the inner loop of mandelbrot is now identical (at the ucode IR level) to the IR generated by a C compiler. All type checks have been eliminated and only the pure (statically typed) FP adds/multiplies are left (the remaining task is to transform the ucode to the best possible machine code -- working on it). Similar results can be obtained for other numeric loops.

^ This is from a ten-year-old post about LuaJIT optimizations.

Just pause the VM with the browser in it -> 0 % CPU, but can still read what's on the display.

On a related note, how is "halt all execution and scheduled future execution of scripts in the current tab" not a solved problem for mainstream browsers? Or maybe it is and somehow I missed it?

It's really not that bad with NoScript; my primary machine is from 2009. I was considering buying a backup machine that was a few years older, and the main thing that kept me from pulling the trigger was that the LCD brightness was kinda crap, not anything to do with performance.

The original title was "by hand" which is much better because the main thing that distinguishes this from hundreds of other build logs is that the switch plate was not cut with a laser or waterjet, but with hand tools.

I don't know if it's systematic per se, but I wrote a comparison here: http://technomancy.us/169

My take is that the biggest difference is not in the language but the runtime. Nearly all the things that Racket lacks (except world-class GC and JIT compilation) can be added in after the fact due to its massive flexibility; Racket is a natural choice when you want a lisp but can't afford the memory or launch time overhead of the JVM. Unfortunately the things that Racket does well typically cannot be retrofitted onto Clojure due to its sloppy semantics around nil, but maybe Clojure's port of Racket's contracts system will change the balance there.

Could someone give an example where it really shines and clojure's loop/recur just doesn't?

In practice you don't replace TCO with loop/recur; you replace it with lazy sequences. They both solve the "how to work on unbounded data while consuming bounded memory" problem even though at first glance loop/recur seems more similar.

Most people who complain about not having TCO have not fully wrapped their heads around lazy sequences.

It's also massively more capable on the GPIO front. I used mine for a project with a lot of analog input that a Pi would never have worked for.

Not as badly as English is broken.

I mean, it's generally agreed that English as a language has accrued so much historical baggage that it's shed any elegance or coherence it may or may not have once had. It would honestly be surprising to me if the same thing didn't happen to musical language.

I live in Thailand (but not in a big city). Out here it's because if you find yourself without a home, you can go build one out of bamboo. If you have some friends to help you, it can be done in a week.

you can get both in Lisp if you want

The whole point of benefiting from immutable data structures is their ubiquity. If they're something you reach for only when you personally decide you need them, they're unlikely to work well with 3rd-party code; you will have to do a lot more digging to understand how the code you're writing will behave in a concurrent context vs having it be obvious. Making it opt-in is almost making it pointless, unless you never use 3rd-party code.