Skip to content
Kevin edited this page Jun 6, 2026 · 4 revisions

Fennel is a programming language that brings together the simplicity, speed, and reach of Lua with the flexibility of a Lisp syntax and macro system.

Since Astal-Lua works the same in Fennel, this guide focuses on the syntax differences and common patterns used when translating examples from the documentation.

Childrens

When instantiating child widgets, Lua allows mixing key-value pairs and array values within the same table. However, in Fennel, tables cannot contain both map and array entries in the same literal. To avoid manually specifying numeric indices, child widgets can be passed directly as constructor arguments.

(fn Bar [{: gdkmonitor}]
  (let [{: Window : CenterBox : Label} Widget]
    (Window {: gdkmonitor
             :anchor [:TOP :LEFT :RIGHT]
             :exclusivity :EXCLUSIVE}
            (CenterBox (Label {:label "left label"})
                       (Clock {:clock-format "%a %d %b %H:%M:%S"})
                       (Label {:label "right label"})))))
local function Bar(args)
    return Widget.Window {
        gdkmonitor = args.gdkmonitor,
        anchor = { 'TOP', 'LEFT', 'RIGHT' },
        exclusivity = 'EXCLUSIVE',
        Widget.CenterBox {
            Widget.Label { label = 'left label' },
            Clock {
                clock_format = '%a %d %b %H:%M:%S',
            },
            Widget.Label { label = 'right label' },
        },
    }
end

Properties

Fennel also has fewer restrictions on special characters, which allows the use of $ for things like defining bindings. Additionally, properties passed to a constructor can use hyphens (-) in their names.

(fn Clock [{: clock-format}]
  (let [{: Label} Widget
        time (let [v (Variable.new 0)]
               (v:poll 1000 #(os.time)))
        $date (let [b (bind time)]
                (b:as #(os.date clock-format $)))]
    (Label {:label $date :css-classes [:clock-label] :on_destroy #(time:drop)})))
local function Clock(args)
    local time = Variable.new(0):poll(1000, function()
        return os.time()
    end)

    local date = bind(time):as(function(t)
        return os.date(args.clock_format, t)
    end)

    return Widget.Label {
        label = date,
        css_classes = { 'clock-label' },
        on_destroy = function()
            time:drop()
        end,
    }
end

Macros

One of Fennel's most powerful features is its macro system, they can help reduce boilerplate and create small abstractions.

For example, you can define convenience macros for creating variables and polling values:

(macro defpoll [name value interval exec]
  `(local ,name (let [v# (Variable.new ,value)]
                  (v#:poll ,interval ,exec))))
(defpoll time 0 1000 #(os.time))

;; Instead of
(local time (let [v (Variable.new 0)]
              (v:poll 1000 #(os.time))))

Clone this wiki locally