== Fennel (fn name args optional-docstring body) ; define a function, arity is not checked (fn print-and-add [a b c] (print a) (+ b c)) (lambda name args body) ; define a function with arity-check (let args body) ; scoped variable assignment (local name value) ; file-scoped constant assignment (var name value) ; scoped variable assignment (set name value) ; variable update table fields can be changed independent of the let/var call // integer division lshift, rshift, bor, band, bnot, xor ; bitwise operations 100_000 ; "_" as group separator Strings are essentially immutable byte arrays .. string concatenation {"key" value "number" 531 "f" (fn [x] (+ x 2))} (. tablevar tablekey) ; table lookup (tset tablevar tablekey value) ; set a table value (length tablevar) ; length of strings and *sequential* tables ["abc" "def" "xyz"] ; equivalent to {1 "abc" 2 "def" 3 "xyz"} (table.insert tablevar tablekey) ; Appends "d" (icollect [key val iterator] body) ; iterate the iterator, bind key & val, run the body and if it returns non-nil, append it to the resulting sequential table (collect [key val iterator] body) ; iterate the iterator, bind key & val, run the body and consider the result as key-value pair for the resulting table accumulate each ipairs pairs (each [key value (pairs {"key1" 52 "key2" 99})] (print key value)) (each [item iterator] (do-something with the item)) (while cond body) (for [i 1 10] body) ; iterate from 1 to 10 (for [i 1 10 2] body) ; iterate from 1 to 10 with step size 2 (if (= 0 (% x 2)) "even" (= 0 (% x 9)) "multiple of nine" "I dunno, something else")) (when ...) ; like "if" but not else-branch (useful for side effects) :name ; equals "name" tbl.name ; equals (. tbl name) (let [one 1 two 2 tbl {: one : two}] tbl) ; equals {:one 1 :two 2} ; positional destructuring (let [data [1 2 3] [fst snd thrd] data] (print fst snd thrd)) ; -> 1 2 3 ; key-based destructuring for tables (let [pos {:x 23 :y 42} {:x x-pos :y y-pos} pos] (print x-pos y-pos)) ; -> 23 42 (let [pos {:x 23 :y 42} {: x : y} pos] (print x y)) ; -> 23 42 ; values allows to return multiple values are return value (values nil (.. "Invalid filename: " filename)) ; error handling with exceptions (let [(ok? val-or-msg) (pcall potentially-disastrous-call filename)] (if ok? (print "Got value" val-or-msg) (print "Could not get value:" val-or-msg))) ; variadic function (fn print-each [...] (each [i v (ipairs [...])] (print (.. "Argument " i " is " v)))) (print-each :a :b :c) ; The ... form is not a list or first class value, it expands to multiple values inline. ; [...] would be a regular table ; select function can be used as well: (select "#" ...) ; it is specific for each function; so an inner function will overwrite the outer function's "..." "unknown global in strict mode" == you're trying compile code that uses a global which the Fennel compiler doesn't know about usually programming error but can be a misconfiguration in case of a bug, you can write "_G.foo" instead of "foo" `fennel --globals x,y,z yourfennelscript.fnl` disable strict global checking for the provided variable names (x, y, and z) `fennel --metadata script.fnl` fennel.doc and fennel.metadata contain metadata like docstrings All forms that accept a docstring will also accept a metadata table in the same place: (fn add [...] {:deprecated "v1.9.0" :fnl/docstring "Add arbitrary amount of numbers." :fnl/arglist [a b & more]} body) ; then we can do the following >> (local {: metadata} (require :fennel)) >> (metadata:get foo :deprecated) "v1.9.0" >> (metadata:get foo2 :added) "v2.0.0" common surprises for LISP-only or Lua-only people: * The arithmetic, comparison, and boolean operators are not first-class functions. They can behave in surprising ways with multiple-return-valued functions, because the number of arguments to them must be known at compile-time. * There is no apply function; instead use table.unpack or unpack depending on your Lua version: (f 1 3 (table.unpack [4 9])) does what (apply f 1 3 [4 9]) * Tables are compared for equality by identity, not based on the value of their contents * fennel.view prints the table content * Lua programmers should note Fennel functions cannot do early returns ; example to use other modules (let [lume (require :lume) tbl [52 99 412 654] plus (fn [x y] (+ x y))] (lume.map tbl (partial plus 2))) ; -> [54 101 414 656] ; The last value in a Fennel file will be used as the value of the whole module ; "lib/ui/menu.lua" would be read when loading the module lib.ui.menu ; load the Fennel module so you can require Lua *and* fnl files require("fennel").install() local mylib = require("mylib") ; the following loading pattern can come in handy (local a (require :example.module-a)) ; the following is a way to require "libs/example/utils/greet.fnl" in "libs/example/module-a.fnl" (local greet (require (.. (: ... :match "(.+)%.[^.]+") :.utils.greet))) LuaRocks uses the LUA_PATH env variable to find libraries (local a (require (.. ... :.module-a))) ; in libs/example/init.fnl "..." is set to "libs.example" "libs.example" is concatenated with ".module-a", hence "libs.example.module-a" #foo expands to (hashfn foo) `foo expands to (quote foo) ,foo expands to (unquote foo) TODO stdlib, e.g. threading macros https://fennel-lang.org/reference TODO study macros https://fennel-lang.org/macros#how-to-manipulate-data https://p.hagelb.org/equal-rights-for-functional-objects.html