fennel-reference(5) Fennel Reference fennel-reference(5) (NAME) fennel-reference - (DESCRIPTION) (Fennel) . (Lua) Lua Lua . . Lua Fennel antifennel . Fennel Fennel . ( special) Lua . . Fennel Lua . Fennel Lua . . fennel.view Fennel . Lua . Fennel UTF-8 . (SYNTAX) (): . . : (print "hello world") {}: / ( ) . : {:a 1 :b 2} : . {: this} ; is shorthand for {:this this} []: () ( let) . : [1 2 3] Lua (underscore) . ASCII . .inf -.inf . NaN NaN .nan -.nan . " . . Lua . . Fennel Lua . (symbols) . (:) . : o : ' o (tilde): ~ o : ; o : @ (-) . . & . &as (destructuring). . : " " (multi symbols) . . x.y:z a:b.c . . Fennel :fennel-lang.org "fennel-lang.org" . form feed carriage return (whitespace) . ASCII . : o #foo (hashfn foo) o `foo (quote foo) o ,foo (unquote foo) . (FUNCTIONS) fn . nil . : (fn pxy [x y] (print (+ x y))) . (recursion) : (fn pxy [x y] (print (+ x y))) (local pxy (fn [x y] (print (+ x y)))) : (local functions {}) (fn functions.p [x y z] (print (* x (+ y z)))) ;; equivalent to: (set functions.p (fn [x y z] (print (* x (+ y z))))) Fennel Lua (tail-call optimization) ( ) (tail position) . . nil lambda/ fn nil ? . : (lambda [x ?y z] (print (- x (* (or ?y 1) z)))) Lua nil nil . fn . lambda . lambda case case-try match match-try ?foo nil . lambda . (Docstrings and metadata) fn lambda macro docstring . (fn pxy [x y] "Print the sum of x and y" (print (+ x y))) ( pxyz [x ?y z] "Print the sum of x, y, and z. If y is not provided, defaults to 0." (print (+ x (or ?y 0) z))) REPL (---metadata) {useMetadata=true} doc . ;; this only works in the repl >> ,doc pxy (pxy x y) Print the sum of x and y API fennel.doc fennel.metadata . ( 1.1.0) docstring : (fn add [...] {:fnl/docstring "Add arbitrary amount of numbers." :fnl/arglist [a b & more]} (case (values (select :# ...) ...) (0) 0 (1 a) a (2 a b) (+ a b) (_ a b) (add (+ a b) (select 3 ...)))) ( ). ,doc REPL : >> ,doc add (add a b & more) Add arbitrary amount of numbers. ( 1.3.0) : (fn foo [] {:deprecated "v1.9.0" :fnl/docstring "*DEPRECATED* use foo2"} ;; old way to do stuff ) (fn foo2 [x] {:added "v2.0.0" :fnl/docstring "Incompatible but better version of foo!"} ;; do stuff better, now with x! x) deprecated added . API Fennel : >> (local {: metadata} (require :fennel)) >> (metadata:get foo :deprecated) "v1.9.0" >> (metadata:get foo2 :added) "v2.0.0" (side effects) . Fennel . ( 1.3.1) docstring : (fn some-function [x ...] "Docstring for some-function." {:fnl/arglist [x & xs] :other :metadata} (let [xs [...]] ;; ... )) . metadata:get key : >> (local {: metadata} (require :fennel)) >> (metadata:get some-function) {:fnl/arglist ["x" "&" "xs"] :fnl/docstring "Docstring for some-function." :other "metadata"} Fennel fnl/docstring fnl/arglist ..SS " (Hash function literal shorthand)" Fennel . . : (fn [a b] (+ a b)) (hashfn (+ $1 $2)) ; implementation detail; don't use directly #(+ $1 $2) . . $1 $9 (varargs) $... ... . $ $1 . hashfn # . #$3 ; same as (fn [x y z] z) #[$1 $2 $3] ; same as (fn [a b c] [a b c]) #{:a $1 :b $2} ; same as (fn [a b] {:a a :b b}) #$ ; same as (fn [x] x) (aka the identity function) #val ; same as (fn [] val) #[:one :two $...] ; same as (fn [...] ["one" "two" ...]) (multisyms) . #$.foo "foo" . do do . . partial (partial application) . (currying) "" . : (fn add-print [x y] (print (+ x y))) (partial add-print 2) . (BINDING) let (scoped locals) (scope) . : (let [x 89 y 198] (print (+ x y 12))) ; => 299 set let local (shadow) . let . . (destructure) : : (let [[a b c] [1 2 3]] (+ a b c)) ; => 6 ( 1.5.0): a 1 . : . . : (let [{:msg message : val} {:msg "hello there" :val 19}] (print message) val) ; prints "hello there" and returns 19 & : : (let [[a b & c] [1 2 3 4 5 6]] (table.concat c ",")) ; => "3,4,5,6" ( 1.3.0): ... . &as : : (let [{:a a :b b &as all} {:a 1 :b 2 :c 3 :d 4}] (+ a b all.c all.d)) ; => 10 . Fennel 1.5.0 . : (let [(x y z) (table.unpack [10 9 8])] (+ x y z)) ; => 27 local (declare local) . let . . : (local tau-approx 6.28318) . case (pattern matching) ( 1.3.0) / . . : Lua "" . : (case mytable 59 :will-never-match-hopefully [9 q 5] (print :q q) [1 a b] (+ a b)) mytable / . mytable 59 . mytable 9 nil 5 (print :q q) q mytable . mytable 1 nil . nil . . _ ? nil . . : (case mytable ;; the first and second values of mytable are the same non-nil value [a a] (* a 2) ;; the first and second values are not nil and are not the same value [a b] (+ a b)) ! [a a] [a b] . [a b] nil - ! ? nil . : (case mytable ;; not-nil, maybe-nil [a ?b] :maybe-one-maybe-two-values ;; maybe-nil == maybe-nil, both are nil or both are the same value [?a ?a] :maybe-none-maybe-two-same-values ;; maybe-nil, maybe-nil [?a ?b] :maybe-none-maybe-one-maybe-two-values) _ "" - nil - . _ case "else" nil . : (case mytable ;; not-nil, anything [a _b] :maybe-one-maybe-two-values ;; anything, anything (different to the previous ?a example!) ;; note this is effectively the same as [] [_a _a] :maybe-none-maybe-one-maybe-two-values ;; anything, anything ;; this is identical to [_a _a] and in this example would never actually match. [_a _b] :maybe-none-maybe-one-maybe-two-values ;; when no other clause matched, in this case any non-table value _ :no-match) ( []) / ( {}) . . ( nil ? _ .) - [] . let & . (case mytable {:subtable [a b ?c] :depth depth} (* b depth) _ :unknown) . ( .) . (case (io.open "/some/file") (nil msg) (report-error msg) f (read-file f)) (Guard Clauses) . : (case [91 12 53] (where [a b c] (= 5 a)) :will-not-match (where [a b c] (= 0 (math.fmod (+ a b c) 2)) (= 91 a)) c) ; -> 53 where . . or where : (case [5 1 2] (where (or [a 3 9] [a 1 2]) (= 5 a)) "Either [5 3 9] or [5 1 2]" _ "anything else") : (case [5 1 2] (where [a 3 9] (= 5 a)) "Either [5 3 9] or [5 1 2]" (where [a 1 2] (= 5 a)) "Either [5 3 9] or [5 1 2]" _ "anything else") or : ;; bad (case [1 2 3] ;; Will fail to compile because `b' is not present in the first ;; pattern but the guard still uses it. (where (or [a 1 2] [a b 3]) (< a 0) (< b 1)) :body) ;; ok (case [1 2 3] (where (or [a b 2] [a b 3]) (< a 0) (<= b 1)) :body) (Binding Pinning) case . : (let [x 1] (case [:hello] ;; `x` is simply bound to the first value of [:hello] [x] x)) ; -> :hello . (= binding-name) "" . (= binding-name) case (where) . :.IP (let [x 1] (case [:hello] ;; 1 != :hello (where [(= x)]) x _ :no-match)) ; -> no-match (let [x 1] (case [1] ;; 1 == 1 (where [(= x)]) x _ :no-match)) ; -> 1 (let [pass :hunter2] (case (user-input) (where (= pass)) :login _ :try-again!)) . . : case if-let (Clojure) . Fennel if-let case . match (match pattern matching) match case . case ((= binding-name)) . match ! case . (let [x 95] (match [52 85 95] [b a a] :no ; because a=85 and a=95 [x y z] :no ; because x=95 and x=52 [a b x] :yes)) ; a and b are fresh values while x=95 and x=95 case nil ? - ! : (let [name nil get-input (fn [] "Dave")] (match (get-input) ;; name already exists as nil, "Dave" != nil so this *wont* match name (.. "Hello " name) ?no-input (.. "Hello anonymous"))) ; -> "Hello anonymous" : 0.9.0 Fennel match ? . where : (match [1 2 3] (where [a 2 3] (< 0 a)) "new guard syntax" ([a 2 3] ? (< 0 a)) "obsolete guard syntax") case-try (matching multiple steps) . . . (catch pat1 body1 pat2 body2 ...) case . catch nil . catch . (fn handle [conn token] (case-try (conn:receive :*l) input (parse input) (where (command-name params (= token))) (commands.get command-name) command (pcall command (table.unpack params)) (catch (_ :timeout) nil (_ :closed) (pcall disconnect conn "connection closed") (_ msg) (print "Error handling input" msg)))) . catch . Fennel Lua : assert/error nil . pcall error . match-try (matching multiple steps) case-try match . case match . case-try match-try catch . (fn handle [conn token] (match-try (conn:receive :*l) input (parse input) (command-name params token) (commands.get command-name) command (pcall command (table.unpack params)) (catch (_ :timeout) nil (_ :closed) (pcall disconnect conn "connection closed") (_ msg) (print "Error handling input" msg)))) var (declare local variable) . set local . : (var x 83) . set var . (globals) let/local . let local . tbl.field (. tbl field) . : (set x (+ x 91)) ; var (let [t {:a 4 :b 8}] ; static table field (set t.a 2) t) ; => {:a 2 :b 8} (let [t {:supported-chars {:x true}} field1 :supported-chars field2 :y] ; dynamic table field (set (. t field1 field2) true) t) ; => {:supported-chars {:x true :y true}} . tset . : (let [tbl {:d 32} field :d] (tset tbl field 19) tbl) ; => {:d 19} . : (let [tbl {:a {:b {}}} field :c] (tset tbl :a :b field "d") tbl) ; => {:a {:b {:c "d"}}} 1.5.0 tset set . tset doto set . with-open Lua (garbage collection) GC with-open . let : o ( ) o :close . with-open (value:close) . xpcall . --to-be-closed Lua 5.4+ xpcall (stack trace) . : ;; Basic usage (with-open [fout (io.open :output.txt :w) fin (io.open :input.txt)] (fout:write "Here is some text!\n") ((fin:lines))) ; => first line of input.txt ;; This demonstrates that the file will also be closed upon error. (var fh nil) (local [ok err] [(pcall #(with-open [file (io.open :test.txt :w)] (set fh file) ; you would normally never do this (error :whoops!)))]) (io.type fh) ; => "closed file" [ok err] ; => [false ""] n pick-values (...) n . (variadic) . let n : (pick-values 2 (func)) : (let [[_0_ _1_] [(func)]] (values _0_ _1_)) : (pick-values 0 :a :b :c :d :e) ; => nil [(pick-values 2 (table.unpack [:a :b :c]))] ;-> ["a" "b"] (fn add [x y ...] (let [sum (+ (or x 0) (or y 0))] (if (= ... nil) sum (add sum ...)))) (add (pick-values 2 10 10 10 10)) ; => 20 (->> [1 2 3 4 5] (table.unpack) (pick-values 3) (add)) ; => 6 : n n . (select "#" ...) varargs [...] nil : (select :# (pick-values 5 "one" "two")) ; => 5 [(pick-values 5 "one" "two")] ; => ["one" "two"] (FLOW CONTROL) if (conditional) . / "else" . cond . : (let [x (math.random 64)] (if (= 0 (% x 10)) "multiple of ten" (= 0 (% x 2)) "even" "I dunno, something else")) nil false true . when nil false . . . : (when launch-missiles? (power-on) (open-doors) (fire)) each (general iteration) (iterator) . ipairs ( ) pairs ( ) . nil . : (each [key value (pairs mytbl)] (print "executing key") (print (f value))) &until : (local out []) (each [_ value (pairs tbl) &until (< max-len (length out))] (table.insert out value)) : 1.2.0 fennel :until &until . each . Lua . for (numeric loop) ( ) . (step) . nil . :.IP (for [i 1 10 2] (log-number i) (print i)) . each for &until . . (var x 0) (for [i 1 128 &until (maxed-out? x)] (set x (+ x i))) while (while loop) . while Lua . nil . : (var done? false) (while (not done?) (print :not-done) (when (< 0.95 (math.random)) (set done? true))) do . if when . begin progn . (if launch-missiles? (do (power-on) (open-doors) (fire)) false-alarm? (promote lt-petrov)) fn let do . (DATA) (operators) o and or not: (boolean) o + - * / // % ^: (arithmetic) o > < >= <= = not=: (comparison) o lshift rshift band bor bxor bnot: (bitwise operations) . Lua 5.3+ --use-bit-lib useBitLib LuaJIT . (//) Lua 5.3+ . . (= 2 2 (table.unpack [2 5])) true . . . .. (string concatenation) . (coerce) . : (.. "Hello" " " "world" 7 "!!!") ; => "Hello world7!!!" . length . (nil) "" nil nil . nil ipairs nil Lua <= 5.2 table.maxn . : (+ (length [1 2 3 nil 8]) (length "abc")) ; => 6 or 8 . (table lookup) . . : (. mytbl myfield) : (let [t {:a [2 3 4]}] (. t :a 2)) ; => 3 mytbl.field . nil ?. . . nil . : (?. mytbl myfield) : (let [t {:a [2 3 4]}] (?. t :a 4 :b)) ; => nil (let [t {:a [2 3 4 {:b 42}]}] (?. t :a 4 :b)) ; => 42 icollect collect (table comprehension) icollect " " each . map . nil . filter . (icollect [_ v (ipairs [1 2 3 4 5 6])] (if (< 2 v) (* v v))) ;; -> [9 16 25 36] ;; equivalent to: (let [tbl []] (each [_ v (ipairs [1 2 3 4 5 6])] (tset tbl (+ (length tbl) 1) (if (< 2 v) (* v v)))) tbl) collect : . (collect [k v (pairs {:apple "red" :orange "orange" :lemon "yellow"})] (if (not= v "yellow") (values (.. "color-" v) k))) ;; -> {:color-orange "orange" :color-red "apple"} ;; equivalent to: (let [tbl {}] (each [k v (pairs {:apple "red" :orange "orange"})] (if (not= v "yellow") (match [(.. "color-" v) k] [key value] (tset tbl key value)))) tbl) collect values : (collect [k v (pairs {:a 85 :b 52 :c 621 :d 44})] k (* v 5)) each for &until . icollect collect &into : (icollect [_ x (ipairs [2 3]) &into [9]] (* x 11)) ;; -> [9 22 33] : 1.2.0 fennel :into &into . accumulate (iterator accumulation) fold reduce . collect icollect . accumulate "" (accumulator) . . accumulate . : (accumulate [sum 0 i n (ipairs [10 20 30 40])] (+ sum n)) ; -> 100 &until . faccumulate (range accumulation) ( 1.3.0) accumulate each for . for accumulate &until . : (faccumulate [n 0 i 1 5] (+ n i)) ; => 15 fcollect (range comprehension macro) ( 1.1.1) icollect fcollect . icollect for . &into &until icollect . : (fcollect [i 0 10 2] (if (> i 2) (* i i))) ;; -> [16 36 64 100] ;; equivalent to: (let [tbl {}] (for [i 0 10 2] (if (> i 2) (table.insert tbl (* i i)))) tbl) values (multi-valued return) . nil . : (fn [filename] (if (valid-file-name? filename) (open-file filename) (values nil (.. "Invalid filename: " filename)))) (OTHER) : (method call) . API Lua . Lua : . : (let [f (assert (io.open "hello" "w"))] (f:write "world") (f:close)) f:write . : : : (let [f (assert (io.open "hello" "w")) method1 :write method2 :close] (: f method1 "world") (: f method2)) : (let [f (assert (io.open "hello" "w"))] (f.write f "world") (f.close f)) Lua self . (local t {}) (fn t.enable [self] (set self.enabled? true)) (t:enable) -> ->> -?> -?>> (threading macros) -> . . : (-> 52 (+ 91 2) ; (+ 52 91 2) (- 8) ; (- (+ 52 91 2) 8) (print "is the answer")) ; (print (- (+ 52 91 2) 8) "is the answer") ->> . -?> -?>> ( thread maybe) -> ->> . false nil . -?> -> -?>> ->> . nil :.IP (-?> {:a {:b {:c 42}}} (. :a) (. :missing) (. :c)) ; -> nil (-?>> :a (. {:a :b}) (. {:b :missing}) (. {:c 42})) ; -> nil -> ->> -?> -?>> . "" (threads) . |> OCaml Elixir . doto doto . . (doto (io.open "/tmp/err.log") (: :write contents) (: :close)) ;; equivalent to: (let [x (io.open "/tmp/err.log")] (: x :write contents) (: x :close) x) . tail! . tail! (tail position) . . (fn process-all [data i] (case (process (. data i)) :done (print "Process completed.") :next (process-all data (+ i 1)) :skip (do (tail! (process-all data (+ i 2))) ;; ^^^^^ Compile error: Must be in tail position (print "Skipped" (+ i 1))))) include (include :my.embedded.module) Fennel/Lua . . Lua package.preload[modulename] require include . require requireAsInclude API --require-as-include (fennel --help) . require Fennel Lua . Fennel . require Lua . include --require-as-include import-macros . require . assert-repl ( 1.4.0) REPL . assert-repl : (let [input (get-input) value []] (fn helper [x] (table.insert value (calculate x))) (assert-repl (transform helper value) "could not transform")) assert false nil REPL (input value helper ). (AOT) Fennel . Fennel --assert-as-repl assert assert-repl . : Fennel 1.4.0 assert-repl fennel.repl . 1.4.1 assert . REPL assert-repl fennel.repl 1.4.1 API . API Fennel . (Recovering from failed assertions) REPL ,return EXPRESSION . false nil assert . : REPL . ,return . (MACROS) . . . . import-macros . . .fnl .fnlm . import-macros . when2 if do : (fn when2 [condition body1 & rest-body] (assert body1 "expected body") `(if ,condition (do ,body1 ,(unpack rest-body)))) {:when2 when2} . Fennel table.insert ipairs . (backtick) "unquote" . "my-macros.fnl" : (import-macros {: when2} :my-macros) (when2 (= 3 (+ 2 a)) (print "yes") (finish-calculation)) : (if (= 3 (+ 2 a)) (do (print "yes") (finish-calculation))) import-macros / . . : (import-macros mine :my-macros) (mine.when2 (= 3 (+ 2 a)) (print "yes") (finish-calculation)) . . : (local (module-name file-name) ...) (import-macros mymacros (.. module-name ".macros")) : (import-macros mymacros (.. ... ".macros")) "API " . (Macro module searching) Fennel : fennel.macro-path . Fennel . fennel.macro-searchers . find-in-archive : (local fennel (require :fennel)) (fn my-searcher [module-name] (let [filename (.. "src/" module-name ".fnl")] (match (find-in-archive filename) code (values (partial fennel.eval code {:env :_COMPILER}) filename)))) (table.insert fennel.macro-searchers my-searcher) : . . macros . . . "API " . (macros {:my-max (fn [x y] `(let [x# ,x y# ,y] (if (< x# y#) y# x#)))}) (print (my-max 10 20)) (print (my-max 20 10)) (print (my-max 20 20)) macro (macro my-max [x y] `(let [x# ,x y# ,y] (if (< x# y#) y# x#))) . fn . macrodebug (macrodebug (-> abc (+ 99) (< 0) (when (os.exit)))) ; -> (if (< (+ abc 99) 0) (do (os.exit))) macrodebug . . macrodebug . (Macro gotchas) . : (var v 1) (macros {:my-max (fn [x y] `(if (< ,x ,y) ,y ,x))}) (fn f [] (set v (+ v 1)) v) (print (my-max (f) 2)) ; -> 3 since (f) is called twice in the macro body above (symbol capture) . # (auto gensym) . (macros {:my-max (fn [x y] `(let [x2 ,x y2 ,y] (if (< x2 y2) y2 x2)))}) (print (my-max 10 20)) ; Compile error in 'x2' unknown:?: macro tried to bind x2 without gensym; try x2# instead macros . import-macros . (strict mode) ! my-fn : (fn my-fn [] (print "hi!")) (macros {:my-max (fn [x y] (my-fn) `(let [x# ,x y# ,y] (if (< x# y#) y# x#)))}) ; Compile error in 'my-max': attempt to call global '__fnl_global__my_2dfn' (a nil value) ..SS eval-compiler . . : (eval-compiler (each [name (pairs _G)] (print name))) . (Compiler Environment) eval-compiler macros macro import-macros : o list - . o sym - . o gensym - . o list? - false . o sym? - false . o table? - false . o sequence? - ( [] {}) false . o varg? - ... false . o multi-sym? - . false . o comment? - opts.comments . o view - fennel.view. o assert-compile - assert / . Lua 5.1 5.4 . Lua (local unpack (or _G.unpack table.unpack)) : o unpack - _G.unpack Lua 5.1/LuaJit table.unpack Lua >= 5.2 o pack - table.pack Lua 5.2 . (pack :a nil :c nil nil) -> {1 :a 3 :c :n 5}. nil . eval-compiler : o in-scope? - nil . o macroexpand - (AST) . : (Note: Compile-time List implementation) . . tostring . (Sandboxing) eval-compiler " " : print io.open . io os . {:compiler-env _G} API --no-compiler-sandbox . . . . lua (lua ESCAPE HATCH) Lua Fennel Fennel . Lua (early returns) Fennel . lua : (fn find [tbl pred] (each [key val (pairs tbl)] (when (pred val) (lua "return key")))) Lua (mangling) Lua . Fennel foo-bar foo_bar Lua . . Fennel : (local foo-bar 3) (let [foo-bar :hello] (lua "print(foo_bar0 .. \" world\")")) Lua : local foo_bar = 3 local foo_bar0 = "hello" print(foo_bar0 .. " world") return nil (statement) Lua . (expression) . . (DEPRECATED FORMS) # length ~= not= . require-macros ( 0.4.0) require-macros import-macros . import-macros . pick-args ( 0.10.0) pick-values n / f n f . global ( 1.1.0) . . . : (global prettyprint (fn [x] (print (fennel.view x)))) global . _G . (Rest destructuring metamethod) ( 1.4.1 ) __fennelrest . . table.unpack . : (local t [1 2 3 4 5 6]) (setmetatable t {:__fennelrest (fn [t k] (let [res {}] (for [i k (length t)] (tset res (tostring (. t i)) (. t i))) res))}) (let [[a b & c] t] c) ;; => {:3 3 :4 4 :5 5 :6 6} (AUTHORS) (Fennel Maintainers). fennel 1.6.1 2025-12-30 fennel-reference(5)