Matt Roelle | Fennel: The Practical Lisp

04/08/2022

Fennel?

Around 6 months ago, I discovered a little lisp called Fennel. It compiles to Lua, meaning it's extremely portable since Lua runs basically anywhere. Lua interop is seamless. It runs flawlessly on desktop, in the web, on mobile devices, gaming consoles and on microcontrollers to name a few.

Lua is written in portable C code and embeddable by design, extending any game engine with Lua, and therefore Fennel, is a breeze. The official implementation of lua is roughly 30,000 lines of C code according to lua.org, it's tiny. There are libraries for many languages[1] that make getting lua running inside of your project possible in an afternoon.

Fennel quickly became my full-time programming language for all things. This website runs Fennel via OpenResty. I am working on a video game project & game-dev framework using the LOVE engine. I write Fennel to extend my Neovim configuration. On a tilde server I'm a part of we run an IRC server written in Fennel, called Taverner.

Seriously, I use it everywhere.

There are a couple of runtimes available for you to use. On some platforms, you have access to LuaJIT. On platforms that don't support JIT compilation, like some embedded devices, phones and gaming consoles, you can easily use PUC Lua, which is the official implementation.

LuaJIT is a JIT compiler for Lua, and it is stupid fast. On platforms like iOS and game consoles, where you don't have access to LuaJIT, you can use the LuaJIT Interpreter as an alternative to PUC which still has impressive speeds.

Overview

Now I'm not an expert lisp programmer, I've dabbled in Common Lisp and Racket and written a decent amount of Clojure. So, I can't really comment on how "true" of a lisp Fennel is. However, when I think about all the the things I like about the other lisps I've used, Fennel checks all the boxes.

Lispy Syntax

Syntatically speaking, Fennel looks a lot like Clojure. Here's a quick example, taken from the website.

;; Sample: read the state of the keyboard and move the player accordingly
(local dirs {:up [0 -1] :down [0 1] :left [-1 0] :right [1 0]})

(each [key delta (pairs dirs)]
  (when (love.keyboard.isDown key)
    (let [[dx dy] delta
          [px py] player
          x (+ px (* dx player.speed dt))
          y (+ py (* dy player.speed dt))]
      (world:move player x y))))

If you're familiar with lisp, this code will hopefully be somewhat clear. The map syntax is similar to clojure. There is some syntatic sugar, the things that start with colons are strings. The infix colon notation comes from Lua, it is a method calling convention.

The (parenthesis) denote operations, function calls or macro invocations, much like all other lisps.

The [square brackets] denote either a sequential table or binding expression, like function args or a let binding. The {curly brackets} denote a table. These forms can also be used for destructuring.

Tables are Lua's answer to Objects. Tables are the only data structure, they are basically fancy hashmaps. Even sequential data is represented as a table with numeric keys.

Functional and Object-Oriented

Lua has support for first class functions and Fennel enforces immutable locals, unless declared like (var x 10), allowing for a really strict functional style if that's your jam. If you're coming from a Clojure background, there is a cljlib, a Fennel library that implements a large portion of Clojure.

Lua also has these things called Metatables and Metamethods. Metamethods are operator overloading on steroids. You can overload what it means to add a new key to a table, or access a key in a table, so you can literally implement custom class systems, tailor made to your use-case.

Seriously, just Google "Lua class library" and the top 3 links are all various flavors of OOP libraries people made that you can choose from. Do what feels right to you. I've tried them all, they are all more than sufficient.

Here is an article by technomancy that details 5 different flavors of method implementations in Fennel/Lua, some with support for live reloading of instances, sort of like in the Common Lisp Object System

Macro System

Now, I'm still less than a year into writing lisp. I have written one, potentially two macros that I am actually using. I usually end up replacing any macro I write with a regular function. So please decide for yourself if you think the Fennel macro system is up to par.

That said, I've written some really unnecessarily complicated macros with dope syntax and I can attest that Fennel's macro system allows for bending the language literally any way you want to go. There are even backdoor compiler tricks that allow for quasi reader macros.

Read the official macro guide for more information.

Excellent REPL

Fennel has excellent REPL capabilities. If you're a vim user, Conjure works great. There is fennel-mode for emacs.

There is NREPL support via Jeejah. I have done some testing with Jeejah and had promising results, but have not used it extensively

I've recently switched to Emacs full time and I'm using fennel-mode and communicating with an embedded REPL in my game project via STDIO. It works wonderfully.

I now code completely out of a handful of multi-thousand line files. I have my library stuff, shared between projects, and my game code. I no longer need to think about organizing my code because line numbers don't matter, I'm only ever thinking about one function at a time. REPL driven development with Fennel is amazing.

In Conclusion

I absolutely cannot recommend Fennel enough. It's a lisp that's blazing fast, portable as heck, and has access to the whole Lua ecosystem, which is way more mature than I think most people realize. It's the lisp I always wanted

In using it, I have written more good software in the last 6 months than ever before in my life. I urge you to check it out. Visit fennel-lang.org to learn more, or get started on game dev with gitlab.com/alexjgriffith/min-love2d-fennel. You won't be disappointed

If you have questions or want help getting started, please email me, matt@mattroelle.com! I'd love to talk Fennel anytime. We are also organizing a Fennel User Group. I'll update this post when we get the info up on the Fennel website.

[1]  Fennel runs on the Lua runtime. The Lua runtime, both PUC and luajit implementations, are easily supported in many environments. There is official support for: C/C++, Rust, C#, and more.

Since most languages have an FFI to C, rolling your own integration in almost any environment is not that hard either.