Posts with tag "commonlisp"

Maxima скомпилировали для запуска в браузере!

posted on 2025-01-29

Maxima скомпилировали для запуска в браузере!

Maxima это программа для работы с математическими выражениями. Примечательно то, что эта система написана на Common Lisp.

И вот недавно на Reddit появилось сообщение, что Maxima удалось скомпилировать в WebAssembley так что теперь её можно запустить прямо в браузере.

Я записал для вас небольшую гифку с демкой, а кому интересно, тот может сам потыкать в пример развернутый на сайте:

http://maxima-on-wasm.pages.dev/

Что в этой истории примечательного? То что компиляция в WebAssembly расширяет возможности использования Common Lisp. Я пока не погружался в детали того как это работает, но судя по тому, что в данном проекте использовался ECL (Embeddable Common Lisp), могу предположить, что Maxima транслировали в C, а затем уже собрали в WebAssembly.

Обсудить пост в Telegram канале.

Более быстрый робот в первом задании MTS True Tech Champ

posted on 2025-11-20

Как я писал в предыдущей заметке, первая моя попытка провести робота через две комнаты из первого задания хакатона МТС True Tech была неудачной. Робот ехал медленно и сбивался с пути. Но в какой-то момент один из участников подсказал мне в чате, что нужно действовать иначе.

Первая моя ошибка была в том, как я пытался вывести робота из тупика, откуда он начинает свой путь. Я сначала делал разворот на 180 градусов. И это было медленно. Чертовски медленно. На разворот уходила почти минута. А надо было просто выезжать задом!

Причем задом можно было проехать весь путь от начальной точки до конечной. Разницы никакой не было. Робот ехал что вперед, что назад одинаково быстро. Так я начал выезжать из тупика задним ходом.

Второй совет заключался в том, что надо ориентироваться не на путь по точкам, а на лазерные лучи. У робота на борту был лидар, у которого угол обзора составлял 90 градусов. Ну и вот, я написал очень простой код, который позволял роботу двигаться с определенной скоростью и углом поворота до тех пор, пока не выполнится условие.

Когда условие выполнялось, робот переходил к следующему шагу. Мог изменить скорость, угол разворота и проверял новые условия, которые должны наступить, чтобы перейти к дальнейшему шагу. Для того, чтобы это было удобно описывать в коде, я написал маленький макрос, который позволял указать параметры движения и условия выхода. Причем в условии выхода можно было написать любой лисп-код, в том числе логирование или какие-то сложные вычисления.

В дальнейшем я даже усовершенствовал этот макрос и сделал так, что и скорость с углом поворота тоже можно вычислять на каждом шаге. Вот пример такого кода, которому следовал робот. Каждый блок go-until соответствует шагу, который выполняется до тех пор, пока тело макроса не вернет true. Под капотом у каждого из таких блоков лежит цикл:

;; Выезжаем из тупика
(go-until (2.0)
  (log-step 1)
  ;; Меряем расстояние по центрально точке лидара
  (> front 4.5))

;; Начинаем поворачивать вправо
(go-until (2.0 :turn-angle 45.0)
  (log-step 2)
  ;; Используем крайние точки лидара
  (and (around left 2.80)
       (around right 4.35)))

;; Движемся по прямой до стены
(go-until (2.0)
  (log-step 3)
  (around front 8.0))

;; Поворачиваем налево, к мосту
(go-until (2.0 :turn-angle -30.0)
  (log-step 4)
  (and (around left 2.71 :delta 0.2)
       (around front 3.49 :delta 0.2)
       (around right 6.26 :delta 0.2)))

То есть робот в цикле, роботу отправляется команда ехать с указанной скоростью и углом разворота. И в цикле же проверяется условие выхода из цикла. Вот и всё! На такой простой идее получилось достичь достаточно стабильных результатов и проехать уровень не сбавляя скорости.

Как я это отлаживал? Запускал в симуляторе робота и смотрел по крайним точкам лидара на расстояния. В момент, когда мне нужно было, например, перейти из прямолинейного движения робота в поворот, я смотрел примерное положение, которое показывает лидар и вносил эти значения в условия выхода из последнего блока робота. Следом за ним добавлял следующий блок с измененными характеристиками движения. И смотрел дальше, какие параметры мне нужно отследить, чтобы посчитать условия выхода для этого нового блока.

Так получилось проложить всю траекторию от начала до конца. На видео, которое приложено выше, как раз и показано, как робот проходит весь путь из комнаты 1 в комнату 2, обходя все препятствия.

В следующем посте расскажу, как обстояли дела со вторым заданием хакатона MTS True Tech Champ 2025.

Как продвигаются дела с MCP фреймфорком для Common Lisp

posted on 2025-06-29

Привет, друзья! Сегодня хочу дать небольшой апдейт по тому, как продвигаются дела с MCP-фреймворком для того, чтобы можно было писать MCP-сервера на Common Lisp.

На этих выходных я закончил реализацию протокола с Streamable-HTTP. Для чего это нужно было? Почему нельзя было обойтись просто поддержкой STDIN/STDOUT протокола? Streamable-HTTP позволяет создавать MCP-сервера, к которым смогут подключаться клиенты удаленно. То есть этот MCP-сервер может быть развернут где-то в облаке.

Одна из моих ближайших целей - сделать MCP-сервер для сайта https://ultralisp.org. Идея пока такая, что Ultralisp будет предоставлять MCP-сервер для поиска Common Lisp библиотек, а также для поиска функций, макросов и прочего, что внутри этих библиотек есть.

То есть этот MCP-сервер будет давать код-ассистенту доступ к док-стрингам всех Lisp библиотек. И тогда код-ассистент сможет находить нужные ему библиотеки и функции внутри этих библиотек, даже если эти библиотеки еще не установлены в его окружении. И далее с помощью других инструментов код-ассистент сможет эти библиотеки поставить и воспользоваться теми функциями, что он нашел.

Для этого я добавил в свой MCP-фреймворк поддержку Streamable-HTTP. Чтобы это сделать, пришлось написать еще одну библиотечку, которая реализует поддержку Server-Sent Events протокола для CLACK - clack-sse.

Server-Sent-Events - это такой упрощенный аналог WebSocket, который позволяет серверу отправлять события на клиент по HTTP. В Streamable-HTTP протоколе MCP-сервера этот метод используется для того, чтобы отправлять уведомления об изменениях внутри сервера.

В видео, которое закреплено в этом посте, я как раз показываю, для чего используются такие пуши с сервера. Там демонстрируется, как при изменении инструментов внутри Common Lisp образа уведомления автоматически доставляются в IDE, и оно мгновенно узнает о том, что поменялось описание инструмента или, например, добавился новый инструмент.

Выглядит все это довольно круто, теперь можно с помощью моего фреймворка интерактивно разрабатывать MCP-сервера и тут же их тестировать в IDE.

Обсудить пост в Telegram канале.

Красота для LispWorks

posted on 2025-01-31

Красота для LispWorks

Когда я впервые увидел LispWorks IDE, она показалась мне довольно непривычной и допотопной что ли. Там нет многих привычных вещей из Emacs. Но оказывается, если приложить усилия, то можно сделать из этого IDE конфетку. Но вероятно надо быть прямо очень мотивированным к тому, чтобы писать код именно в интерфейсе LispWorks, а не подключившись к нему из Emacs.

Недавно на Reddit анонсировали библиотеку расширений для LispWorks - lw-plugins. Она добавляет сайдбар с деревом папок, кастомные иконки, фолдинг докстрингов и многое другое. На скриншоте к моему посту демо некоторых фич.

Я добавил этот проект в "lispworks" дист на Ultralisp.org и теперь расширения можно установить легко и просто с помощью Quicklisp.

Приятно, когда люди вкладывают душу в подобные проекты.

Обсудить пост в Telegram канале.

Первый этап хакатона МТС True Tech Champ 2025

posted on 2025-11-16

Как и обещал, начинаю серию постов про хакатон MTS True Tech 2025. Это технический хакатон, который разделен на две части. Первая часть алгоритмическая, а вторая часть робототехническая. Участвовать можно только в какой-то одной. Я решил попробовать себя в робототехнике. При этом опыта на момент старта было примерно ноль. Никогда раньше ничего не делал для роботов и было очень интересно попробовать.

Хакатон начался в сентябре, но присоединился я к нему только лишь в начале октября, пропустив довольно большое количество времени просто из-за того, что в сентябре участвовал в другом хакатоне, лидеры цифровой трансформации, да и не было полной уверенности еще в том, что я хочу попробовать себя в робототехнике. MTS TrueTech состоит из нескольких этапов.

Первый этап, отборочный, представлял собой три задачи, которые нужно было решить. В каждой задаче требовалось управлять роботом внутри симулятора. При этом код можно было писать на любом языке и, конечно, я выбрал в качестве языка Common Lisp. Было интересно попробовать, как вообще на нем можно делать что-то для роботов.

Cоздатели MTS TrueTech подготовили для участников специальную прослойку, которая позволяла запускать код, управляющий роботом, как отдельный процесс, и она могла взаимодействовать с симулятором, получая от него информацию и подавая управляющие воздействия на робота.

На видео, которое приложено к этому посту, видно одно из моих первых решений. Это решение для первой задачи, первого отборочного этапа, где нужно было проехать роботом из одной виртуальной комнаты в другую, обойдя все препятствия. Мы называли эту задачу "уточки", потому что робот стартовал из тупика ограниченного игрушечными уточками.

В этом решении участникам заранее организаторы сказали, что можно, в общем-то, и захардкодить путь робота. Это я и пытался сделать почти неделю – хотел написать алгоритм, который проведет робота по точкам. Но дело осложнялось тем, что у робота была большая инерция, а так же не было точных координат этого робота. Из-за этого он постоянно пролетал мимо точек, либо доворачивался не на тот угол, который нужно. В итоге, к концу трека робот часто сбивался c пути и не выходил на заданную точку – до центра второй комнаты, где нарисован белый прямоугольник.

Затем мне один из участников в чате подсказал, что на самом деле нужно использовать другой подход. Я переключился на другой подход и сделал решение не за неделю, а всего за полдня. В этот раз робот ехал ориентируясь на лидар.

Лидар это такое устройство, которое с помощью лазера измеряет расстояние до других объектов. Вот у робота был такой лидар, который направлен вперед. И по этому лидару я замерял расстояние до стен. Причем использовал всего три точки лидара. Одну, которая смотрит прямо, и два крайних луча, которые смотрят под 45 градусов. Я написал специальный DSL-чик на Лиспе, который позволяет описать последовательность условий, при которых нужно изменять управляющее воздействие на робота.

И с помощью такого нехитрого алгоритма робот гораздо более стабильно начал доходить до цели. При чём мой робот проехал уровень достаточно быстро, чтобы попасть на 45 строчку лидерборда из 124 участников!

Если вам интересно, напишите в комментариях, и я следующим постом приложу ролик, который показывает, как робот ехал по новому алгоритму, и покажу кусочек кода того, как выглядел на DSL для описания шагов робота.

Почему я использую package-inferred ASDF стиль в своих библиотеках

posted on 2025-08-24

C package-inferred ASDF, тебе не надо задумываться о том, какие зависимости есть между твоими файлами. Точнее задумываеться конечно надо, но исключается ситуация, когда ты прописал их в неправильном порядке.

Например, если ты добавляешь в один из файлов использование символа, который определен в другом файле как функция, то нужно поменять местами эти модули в описании ASDF системы.

Тут важно не забыть сделать это вручную, потому что иначе компилятор не сможет оптимизировать код. Ведь он не будет знать, что этому символу соответствует функция.

В случае же с в package-inferred system, все зависимости между компонентами системы прописываются явно в каждом файле. За счёт этого легко отследить какие символы из какого файла проимпортированы, а загрузчик библиотеки загружает файлы в правильном порядке согласно импортам в DEFPACKAGE.

Но даже в случае с package-inferred system зависимости надо прописывать в каждый файл и это можно забыть сдеалать. В таком случае, при загрузке библиотеки с нуля может возникнуть ошибка в духе "такой-то пакет не найден". Чтобы предотвратить случаи, когда для какого-то из используемых символов я забыл прописать IMPORT-FROM, я сделал линтер, который проверяет (в том числе и в PR), что:

• Ни одна из зависимостей не забыта • Нет неиспользуемых импортов

Это позволяет поддерживать ASDF систему в здоровом состоянии.

Конечно, для небольшой системы, состоящей из одного файла, использование старого способа описания ASDF системы не вызывает проблем. Однако со временем системы склонны разрастаться.

Когда система становится большой, рефакторить ее и переделывать из old-school ASDF систем в package-inferred system может быть довольно проблематично.

Также, то что package-inferred ASDF система требует явного описания и указания пространства имён, в которое помещаются символы. Это помогает пользователям библиотек лучше понимать, к какой области относится определённый символ.

Конечно, то же самое можно сделать, определив несколько пакетов и описав их в системе в старом стиле. Однако это потребует дополнительных усилий, так как нет чётких правил о том, в каком пакете должны находиться символы из определённого файла.

Как я вижу, тут коммон-лисперы разделяются на два лагеря. Одни - любят и используют package-inferred, другие - ненавидят. А к какому лагерю относишься ты?

Обсудить пост в Telegram канале.

Современный i18n с Fluent

posted on 2025-08-17

Периодически я просматриваю новые библиотеки, которые добавляются в репозиторий https://ultralisp.org. Недавно мне попалась Common Lisp библиотека для интернационализации программ на Common Lisp. Она называется Fluent.

Я начал разбираться, как она работает, и оказалось, что Fluent — это стандарт, подход к интернационализации, предложенный несколько лет назад компанией Mozilla. С тех пор для этого формата переводов создали библиотеки для разных языков программирования. Теперь такая библиотека появилась и для Common Lisp.

Чем Fluent отличается от давно известного всем gettext и ему подобных? Формат Fluent предлагает более гибкий подход к формированию переводов. Под каждый язык и его грамматические особенности можно сделать свой шаблон. Fluent чем-то напоминает язык шаблонов Mustache.

Для примера покажу, как это выглядит на Common Lisp. Сначала нужно создать директорию для хранения переводов. Для примера сделаем поддержку английского и русского языков.

Для английского перевода создадим файл /tmp/i18n/en-US/app.ftl с таким содержимым:

apples-and-leafs = { $apple-count ->
    [one] {$apple-count} apple
   *[many] {$apple-count} apples
} and { $leaf-count ->
    [one] {$leaf-count} leaf.
   *[many] {$leaf-count} leafs.
}

Аналогичный файл /tmp/i18n/ru-RU/app.ftl создадим для русского языка:

apples-and-leafs = { $apple-count ->
    [one] {$apple-count} яблоко
    [few] {$apple-count} яблока
   *[many] {$apple-count} яблок
} и { $leaf-count ->
    [one] {$leaf-count} лист.
    [few] {$leaf-count} листа.
   *[many] {$leaf-count} листов.
}

Обратите внимание, что в русском языке три формы множественного числа, а в английском только две. Благодаря гибкости формата Fluent, можно описать в файле целое предложение, даже если оно зависит от нескольких чисел.

Когда директория с переводами готова, мы можем загрузить эти переводы и получить текст на нужном языке. В Common Lisp это выглядит так:

CL-USER> (let ((ctx (fluent:fluent (fluent:read-all-localisations #P"/tmp/i18n/")
                                   :locale :ru-ru)))
           (fluent:resolve ctx "apples-and-leafs" :apple-count 21 :leaf-count 33))
"21 яблоко и 33 листа."

CL-USER> (let ((ctx (fluent:fluent (fluent:read-all-localisations #P"/tmp/i18n/")
                                   :locale :en-us)))
           (fluent:resolve ctx "apples-and-leafs" :apple-count 21 :leaf-count 33))
"21 apples and 33 leafs."

По-моему, довольно гибкое решение.

Если мне когда-нибудь понадобится делать приложение с переводами, я обязательно буду использовать Fluent. А был ли у вас опыт создания мультиязычных приложений? Если да, расскажите комментариях, какой подход к хранению переводов использовали?

Обсудить пост в Telegram канале.

Шпаргалка по использованию Roswell и Qlot в Common Lisp проектах

posted on 2025-02-16

Вчера в телеграм-чате LispForever, посвященном Сommon Lisp просили подсказать, какой подход лучше использовать для запуска лиспа. Мнения разделились. Кто-то рекомендовал использовать Roswell, кто-то наоборот - был против. Я же Roswell люблю по той причине, что он позволяет легко и просто устанавливать разные реализации Common Lisp и переключаться между ними. Это особенно удобно, когда у тебя куча проектов, и каждый может использовать свою версию CL.

По той же причине, кстати, я использую и Qlot. Эта утилитка позволяет создавать виртуальное окружение для каждого проекта, и не засорять пакетами систему. Кроме того, Qlot фиксирует версии библиотек в репозитории проекта, делая сборки воспроизводимыми.

Итак, вот какими командами я обычно пользуюсь в Roswell:

# Установка лиспа
ros install sbcl-bin
ros install sbcl-bin/2.5.1
# Запуск Emacs или REPL
ros emacs
ros run
# Создание command-line утилиты и сборка ее в бинарник
ros init some-script
vim some-script.ros
ros build some-script.ros
./some-script

Ещё имеет смысл в конфиге подкрутить ограничение памяти, потому что по умолчанию оно всего 1G:

ros config set dynamic-space-size 4gb

И да. Обычно я использую не roswell сам по себе, а вместе с Qlot. Qlot позволяет ставить либы не глобально, а в директорию проекта. Как virtualenv в Python или node_modules у npm.

Так, у каждого проекта свои зависимости, а поработав с каким-то можно без проблем снести директорию .qlot.

Краткая инструкция по Qlot:

# Подключаем cutting-edge репозиторий пакетов
echo 'dist ultralisp http://dist.ultralisp.org/' >> qlfile
# Инициализируем:
qlot install
# Далее запускаем Емакс и хакаем лисп код
CL_SOURCE_REGISTRY=`pwd`/ qlot exec ros emacs
CL_SOURCE_REGISTRY=`pwd`/ qlot exec ros <любые другие команды, такие как ros build>

# Когда надо обновить зависимости (qlot фиксирует версии в `qlfile.lock`), делаем:

qlot update

Вот и всё, друзья!

Обсудить пост в Telegram канале.

Created with passion by 40Ants