Language Showcase: Fennel
Fennel brings together the speed, simplicity, and reach of Lua with the flexibility of a lisp syntax and macro system.
Fennel is a Clojure-like lisp with seamless Lua integration which runs anywhere Lua does. Fennel’s maintainer, technomancy, talks in depth about it for today’s language showcase.
How would you describe Fennel to the average programmer?
Fennel is a language a lot like Lua, but it simplifies the syntax and removes some of the most error-prone and troublesome features of Lua while adding modern conveniences like pattern matching and a capable REPL for interactive use. That description omits to mention it's a language in the lisp family, which I think has an undeserved negative association in the mind of the average programmer. Maybe it's better to focus on the concrete benefits like the simplified syntax and lack of operator precedence rules to memorize!
Which problems is Fennel trying to solve?
Like Lua, Fennel's strengths come from its extremely small size. A compiled Fennel standalone program can be distributed in under 300kb, but it can also be embedded inside a larger program. Historically this has been C programs, but nowadays it's possible to put a Lua VM (and thus Fennel) into programs written in Java, C#, Go, Javascript, and other languages. But it's not just small in terms of disk space, it also has a conceptual simplicity that makes it much easier to fit the whole thing in your head.
The other thing Fennel is particularly good for is writing games.
Fennel’s included out of the box in TIC-80, the fantasy console, and it works great in LÖVE, the classic 2d Lua game framework. Every year we have a game jam for lisp-family games and the winners of the competition are almost always written in Fennel.
I've written up more about the rationale behind Fennel on the web site here.
What makes Fennel unique?
Fennel is unique in that the language design space is severely constrained: this is a language without a runtime, so we can only implement features which can happen at compile time. All the runtime semantics are predetermined by the Lua VM. There is no standard library. The only other language I know of which did this was Mirah, which compiled to JVM bytecode without any of its own runtime. But the JVM is a much, much larger target than the Lua VM.
What's the coolest thing that's been built with Fennel so far?
This has to be a tie because it's impossible to pick just one. Ramsey Nasser has created 8fl, which is environment for live-coding chiptune type music. It's is really expressive and fluid. The other one that comes to mind is Jeremy Penner's Honeylisp, which is an environment for live-coding games for the Apple II platform (yes, really). It's an editor and compiler written in Fennel which targets the 6502 instruction set and lets you stream your code changes live to a real 8-bit Apple II computer over a serial port.
What are some of the toughest challenges with building Fennel?
The most difficult thing is holding back on an idea until the time is right.
We'll often come up with ideas for new features that would make something a lot more convenient, but the initial idea just doesn't *fit*.
For example, the first implementation of macros brought in all the macros from a given file without naming them. So you could have identifiers introduced into the code but there was no hint in the code where they came from. This was later identified as an antipattern, so it was replaced by import-macros which had you name the macros you wanted. But we have the old way still hanging around with a big old warning in the docs that it's not recommended.
Another example is our map construct. Almost every language (but not Lua) has a construct like map, which iterates over a list of elements with a given function and returns a new list with the results. We really wanted this early on with Fennel. But Fennel has no runtime, and map is a function. So there wasn't any way to do this that fit. We wanted to put it in, but we held off because we hadn't found the right way. Later on someone suggested an approach based on list comprehensions, which are used for the same thing but are a syntactic construct rather than a function. Following this idea, we added icollect as a macro, and it was a much better design than it would have been if we had tried to add map like we originally wanted to.
We still haven't found a way to express custom types which can roundtrip thru serialization and being read back in again. We have some ideas, but they're half-baked, and we know we're better off without that feature until we find the right way to do it.
Program comparison: arithmetic
Which paradigms does Fennel target?
I can't say that Fennel targets functional programming exactly since there is no support for immutable data structures in the language (you need a 3rd-party library), but idiomatic Fennel uses partial application and higher order functions much more than Lua does. It also pushes you in a more functional direction by making rebinding of locals opt-in instead of opt-out. Lack of statements and early returns also contribute to the "imperative-lite" feel, but in the end it is an imperative language.
I don't really know if this counts as a paradigm, but Fennel focuses strongly on *interactive* programming; that is, coding alongside your running program and making changes to it instantaneously without restarting.
Even if you aren't working in an editor with first-class Fennel support, the repl has built-in support for reloading modules on the fly.
Which language would you say Fennel is closest to?
The runtime semantics are a pure subset of Lua. In particular, the module system is one under-appreciated feature that is very well-designed, and I've come to prefer Lua's modules over Clojure's. However, the syntax is closest to Clojure, with a few improvements to the destructuring syntax and the addition of pattern matching. Many of the syntactic similarities are somewhat superficial, but the macro system in particular uses Clojure's absolutely brilliant solution to Common Lisp's accidental symbol capture issues. It solves all the same problems as Scheme's hygenic macro system in a fraction of the complexity.
Which languages inspired Fennel the most?
When Clojure came out in 2007, it showed us the power of hosted languages; that is, taking a runtime from an existing language and developing on it symbiotically so you don't have to start the whole stack from scratch. At the time this was very novel; the only precursor I'm aware of was Coldfusion, which most people didn't take very seriously. Nowadays hosted languages are perhaps even more common than non-hosted languages, and we're even starting to see them break into the mainstream with Kotlin, F#, TypeScript, and Elixir.
I believe Fennel takes this idea even further than Clojure: since there is no Fennel runtime, compiled Fennel programs are indistinguishable from Lua programs (with maybe a few funny variable names); Fennel functions are Lua functions, and Fennel modules are Lua modules. Of course, the library ecosystem for Lua isn't as rich as that on the JVM, but given its small size and instantaneous startup time, it addresses a niche which Clojure has historically done very poorly.
Show off some cool Fennel code
What's next for Fennel?
We had our 1.0 release less than a year ago, and feature changes have certainly slowed down since then. Right now we're focusing more on tooling. We have a formatter and a lua->fennel reverse compiler to aid porting codebases and example code. We have someone working on a stepping debugger, and someone else just started to implement an LSP server for Fennel.
So upcoming changes to the compiler now are going to be less about changing the language and more about exposing more information like scoping and modules and such from the compiler to third-party tooling through the API.
Where can people learn more?
The main site is https://fennel-lang.org but we've also got a decent wiki that we haven't yet moved off Github at https://github.com/bakpakin/Fennel/wiki
But the main place to learn is the chat which is available both with IRC at #fennel
on Libera Chat and via Matrix on #fennel:matrix.org
depending on your client preference.
The chat is fairly active and very friendly to newcomers.