Hacker News new | past | comments | ask | show | jobs | submit login
The Fennel Programming Language (fennel-lang.org)
235 points by tosh 31 days ago | hide | past | favorite | 77 comments



I've talked about it a few times before when it comes up but I love this language and think it's just an incredible technical accomplishment.

Lua is beloved and I have a ton of respect for that project in itself, it's definitely one of the seven wonders of the programming world. But the (well-considered) compromises for its intended use as an embedded scripting language make it pretty rough for large complex projects.

Fennel is just an extremely focused aid to some of the worst lua warts. Pattern matching alone is incredible for the overloaded tables lua uses. The macro system is excellent, and since a lot of the time what you're doing with lua is defining a DSL anyway it gives you more powerful tools for that.

I initially recoiled but I now think it was a genius decision to do it with just a few special forms over the lua semantics. Other than the lisp syntax there's very little new to learn to use it. And the best part is that it hooks into the lua module loader system so you can freely mix tables and functions between the two, a life changer in legacy codebases.

Can't say enough good things about fennel. There are a lot of languages that I like but it's one of the only ones I think is actually good. I rarely write normal lua anymore it's just so flexible.


I ported LUA to the Nintendo DS in 2007. It wasn't designed for embedded devices back then, let me tell you, it was one hell of a headache to chop down to size.

Not sure if the market for LUA has changed since then...

Still, it's a great language for scripting.


Lua isn't made for embedded devices, it's made to be embedded into other programs and systems. It is widely used as a scripting language in games. There's even APT malware that has used Lua as a scripting language.


> Lua isn't made for embedded devices

True, however it was later adapted and ported to a number of small architectures. NodeMCU is just the most popular one of them as the EluaProject started years before even the ESP8266 was available. https://eluaproject.net/


it's still used heavily in some roblox games (they moved to their own implementation with convenience features) and openresty. https://github.com/leafo/lapis

also, minor observation: https://lua.org/about.html#name


IIRC, Factorio uses Lua for all Mod scripting.


This speaks to the performance of lua, as some of the factorio mods are quite extensive


Related:

Why Fennel? - https://news.ycombinator.com/item?id=37497131 - Sept 2023 (102 comments)

Language Showcase: Fennel - https://news.ycombinator.com/item?id=32349491 - Aug 2022 (2 comments)

Fennel: A Practical Lisp - https://news.ycombinator.com/item?id=31029478 - April 2022 (85 comments)

Fennel: A Practical Lisp - https://news.ycombinator.com/item?id=30963628 - April 2022 (2 comments)

Fennel 1.0.0 Released - https://news.ycombinator.com/item?id=29221245 - Nov 2021 (1 comment)

Fennel 0.8.0 Released - https://news.ycombinator.com/item?id=25842797 - Jan 2021 (1 comment)

Raymarching with Fennel and LÖVE - https://news.ycombinator.com/item?id=24835766 - Oct 2020 (3 comments)

Fennel – Lisp in Lua - https://news.ycombinator.com/item?id=24390904 - Sept 2020 (112 comments)

Neovim Configuration and Plugins in Fennel Lisp - https://news.ycombinator.com/item?id=21676606 - Dec 2019 (19 comments)

Fennel – Lisp in Lua - https://news.ycombinator.com/item?id=18016168 - Sept 2018 (62 comments)


For folks who are curious, it is possible to use Fennel w/ the Lua in Lualatex:

https://wiki.fennel-lang.org/Fennel-in-LuaTeX

but no activity for this on tex.stackexchange (yet?) --- given the difficulties in doing recursion (see: https://tex.stackexchange.com/questions/723897/breaking-out-... ) using something like to Lisp could potentially be very helpful.


I've always wondered about this. Thanks for sharing!


"you can use Fennel right here without installing anything"

It sits at 99% forever.


With "[sprintf] unexpected placeholder".

This is presumably https://github.com/fengari-lua/fengari/issues/147. You can workaround it by substituting "%.f" for "%.0f" in https://fennel-lang.org/fennel/fennel.lua.

However Chrome won't let me override the contents of this URL for some reason. And the first Firefox response override extension I tried ended up confusing the page. So you can alternatively override the contents https://fennel-lang.org/fengari-web.js as follows:

`"string" == typeof o.response ? a.f = yt(o.response.replace("%.f","%.0f")) : a.f = new Uint8Array(o.response.replace("%.f","%.0f"));`

(Just before `var l = Ee(a);`)

Opened a bug for it: https://github.com/bakpakin/Fennel/issues/485


And it looks like its already fixed.


It has not been fixed yet.

> I've turned the web repl back to 1.5.0 for now while we investigate the issue.[1]

[1] https://github.com/bakpakin/Fennel/issues/485#issuecomment-2...


There's also a console message for an unhandled promise rejection with an error message about an unexpected sprintf placeholder.


MoveOrDie and Acro were made with Löve and Lua. Does anyone know if I can make a 2d game with fennel alone, without touching Lua? Alternatively, what would be a better choice for simple 2d graphics (Python would be preferred)? I want to experiment with something like Dwarf Fortress.


Depending on your preferred workflow you would typically have a tiny loader stub in a .lua file that you never need to touch and all of your code in Fennel. You can still add third party Lua libraries if you desire and seamlessly call back and forth between these and your Fennel code. You can even have a live REPL to eval code in your running Löve session.


Lua is a very simple language. If you're coming from Python, it's probably even quicker to learn vanilla Lua (and Löve) than using something like Moonscript or fennel.

If you're set on Python there is PyGame. I haven't touched it for a number of years but back then it seemed less polished and waaay less performant than Löve.


>what would be a better choice for simple 2d graphics (Python would be preferred)? I want to experiment with something like Dwarf Fortress.

If you want to experiment with something like Dwarf Fortress, you don't need 2d graphics. You can just start with ASCII-graphics like Dwarf Fortress did.


True, but at some point I need to integrate graphics, so I don't think it's a bad idea to have simple tile graphics in place. Also, I would need some kind of inventory, text fields, etc. and they might be much easier with a framework that supports them - as you see, I have next to no experience in this area.


Yeah, I think most of the games in the Lisp Game Jam are made with Fennel and Love. The game jam is cohosted by technomancy, the maintainer of Fennel.

https://itch.io/jam/spring-lisp-game-jam-2024


You'll always need to deal with a bit of Lua afaik. If you like fantasy consoles, you can use TIC-80[1] to not have to deal with any Lua.

[1] https://tic80.com/


Has anyone tried writing their NeoVim config in Lua? Is this a good use case for Fennel?


I just finished converting my rather large (just under 500 lines) init.vim to Lua. It took way longer than I had hoped. I feel like I've forgotten the motivation and what benefits it was supposed to bring. At this point I really don't want to consider another conversion, using Fennel or otherwise.


I think about writing the config in fennel all the time, but I’m not a big config tinkerer and already worry about struggling with Lua when I have an upgrade gone awry.

I’ve found the neovim ecosystem to churn way more in recent years than it did when I initially started using it 8 years ago. It really reminds me of the JS ecosystem of the past decade: full featured plugin that works great decides to strip things back so that it’s functionality is all plugable and you suddenly have to wade through 2 major release’s migration docs to get things kind of working like they were before. I digress to point out why it might be hard to have yet another layer to translate through. Though if you don’t have kids, I’d say “go for it!”


I have 3500 lines config file in Lua that I have converted from VimScript last year. I see that Lua it's way easier to maintain than VimScript.

Fennel solves some Lua kirks, so I think that is a good use case, since Fennel has some cool features that can help to maintain the code. Right now I am moving the plugins that I maintain to Fennel.

If interested take a look on the https://github.com/Olical/nfnl and https://github.com/gpanders/fennel-repl.nvim plugins.


Yes, I converted mine. I have a bunch of macros that mapping super easy and ergonomic. https://github.com/Grazfather/dotfiles/blob/master/nvim/fnl/...


I've been writing my config and plugins in it for years :) https://github.com/Olical/conjure https://github.com/Olical/nfnl


There is a plugin to help you do so: https://github.com/Olical/aniseed

Didn't try it myself though.


Edit: I meant writing it in Fennel


Strongly agree. We liked Fennel so much we adopted it for building our mobile apps. We have a SQLite + Lua core that handles business logic and increasingly also view and controller logic with a thin native wrapper for iOS and Android. With over 100k downloads and active users, it’s proven itself stable and practical.

It’s a convenient way to access the immense power of SQLite, with the succinctness of a lisp-like syntax. Throw in tests that execute at 350 tests per second and it’s a fast, stable development platform.

Right now we’re building a wrapper to display graphics sufficient for simple puzzle games so we can generate the layouts and handle interactions also in Fennel (by mapping to native iOS and Android views). It also does hot reloading in the iOS simulator. Very much a WIP though. We haven’t shipped this part yet.

Fun times. Stable. Small. Uncomplicated. Great community.


Would someone explain the function of scripting languages. Why use a scripting language spun off from another language than use the original host language. What does lua offer as a scripting language when offering it as a scripting language?


They offer a higher level abstraction that might make interacting with the underlying APIs a little more egonomic. For example, Python being used a scripting language for AI and other data science things.

The performant APIs that the Python wrappers call are powerful but it might be a little cumbersome to use. A library I used a lot in university was the OpenCV through the native C++ APIs, the Java API and the Python API; the Python version felt a lot easier to work with.


Other people have given good examples about scripting languages in general. But one thing that Lua in particular excels at, is being embedded in other programs. For example, it's a common language for video games to use, both for internal scripting and external modding.

For internal use, you give the game designers a higher-level and easier-to-use language with quick feedback (no recompiling your game just to change some logic). For modders, it lets you sandbox their contributions to a specific API, but let them implement arbitrary logic inside of it. This lets users install mods from untrusted sources with confidence that it's not going to crash their computer, delete their data or install a keylogger.


They enforce invariants automatically for you that you might have to manually deal with in the host language. Examples:

* You'd have to remember to free heap allocations in C. Lua does it for you.

* You might want to always access a particular object's field through a function rather than directly. If you programmed in C it's your responsibility to not mess this up, you won't get any warnings or errors if you forget. A scripting language could automatically call the function when you try to access the field.

* You might want to ensure that any switch case over a enum always considers all possible values of the enum. C wouldn't help you here, but Fennel's infrastructure for pattern matching would.


Ideally all languages would be quick to program in but the reality is that some languages have really long compile times and are unsuitable for quick prototyping, experimenting, creating proof of concepts, or small programs. That's where scripting languages come in handy.


Nothing prevents anyone from building an ABI compatible interpreter where only parts of the program are compiled.


I think the bigger problem is a language design one. Scripting languages are often easier to use than more performant ones because they make a lot of choices for the developer (automatic allocation and garbage collection, doesn't expose reference versus value, no pointers, everything is a table/object/whatever). A runtime can try to make inferences about how language features are being used in order to speed up execution or reduce memory use, but usually even then things end up being less efficient than if the programmer had to actually make choices about memory allocation, if/when to free things, etc.


Nothing save for time, skill and enough financial support. Sure. While we're at it why not make a Web Browser too? Technically, nothing stops us.


Some of the syntax choices seem a little strange to me, but I maybe just don't understand the language.

What's the reason for (/ num1 num2) rather than just num1/num2 for division for example?


`/` is just a function, there aren't operators as something special above functions. This is because it is a Lisp, and even this syntax is just a generalization of Polish notation [0].

[0] https://en.wikipedia.org/wiki/Polish_notation


There is a whole branch of programming languages that use prefix notation like that. It's one of the oldest branches starting in 1958. The benefit of the prefix notation is that it is really easy to write macros and embed Domain Specific Languages (DSLs). It's an interesting part of the programming world. I would recommend checking it out!

https://en.wikipedia.org/wiki/Lisp_(programming_language)


A lot of people have asked the same question over the years, and by now there are some good answers.

For example many lisps implement SRFI 105: Curly Infix https://srfi.schemers.org/srfi-105/ which lets you write {a * b + c / d} instead of (+ (* a b) (/ c d)).

Afaik Fennel doesn't have this, but that's fine because it has macros!

  (macro infix [expr]
    (let [stack []
        ops {
          "+" (fn [a b] (+ a b))
          "-" (fn [a b] (- a b))
          "*" (fn [a b] (* a b))
          "/" (fn [a b] (/ a b))}]
      (fn [item]
        (if (number? item)
          (table.insert stack item)
          (let [op (get ops item)
              b (table.remove stack -1)
              a (table.remove stack -1)]
          (table.insert stack (op a b)))))
      (each [_ v (ipairs expr)]
        (infix v))
      (first stack)))

And now you can do:

    (infix [1 + 2 * 3 - 4 / 5])
    ; => 5.2


I can't quickly parse this in a way that makes 5.2

    1 + (2 * 3) - (4 / 5) = 6.2 (PEMDAS)
    1 + (2 * (3 - (4 / 5))) = 5.4 (greedy)
    (((1+2) * 3) - 4) / 5 = 0.4 (non-greedy)
Was it a typo or is it using some other grouping I don't understand?


Typo, I should have actually run the code! That macro doesn't have any fancy operator precedence rules, but you could certainly add them.


Call me old fashioned, but I always thought syntax like "x = a * b + c" was a leap forward.


To a point, but at a cost.

In C++, there's 17 levels of precedence across 60 operators, with varying associativity.

In many languages, due to precedence, 1 + 2 * 3 = 7, but in others, like Smalltalk, it equals 9. In those languages, its purely left to right, with no precedence. But at a casual glance, they look identical. And for someone bouncing back and forth, they better ensure they have the right hat on when they start writing out equations.

In prefix languages, this is not an issue. Everything looks like a function.

+, -, sqrt, sin, draw.

You also can have niceties like (+ 1 2 3 4), which simply adds up all of the arguments, in contrast to (+ 1 (+ 2 (+ 3 4))). But, that's not different grammatically from (sin (sqrt (abs x))), (f1 (f2 (f3 x))), which appears like sin(sqrt(abs(x))) in infix languages, not much different really at all.

Clearly there are benefits to infix presentation, and for basic (or, even BASIC!) use its very approachable.

But underlying it, there's some definite complexity that you need to grok when the equations start to get complicated.


I am old. And fashioned. "a b * c +" rules! :-)


... because it's a lisp?


What I would love to have is a Lisp which is as easy to embed (and as portable) as Lua but comes with all the niceties of Lisp like a restart-system, REPL-driven development, numeric tower, hygienic and non-hygienic macros, etc.

Unfortunately, no such thing seems to exist.


Chibi scheme has almost all of those things, except non-hygienic macros: https://synthcode.com/scheme/chibi/

It's R7RS scheme, which is a very small, well designed core for lisp. 13k LOCs of C. Very easy to embed, and a nice C FFI. I absolutely love it.

Geiser supports Chibi scheme out of the box, and I wrote this simple nREPL for it that I use: https://github.com/Risto-Stevcev/chibi-repl-server/

I use delimited continuations for conditions and restarts: https://gist.github.com/Risto-Stevcev/e6fd8417e34ef74a74adfa...


Chibi seems to rely on Cygwin/MSYS for Windows which is a pretty heavy dependency (and very non-Lua). https://github.com/ashinn/chibi-scheme/blob/master/README-wi...


I think the posix stuff is only if you're using some of chibi's batteries-included libraries, like filesystem, regexp, shell, tar, zlib. You can probably pretty easily just stub all those files or remove them from the make build and it should build fine. The r7rs core code and libraries don't rely on anything posix related.


I see, thanks! Will take a look at it.


S7 (https://ccrma.stanford.edu/software/snd/snd/s7.html) is made to be embedded. It's very spartan, but has some of those features.


I tried figuring out how to do REPL-driven programming (condition system, restarts, etc.) But couldn't figure out how to. Any references?


I don't think it has absolutely all that, but janet might be close?

https://janet-lang.org/ https://janet.guide/


Janet is nice, but the ecosystem is too small. I’ve actually uninstalled it from my Macs recently because I ended up not using it, whereas I can take any C and Lua library and do something with Fennel.

The only real issue with Fennel is luarocks—the experience of adding new libraries (or general dependency management) to a (Lua) project is still gnarly.


Janet has a way to break into REPL, but AFAIK no way to restart (a la condition system).

Fennel has something a bit more robust (assert-repl) and it can restart, but can only return a single value from the REPL, which often creates more errors downstream.


It's been a couple years but I think janet has the primitives for this in the netrepl module which I think is part of the standard library. I remember being really surprised at how much introspection and control over the env when you responded to a repl connection, just at that time no one had put it together into what you want. I also seem to remember the actual janet repl being a different implementation which always seemed weird to me.


Any reference for this? The docs[1] don't seem to have much of anything.

[1]: https://janet-lang.org/api/spork/netrepl.html


Yeah that standard lib extension has always been extremely underdocumented.

I thought I remembered being able to manipulate the env as a dynamic which I think could get you what you need. But reading the source now I guess not? I'm pretty out of practice reading janet.


it has the opposite of the numeric tower – numbers are floats. (ugh)


There’s Embeddable Common Lisp: https://ecl.common-lisp.dev/

Most recent release was May of this year. I don’t know if it is as portable as Lua, but it should basically run anywhere you can call into C.

You’d have to import a third-party hygienic CL macro system, though. But in return you get a full-fledged Lisp.


I believe ECL uses global state, which makes the interpreter non-reentrant, which in turn makes running multiple scripts extremely difficult (one would need to add a GIL).


Maybe GNU Guile?


You’d think Guile would work. That sounds like a Guile checklist to be honest.

But that said, my random forays into Guile have been met with frustration. I can’t say anything specific, but for whatever reason, whenever I tried to use it, I’d get no momentum on my little project du jour, and I’d shelve it once again, moving on to something else.

Perhaps next time it’ll click.


If I may briefly advertise my Guile examples repository: https://codeberg.org/ZelphirKaltstahl/guile-examples Perhaps it will be useful.


JITed Guile is interesting, but Guile itself seems to live in a weird superposition state—it’s been around forever but there aren’t a lot of projects actually using it, so you’re stuck trying to figure out the best ways to, say, serve HTTP or set headers on requests.


I am a bit biased against GNU libraries. Maybe irrationally so, but I haven't had a good experience with them all the way down from Glibc. They have a tendency to be developer-hostile in very high-handed ways (sort of how GNOME treats its users). I wonder how many people still remember this piece of code:

    __asm__(".symver memcpy,memcpy@GLIBC_2.2.5");
Plus, the LGPL license will ensure that it can't be shipped on a multitude of platforms (iOS I believe, game consoles, etc.)


"""

Hundreds of software projects today contain the mysterious line

    __asm__(".symver memcpy,memcpy@GLIBC_2.2.5");
as a lasting memory of this glibc debacle. I just added it to my code as well.

"""

https://www.win.tue.nl/~aeb/linux/misc/gcc-semibug.html


> Plus, the LGPL license will ensure that it can't be shipped on a multitude of platforms (iOS I believe, game consoles, etc.)

Just for my understanding, is this based on the fact that these platforms don't allow the user to modify or upgrade Guile to another version in the future?


I believe that iOS simply didn't allow any LGPL code on App Store, though I don't remember the exact reasoning.


(This is just from my very quick searching, so please correct me if I'm wrong.)

I don't think there's any restrictions on LGPL code for the App Store, but the restrictions I mentioned earlier are probably tricky to conform to[1].

[1]: https://stackoverflow.com/questions/35068054/does-app-store-...


Yeah, seems like even VLC is there. I remember that there used to be some issue with FSF licenses on the App Store, but it's been almost a decade, so I can't recall the details. I do remember switching out OpenAL-soft due to this issue.


Last time I checked Guile it was somewhat hard to get working on Windows, build tooling was a classic autotools mess compared to Lua consisting of a few ANSI compatible C files.

Also Guile (and many other embeddable scripting languages) want to initialize global state which I'd prefer not to.


Great website




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

Search: