I always say this when it comes up but fennel is very very impressive as a language project. Lua is much beloved but my professional experience is that working with it gets gnarly much faster than most other languages. Fennel focuses on a small set of the worst lua issues and addresses them without getting distracted or bogged down in language design extravagances or lisp nerd semiotics.
The decision to strictly adhere to lua runtime semantics was initially repulsive to me, but I've come to appreciate the incredible wisdom of that approach. The practical experience of using lua is that because of the constraints of the language, every lua runtime is in some ways custom & unique. This decision of fennel's creator allows you to drop fennel onto any lua codebase, of any version, with any amount of custom freak shit grafted in, and just use it. Not only use it, but hook its compiler into the lua module loader system, and freely & transparently mix functions and tables between the two languages. A life raft in decrepit legacy lua codebases.
The restraint and technical focus of this language led to me checking out janet, another project of its creator. I've also come to really like that language, it makes some similarly minor-seeming but brilliant decisions like including PEGs in the core lang instead of regex.
Anyway try fennel, if you have to interact with a lot of lua pattern match alone will improve your life. Try janet too it's cool.
I think Fennel and Janet went separate ways fairly early on, and the creator of Fennel focussed primarily on Janet whilst the current maintainer of Fennel is responsible for much (most?) of what you see in Fennel today...So some very different design choices between the two languages, i believe.
I’ve been thinking about doing this, but then reminding myself that this is not a wise use of my time lol. However, this looks pretty nice. I might have to do it.
Thanks for sharing your setup, I’ll be stealing some ideas.
This nerdsniped me. I've finally taken plunge and started converting my aniseed config to nfnl. It's pretty painful, not even sure if I will keep the changes, but I want to see.
What else is there apart from an editor and a lispy config language?
I always thought of emacs as that, i.e. an editor that is extensible and dev friendly (in contrast to vimscript). I don’t know much about emacs though, so this got me curious.
Well the famous joke is "Emacs is an OS that lacks a decent text editor." (Yes Evil mode exists)
Emacs can do basically anything you write an extension for it to do. It can be your calendar, your email client, your rss reader or even your git gui on top of being an editor. Emacs can do so much that it's daunting to start using.
> Emacs can do basically anything you write an extension for it to do. It can be your calendar, your email client, your rss reader or even your git gui on top of being an editor. Emacs can do so much that it's daunting to start using.
You can build a graphics editor in the browser, but it probably will never reach the level of Photoshop. Emacs can emulate Vim to a fairly good extent. However, I don't think Vim can ever surpass Emacs in terms of power and extensibility. It's simply the way Emacs is built. I love Vim, and I am a die-hard Vimmer. But I have to admit there are things that work in Emacs in a way that no other editor, whether it's Vim, VSCode, or anything else, does better.
The interactivity is one. It's easy to evaluate expressions from within the editor. I am not sure you can programmatically change aspects of nvim on the fly, and even if so, probably not a central component of nvim experience. M-x in Emacs is much more powerful than : in (n)vim.
Now I like lua and think single pass is the way to go for interpreted, since you don't have the disadvantage of a slow compile time no matter how big your codebase gets, BUT its not great to write in. some things are (apparently) not possible, which means the only solution is to transpile into it, which has led to some good languages like moonscript[0], and the dynamic nature is a boon as your codebase grows, which has led to teal[1] which offers static type checking.
I've been wanting to give Fennel a try, currently I use Moonscript for my Lua transpilation
(https://moonscript.org/), but it has some criticisms and it can be a bit confusing sometimes.
I'm a big fan of moonscript, but occasionally wish it was still being improved and worked on. Yuescript¹ looks like it fixes most of my bugbears with moonscript, and is for the most part a faster² drop-in replacement.
There was s little discussion here ~18 months ago³, but it will feel largely circular if you look at it as people are suggesting fennel there ;)
What makes Fennel a bit different is that it is a Lisp inspired by Clojure. If for any reason neither of these ever interested you, then, yes, Fennel would not feel any different to you.
Like I said, if one likes Clojure but has to write for a Lua-enabled platform, Fennel is the best choice today, since there's no Clojure dialect for Lua, like ClojureScript or ClojureDart. Fennel although inspired by Clojure, still a very different language. Phil Hagelberg, AKA @technomancy, is the maintainer, a well-known and highly respected Clojurista.
> Another common criticism of Lua is that it lacks arity checks; that is, if you call a function without enough arguments, it will simply proceed instead of indicating an error. Fennel allows you to write functions that work this way (fn) when it's needed for speed, but it also lets you write functions which check for the arguments they expect using lambda.
I don't understand this; why is this a speed consideration?
I'm not an expert, but my understanding is the Lua has a very limited grammar that is specifically designed to be parsed in a single pass. This precluded Lua from performing arity checks on function calls.
Fennel does support arity checks but it comes with a runtime cost since it's implemented as a separate Lua call after being transpiled from Fennel.
The grammar shouldn't have anything to do with arity checking – at runtime, you're going to know if you passed a function 2 arguments or 3 arguments, and due to the presence of dynamic evaluation, knowing how many arguments a function takes is not statically knowable anyway. My best guess is that they want to avoid the overhead of a runtime check for arity.
local function add_1(x)
return (x + 1)
end
local function add_2(x)
_G.assert((nil ~= x), "Missing argument x on /home/sullyj3/tmp/fn-vs-lambda/fnl/x.fnl:3")
return (x + 2)
end
return add_2
IIRC (and I remember it quite vaguely from some random blog post on the Internet) the Lua implementation uses some trick that greatly speeds up setting up and tearing down activation frames (and has also something to do with the ability to easily cross-call into C back to Lua again?), but it hinges on that argument-passing/result-returning semantics being "use nils for missing stuff, throw away the extras" so e.g. Python can't use it to speed up its implementation of function calls.
Unfortunately, I can't find that blog post so take my words with a grain of salt.
This is mostly based on my experience in the past with Lua circa 2015 when I developed a GUI using Crank Storyboard Engine and needed to write some stuff in C because the Lua equivalent code was too CPU-intensive.
If I remember correctly, Lua is a stack-based VM. What this means is that every piece of data has a corresponding location on a stack data structure in-memory. Function arguments are pushed into the stack as-needed and popped in the function context, and vice-versa for the function return values.
If you wanted arity-checking in this context, you'd have to confirm that you got exactly the right number of elements on the stack, meaning there would be an extra branch in every function call. This might reduce performance if the branch predictor gets it wrong. Plus there would need to be extra instructions to count and to check the count for each pop off the stack in the context of the function call.
This is from the perspective of calling C functions in Lua, so it's probably more complicated for the VM itself when it's running native Lua code.
Declaring a function with `lambda` prepends runtime nil checks for each argument to the function body (excluding optional args-- specified with a question mark, eg. `?x`-- and variable arguments, eg. `...`[1]).
This Fennel:
(fn foo [x y ...]
(print x y ...))
(lambda bar [x ?y ...]
(print x ?y ...))
...transpiles to this Lua:
local function foo(x, y, ...)
return print(x, y, ...)
end
local function bar(x, _3fy, ...)
_G.assert((nil ~= x), "Missing argument x on test.fnl:3")
return print(x, _3fy, ...)
end
[1] Fennel's alternative `& xs` vargs format does create a runtime check in functions declared with lambda, but it always evaluates to true because it simply nil-checks the table (here `xs`) that it dumps Lua `...` vargs into.
My memory of this is hazy, in part because I wasn't a C programmer in those days, but ISTR C used to do this too. I think prototypes were added (and required) only by the time ANSI got involved.
int foo() {
return 0;
}
int main(int argc, char* argv[]) {
return foo("alpha", "beta", 1);
}
$ gcc-13 -Wall -Werror -o foo foo.c
# oh, sads
$ clang -o foo ./foo.c
./foo.c:5:34: warning: too many arguments in call to 'foo'
return foo("alpha", "beta", 1);
~~~ ^
./foo.c:5:15: warning: passing arguments to 'foo' without a prototype is deprecated in all versions of C and is not supported in C2x [-Wdeprecated-non-prototype]
return foo("alpha", "beta", 1);
^
2 warnings generated.
From the blurb you quoted, it sounds like Lua doesn't check function argument arity natively, and fennel can. This means that fennel applies a runtime check to validate if a function was called with an expected arity, and if not, propagates an error (however Lua does that, I do not know), hence why arity checked functions are not as performant.
Caveat: I haven't used Fennel but just inferring from what makes sense...
The Lua VM that they compile to already handles missing arguments with some attendant but unavoidable performance cost.
Fennel's own `lambda` form that they layer on top does check for missing arguments, but it must do so by generating extra Lua code to do those checks. That additional code has a runtime cost.
Using `fn` avoids that extra generated code and its cost.
If Fennel had its own runtime and VM, then the performance story for how missing arguments are handled would be different.
Also interesting is the language the original author of fennel then went on to make (with the knowledge gained by creating fennel):
https://janet-lang.org
Still has a smaller community though and less appeal in the indie game scene
> yet keeps a very small footprint both conceptually
it's only a benefit for writing simplistic things (and configs for some complicated programs like a text editor or a terminal or some MMORPG is already not simple), otherwise you start appreciating that there is not just a single dictionary as a data type etc.
What baffled me when I researched the various Transpiled-to-Lua languages is the fact that most of them insist on dragging their runtimes with them for some strange reason. Why would I want that?
Considering the common role Lua serves, in most cases where I won't be even in a position to install anything besides the single source file I can distribute.
All I want is some quality of life improvements (that includes types). Write in that, then get a single Lua file as output. Nim does that with its JS-backend beautifully.
I forgot if Fennel can do that (seems that's what it means by Ahead-Of-Time compilation), but it's not typed so I passed on it. I might reconsider, as I gave up on the search last time.
So I don't think I have a strong stance on the merits or drawbacks of whitespace-significant languages versus those that use parentheses (or any other syntax choices). But, I'm curious if there are any compelling arguments that objectively demonstrate why one approach might be superior to the other or if there are at least some persuasive reasons to favor one over the other. Genuinely curious!
I think (and am not alone) that indentation based languages are easier for beginners to learn.
The fact that indentation (whether significant to syntax or not)helps readability is something I hope I need not demonstrate since indenting is near universal in languages that use explicit delimiters.
So beginners learning a language with delimiters need to learn both delimiters and indentation, and then learn that while the latter helps them more, it's actually just the former that is required.
For beginners, mental load is death by a thousand cuts, so it may make more of a difference then an experienced programmer thinks.
[Edit]
My first reaction was just the benefits for beginners of indentation e syntax. I personally have a subjective feeling of advantages for non beginners to delimiter based syntax (I use common lisp regularly), but I have fairly low confidence in that, while I'm pretty sure of the advantages to beginners for indentation.
Depends on the person. I knew some newbie programmers who loved Clojure and were hum-drum with Python.
Some folks like parenthesis delimiters and functional programming. It structures and separates things out clearly in their mind. And when you introduce them to editing tricks like parinfer - they fall in love. The guy whom I introduced Clojure to knows it far better than me now.
That does make some sense. It's so hard to go back to the beginner's mind (though undoubtedly valuable to try!), but I think you're right that this has to be easier for a beginner. There's just less syntax overall if there aren't parentheses or curly braces.
In there it says Lua uses 'for' for looping over integer ranges and object ranges. They didn't like that so they broke it up so that for is for integer ranges and each is for object ranges.
That seems strange to me. A range is a range so why have different ways to iterate over ranges based on what type of range it is?
I mean it is lua that has two different ways to loop, with different syntax and semantics, but calls them both "for." "Numeric for" and "generic for" are the official names, with separate pages in the docs. Having different names for them seems like a reasonable and not that surprising choice.
>This feature is intentionally listed last because while lisp programmers have historically made a big deal about how powerful it is, it is relatively rare to encounter situations where such a powerful construct is justified.
> Finally Fennel includes a macro system so that you can easily extend the language to include new syntactic forms. This feature is intentionally listed last because while lisp programmers have historically made a big deal about how powerful it is, it is relatively rare to encounter situations where such a powerful construct is justified.
In other words, there are serious drawbacks to using macros that are usually outweigh any benefits they might give you.
this is a very vague statement. lua is used in a lot of environments and is very performant. you can use it inside nginx (openresty) to process requests at a pretty large scale. you can use it inside of redis.
lua and javascript are somewhat interchangeable aside from the fact that js has a much larger ecosystem.
sure there are situations where lua might be a bad choice but based on your remarks you make it sound like it sucks anywhere except inside a game engine.
The decision to strictly adhere to lua runtime semantics was initially repulsive to me, but I've come to appreciate the incredible wisdom of that approach. The practical experience of using lua is that because of the constraints of the language, every lua runtime is in some ways custom & unique. This decision of fennel's creator allows you to drop fennel onto any lua codebase, of any version, with any amount of custom freak shit grafted in, and just use it. Not only use it, but hook its compiler into the lua module loader system, and freely & transparently mix functions and tables between the two languages. A life raft in decrepit legacy lua codebases.
The restraint and technical focus of this language led to me checking out janet, another project of its creator. I've also come to really like that language, it makes some similarly minor-seeming but brilliant decisions like including PEGs in the core lang instead of regex.
Anyway try fennel, if you have to interact with a lot of lua pattern match alone will improve your life. Try janet too it's cool.