1. 54
  1. 20

    Hey, Fennel lead dev here, ask me anything. We also have a fairly active chat on Libera/Matrix, #fennel / #fennel:matrix.org if you want to discuss there.

    1. 8

      Hi Phil, I just learned about Fennel’s shared heritage with Janet. I’m curious how you think about the pros and cons between the two.

      1. 11

        I haven’t used Janet myself, but from what I can tell they are very similar from a language perspective. Fennel is completely written in Fennel while Janet is mostly written in C. But the main difference is the runtime.

        Fennel is solely a compiler, so we use the pre-existing Lua runtime which gives us access to existing platforms like love2d, TIC-80, Minetest, Neovim, OpenResty, etc. Since we don’t have to develop a VM, we can focus 100% of our efforts on making the compiler as powerful and as polished as possible. We have access to a world-class JIT compiler while Janet only has a bytecode engine. Lua released a generational garbage collector a couple years ago that we got for free without having to do anything.

        On the other hand, having control of the runtime has other benefits. For one, it can be a lot of fun to hack on. You also don’t have to worry about source mapping. (We have some solutions in Fennel but they have some limitations.) The other main difference is that Janet includes immutable data structures out of the box, while with Fennel they require some hacks that have a significant performance penalty.

        Maybe someone who’s used Janet can offer another perspective.

      2. 3

        As someone who had never worked with a lisp, do you recommend fennel or should I look elsewhere First?

        1. 6

          I’d say this really depends on your learning style and what you’re hoping to accomplish. Some people like to learn from books, and they’d be much better off reading SICP or How to Design Programs.

          IMO Lua and Scheme are roughly equivalent in difficulty for learners. The difference is that Scheme on its own is hard to accomplish much in standalone; typically real programs target dramatically larger systems like Racket and Guile. I would also say that Scheme’s hygenic macros are notoriously difficult to understand, but Fennel’s macros (while being more difficult than other parts of the language) are comparatively straightforward: https://fennel-lang.org/macros

          Meanwhile Lua has already been embedded in hundreds of widely-used programs, and loading Fennel into them tends to be trivial. (see https://p.hagelb.org/hello-powdertoy.fnl.html) If you’re interested in learning by creating a game, (which I highly recommend!) Fennel has a huge advantage; it’s easy to use with TIC-80 and Love2d; in fact, most Fennel programs are games.

          It’s true that learning Fennel does require learning Lua and Fennel at the same time to some degree, but Fennel mostly maps 1:1 to Lua in a way that’s really easy (IMO) to internalize. This sounds trickier than it actually is. If you’re ever reading the docs for a Lua library and wonder what it would look like in Fennel; it’s easy to drop it in https://fennel-lang.org/see to understand what a Fennel version of that code would look like. Compared to learning Java and Clojure at the same time, or Erlang and Elixir, it’s much easier.

      3. 13

        I was recommended Fennel here for the Autumn Lisp Game Jam and I don’t use it everywhere (Rust is still my default), but I have been thoroughly pleased with it.

        It solves a lot of papercuts from Lua that bother me:

        • Variables are local and immutable by default
        • Packaging is a little simpler
        • Different syntax for lists and dicts, even if they’re the same type under the hood
        • It does NOT fix Lua’s use of 1-based indexing instead of 0-based for lists, but I’ve tried to fix that myself and it is in fact a lot harder than it looks

        It’s also a quite respectable Lisp:

        • I find the Clojure-y syntax quite pleasant, even though it is definitely not Clojure
        • It uses Lua’s metaprogramming in ways that feel very Lisp-y
        • Its REPL and introspection are very nice
        • It has the whole Lua package ecosystem available
        • It is easily portable to just about any platform, and runs on just about any version of Lua 5
        1. 6

          Fennel being loadable from Lua, and Lua being written in ANSI C makes an interesting combination for anyone wanting to write bootstrappable software in a functional style.

          1. 4

            Aren’t many LISPs written in C?

            1. 9

              Indeed but Lua is insanely portable. They have taken great pains to depend only on what is guaranteed by C89: there are no endian dependencies, no assumptions about struct layout, etc.

              1. 5

                Yes but it’s unusual to have no dependencies beyond a C89 compiler and libc.

                1. 2

                  I’m pretty sure Janst can be built with just a C compiler. And several of the numerous dialects of Scheme.

                2. 3

                  Some, but most are written in Lisp, maybe or maybe not with a C core for runtime. Bootstrapping is occasionally a bit of a headache.

                  1. 3

                    Most LISPs are not what I’d consider Functional. They’d support it, sure, and functions are first class, but they don’t emphasize immutability and bottom-up-creation and side-effect limiting like what I think of with functional languages.

                    1. 4

                      Absolutely true. I’ve never understood why LISP gets associated with functional. The syntax seems like it would lead that direction, but in practise most LISPs are very low level.

                      1. 2

                        Back when lisps were new, the fact that it was even possible to do functional programming at all was considered unique and novel. Having access to lambda was considered a very advanced feature. Nowadays if your language doesn’t even have closures, no one will really take it seriously, so whatever association lisp-in-general has with FP should probably be thought of as historical.

                        1. 3

                          When lisps were new, they frequently lacked lexical closures. See the ‘upwards funarg problem’.

                          1. 1

                            I may be a heretic here but I actually think lexical closures are bad, and a poor substitute for currying/partial application.

                            1. 4

                              You’ll hate Fennel then! Probably shouldn’t use it. Closures are more central to Fennel than any other language I know.

                              1. 1

                                More than other lisps?

                                1. 2

                                  I haven’t used every lisp, but …

                                  • much more than: Emacs Lisp, Common Lisp, Hy, LFE
                                  • a fair bit more than: Clojure
                                  • a little more than: Racket, Scheme (mostly due to the module system which is mostly closures)

                                  Fennel has partial, but not currying, since it’s on a runtime where argument count for a function isn’t determinable.

                              2. 1

                                What is the difference between lexical closures and currying/partial application?

                                1. 2

                                  With a lexical closure every term that is closed over now has to live as long as the execution of that closure, with no explicit annotation of anything about the sharing.

                                  When a human reads the program they have to remember if this is a by-value or by-name binding.

                                  If you want a type system that incorporates lifespans or ownership, then again you need some default rule to describe the relationship between outer and inner code, which the programmer has to remember and cannot alter.

                                  By contrast, if all such sharing happens via a function call (which is to say with explicit parameter passing and type annotation) then the writing programmer can make explicit what the sharing regime is, and the reader can apply the same rules that apply when they read any other function call.

                                  Obviously, you can do absolutely amazing things with lexical closures, but you can also do all those things with non-closing functions and partial application.

                                  I guess I’m saying that explicit is better than implicit.

                                  1. 2

                                    When a human reads the program they have to remember if this is a by-value or by-name binding.

                                    This is less an argument against closures and more an argument for making your language have consistent and sensible argument passing semantics. It’s not a problem in Fennel because the distinction between pass-by-value and pass-by-name is irrelevant.

                                    If you want a type system that incorporates lifespans or ownership, then again you need some default rule to describe the relationship between outer and inner code, which the programmer has to remember and cannot alter.

                                    Again, there are plenty of contexts where this is true, but none of this is the slightest bit relevant here.

                                    1. 2

                                      Thanks for the response, but this didn’t address the question I had.

                          2. 1

                            Yeah my knowledge is somewhat stale but at least back in the day only the very core functions were in C and the majority of the image was LISP.

                            There was also a lot on top of that core that use a foreign function interface to layer in functions from C-land like UI libraries and the like.

                          3. 3

                            Fennel & Lua seem like a great duo to be aware of. I’ve also recently learned about Fabrice Bellard’s QuickJS which is interesting for similar reasons as a simple & embeddable pure C implementation of ES2020.

                          4. 2

                            I’m always curious why projects choose to create an entirely new Lisp dialect instead of a new implementation of an existing Lisp (like Common Lisp or Scheme).

                            Personally, I’d rather use a standardized language and be able to choose the best implementation for my needs. Especially in the Lisp world, where syntax isn’t a big deal because of macros - if I like something Fennel is doing, I can write macros to do it in Common Lisp. And there are a lot of existing Scheme and CL libraries, and a whole bunch of books and other documentation.

                            I can see why it’s practical for Lua users, but for people not already tied to that ecosystem, what’s the advantage over Common Lisp?

                            1. 6

                              I’m always curious why projects choose to create an entirely new Lisp dialect

                              There’s a page for this on the Fennel web site if you’re curious: https://fennel-lang.org/rationale

                              if I like something Fennel is doing, I can write macros to do it in Common Lisp.

                              This isn’t true in a meaningful way; a lot of the advantage of what Fennel provides is what it omits; by removing the ability to do certain things you end up with more predictable, obvious code and powerful guarantees. Simplicity cannot be added to an existing language by piling on macros.

                              I can see why it’s practical for Lua users, but for people not already tied to that ecosystem, what’s the advantage over Common Lisp?

                              For the past few years, there’s been a lisp game jam in the spring and autumn. Before Fennel came around it was mostly Schemers and CL programmers, but since Fennel has been introduced, most of the events have been won by Fennel games, and every time Fennel makes up the majority of the top 5 entries. https://itch.io/jam/spring-lisp-game-jam-2021/results

                              This is 100% due to the ecosystem; the CL programmers end up spending a lot of time building things from scratch which are readily available to Fennel programs. When it’s time to go rate the games, the Fennel versions are always easy to download without any manual hunting down of dependencies, (because these problems have already been solved by the developers of love2d and TIC-80) but it’s like 50/50 whether I can even get the CL ones to run at all on my machine.

                              You don’t have to be already tied to that ecosystem to see the advantage there.

                              1. 1

                                lot of the advantage of what Fennel provides is what it omits; by removing the ability to do certain things you end up with more predictable, obvious code and powerful guarantees

                                It is possible to embed complete languages with arbitrary semantics; see e.g. coalton.

                              2. 1

                                I’m p sure the answer is that it’s easy to get started implementing a lisp, and for a certain sort of person, fun.