Andrey Listopadov

Fennel - зачем, где и как

~4 minutes read

Привет!

Я - Андрей Листопадов (andreyorst), один из разработчиков языка Fennel

Мой вклад в язык:

  • ?. оператор безопасного лукапа по вложенным таблицам,
  • fcollect макрос для диапазонного компрехеншена,
  • более лисповый и настраиваемый pretty-printer,
  • расширение API работы с метаданными функций, и
  • улучшения для интерактивной работы с REPL

Темы сегодняшнего доклада

  • Что такое Fennel и чем он не является
    • Какие фичи делают этот язык оправданным
  • Особенности компилятора
  • Поддержка редакторами
  • Где можно применить Fennel
    • Альтернативы

Lua? Fennel!

Что такое Fennel

  • Fennel - не Lisp*
  • Компилятор в Lua, с лисп макросами
  • Библиотека, для ембеддинга в Lua проекты
  • Lua, как платформа, Lisp, как фронтенд
  • Прямой интероп в обе стороны
  • Отсутствие своего рантайма

Фичи, на мой взгляд, оправдывающие существование Fennel

  • Более регулярный синтаксис (S-expressions)
  • Исправление проблем Lua
    • local-by-default
    • Отдельные формы для числовой итерации и итераторов:
      -- Lua                          ;; fennel
      for i=1,10 do                   (for [i 1 10]
        print(i)                        (print i))
      end
      
      for k,v in pairs(t) do          (each [k v (pairs t)]
        print("key:" k, "val:" v)       (print "key:" k "val:" v))
      end
      
    • Отдельный синтаксис для секвенций и таблиц: [], {}
    • Отказ от statement’ов в пользу expression’ов
  • Расширение языка дополнительными операторами
  • Возможность дальнейшего расширения через макросы

Подробнее о фичах

Таблицы

  • Синтаксис таблиц во многом опирается на опыт Clojure
  • Секвенции, они же массивы пишутся в квадратных скобках
    [1 "2" :foo [:bar (fn baz [] "qux")]]
    
  • Таблицы или хэшмапы в фигурных:
    {:foo "bar"
     :baz {:qux 42}}
    
  • Но это все еще одна и та же структура данных.
  • Списки, на самом деле, тоже таблицы.

Выражения

Немного о коде и его структуре

  • Fennel использует круглые скобки для обозначения исполняемого кода,
    • Единственное исключение - биндинг множественных значений.
  • Язык расширяется дополнительными формами, отсутствующими в Lua:
    • let, when, each, case и пр.
  • Всё является выражениями.

Пример кода на Fennel:

(fn add [a b ...]
  (let [sum (+ a b)]
    (if ...
        (add sum ...)
        sum)))

(add 1 2 3 4)

Использование if как экспрешшена:

(print (if (> (math.random 100) 50) :heads :tails))

Уход от IIFE:

(let [side (if (> (math.random 100) 50) :heads :tails)]
  (print side))

Деструктурирование и паттерн матчинг

  • Destructuring:

    (local t {:a 1 :b 2})
    
    (let [{:a a :b b} t]
      (+ a b))
    
    (fn normalize [[a b]]
      (let [len (math.sqrt (+ (* a a) (* b b)))]
        [(/ a len) (/ b len)]))
    
    (normalize [3 4])
    
    • Вложенное разбиение
      (local t [:a :b {:x 1 :y 2}])
      
      (let [[key1 key2 {: x : y}] t]
        {key1 x
         key2 y})
      ;; {:a 1 :b 2}
      
  • Pattern matching:

    (case (request)
      {:status :ok :data data} (process data)
      {:status :warn :message msg} (io.stderr:write msg "\n")
      {:status _} (error (.. "unexpected status: " _)))
    

Макросы

  • lisp-style quote, unquote
    (macro inc [var*]
      (assert-compile (sym? var*) "must pass a symbol" var*)
      `(do (set ,var* (+ ,var* 1)) ,var*))
    
    (var x 10)
    (inc x)   ;; returns 11
    (print x) ;; 11
    
  • Автоматическая проверка на hygiene
    (macro no-capture [...]
      `(let [answer 1337]
         ,...))
    
    (let [answer 42]
      (no-capture
       (print (.. "Answer to the Ultimate Question of Life, "
                  "the Universe, and Everything: "
                  answer))))
    
  • Манипуляции кодом, как таблицами
    (macro loop [binding-vec ...]
      (let [keys [] gensyms [] bindings []]
        (each [i v (ipairs binding-vec)]
          (when (= 0 (% i 2))
            (let [key (. binding-vec (- i 1))
                  gs (gensym (tostring i))]
              (table.insert gensyms gs)
              (table.insert keys key)
              (doto bindings
                (table.insert gs)
                (table.insert v)
                (table.insert key)
                (table.insert gs)))))
        `(let ,bindings
           ((fn ,(sym :recur) ,keys ,...)
            ,(unpack gensyms)))))
    
    (loop [x 10]
      (when (> x 0)
        (print x)
        (recur (- x 1))))
    

Недостатки макро системы fennelа:

  • Макросы не могут быть эксптртированы из того же файла, что и функции.
  • Не поддерживается вложенный квазиквотинг и анквотинг.
  • Нет unquote-splicing (таблицы всё таки).
  • Сложности с использованием обычных функций из макросов.
  • Сложности с поставкой библиотек, содержащих макросы.

Поддержка редакторами

Редакторы для Fennel

Редакторы, конфигурируемые на fennel

Поддержка инструментами

Благодаря расширяемости require системы Lua, Fennel поддердивается практически всем, что предоставляет доступ к package:

fennel = require("lib.fennel")
table.insert(package.loaders, fennel.searcher)
-- или
require("fennel").install()

Феннел можно использовать с:

Альтернативы

Спасибо

Материалы по теме:

  • fennel-lang.org - официальный сайт языка, wiki, reference
  • #fennel на irc.libera.chat и Matrix
  • andreyor.st - мой блог о fennel’е, Emacs’е и многом другом