Рубрика "дичь, которую творил я когда-то": всё те же часы, только в железной версии (ATMega8).
Корпус из монтажной коробки, пена от коврика, светофильтр из ткани 3M, трубочки для коктейля в роли световодов, вот это вот всё. Макетка и сдвиговый регистр старше меня (макетка — сильно старше).
Забавно, что "часики-то тикают", хотя цать лет назад я даже не очень свежую батарейку ставил :)
Корпус из монтажной коробки, пена от коврика, светофильтр из ткани 3M, трубочки для коктейля в роли световодов, вот это вот всё. Макетка и сдвиговый регистр старше меня (макетка — сильно старше).
Забавно, что "часики-то тикают", хотя цать лет назад я даже не очень свежую батарейку ставил :)
Повозился тут со штуками вокруг Gemini (см выше).
Серверы
Именно статический "сайт" поднимать не особо хотелось, но таки поднял:
Потыкал пакеты из набора haskell-gemini: пока сыровато. Так что #Haskell с Gemini я подружу позже.
Зато приятно оказалось пользоваться Jetforce. Это фреймворк для #Python такой. Построен на Twisted, внутри типы проаннотированы, async тут и там, при этом код выглядит "как Flask", то есть как набор декорированных функций — современненько!
Мне интересно было именно "Gemini приложение" написать, то есть что-то, создающее контент на лету. Получилось вполне прилично: основная функциональность в Jetforce уже есть. Нужно только будет написать eDSL для более удобного (чем захват регулярками) описания параметров в путях.
Клиенты
На десктопе попробовал пожить с Castor, графическим клиентом, написанным на #Rust, опять же. Но клиент пока слишком юн, и им не слишком удобно пользоваться.
В итоге пересел на Lagrange. Вот этот — красавец! Написан на C11 и SDL, аппаратно ускоряет отрисовку, красиво показывает текст, может и графику показать inline, управляется хоткеями. Даже "лого" генерирует для ресурсов на основе их имени — как умолчательные аватары на GitHub, вы такие точно видели. Пока я крайне доволен пользованием, рекомендую!
На Android пока остановился на deedum. Он не слишком красив, но крайне шустр и нетребователен к ресурсам.
Контент
В Gemlogs — это такие дневники в Geminiverse — народ пишет, есть что почитать. А ещё есть зеркала Lobste.rs и HackerNews, даже с комментариями. Ту же Wikipedia можно почитать через такой прокси. Словом, уютненько
Серверы
Именно статический "сайт" поднимать не особо хотелось, но таки поднял:
gemini://recursive.one (он же через проксю). Раздаётся с помощью Agate, минималистичного Gemini-сервера, написанного на #Rust. Agate кроме раздачи статики ничего не умеет, так что пока статическая страница и повисит %)Потыкал пакеты из набора haskell-gemini: пока сыровато. Так что #Haskell с Gemini я подружу позже.
Зато приятно оказалось пользоваться Jetforce. Это фреймворк для #Python такой. Построен на Twisted, внутри типы проаннотированы, async тут и там, при этом код выглядит "как Flask", то есть как набор декорированных функций — современненько!
Мне интересно было именно "Gemini приложение" написать, то есть что-то, создающее контент на лету. Получилось вполне прилично: основная функциональность в Jetforce уже есть. Нужно только будет написать eDSL для более удобного (чем захват регулярками) описания параметров в путях.
Клиенты
На десктопе попробовал пожить с Castor, графическим клиентом, написанным на #Rust, опять же. Но клиент пока слишком юн, и им не слишком удобно пользоваться.
В итоге пересел на Lagrange. Вот этот — красавец! Написан на C11 и SDL, аппаратно ускоряет отрисовку, красиво показывает текст, может и графику показать inline, управляется хоткеями. Даже "лого" генерирует для ресурсов на основе их имени — как умолчательные аватары на GitHub, вы такие точно видели. Пока я крайне доволен пользованием, рекомендую!
На Android пока остановился на deedum. Он не слишком красив, но крайне шустр и нетребователен к ресурсам.
Контент
В Gemlogs — это такие дневники в Geminiverse — народ пишет, есть что почитать. А ещё есть зеркала Lobste.rs и HackerNews, даже с комментариями. Ту же Wikipedia можно почитать через такой прокси. Словом, уютненько
:^)👍1
Я вам скажу, с вводом данных в #Gemini работать не скучно: аналога POST в Gemini нет, как нет и URL encoded параметров! И cookies тоже отсутствуют, как класс, как вы помните. Да, вы можете привязаться к пользовательскому сертификату и таким образом реализовать сессионность, но это уже будет сквозное состояние, чего хотелось бы избежать.
Зато Gemini сервер может запросить строку специальным ответом. После чего клиент покажет пользователю диалог с заданным приглашением, а результат ввода отправит обратно на сервер в виде query string. Соответственно, если вы хотите получить несколько значений, то вам нужно будет каждое запросить отдельно и сохранить в URL (кроме последнего, которое будет в query string). При этом размер запроса ограничен килобайтом на всё про всё — много не попостишь. Но тем интереснее!
Лелею идею написать FSM, которая взяла бы на себя весь этот сбор параметров, чтобы в обработчике запроса уже иметь все данные введёнными. Для Jetforce описание параметров можно будет оформить как "eDSL #Python-style" — на декораторах.
Также было бы интересно попробовать воплотить в Gemini интерактивность, подобную той, что использует #Racket в своих Stateful Servlets. Там вы тоже пишете код так, что данные вводятся "запросами к пользователю": вы отравляете пользователю форму, и получаете результат тут же в коде. Внутри всё реализовано на сохранении состояния в URL и рэкетовых continuations, снаружи же выглядит как магия — люблю такое
Зато Gemini сервер может запросить строку специальным ответом. После чего клиент покажет пользователю диалог с заданным приглашением, а результат ввода отправит обратно на сервер в виде query string. Соответственно, если вы хотите получить несколько значений, то вам нужно будет каждое запросить отдельно и сохранить в URL (кроме последнего, которое будет в query string). При этом размер запроса ограничен килобайтом на всё про всё — много не попостишь. Но тем интереснее!
Лелею идею написать FSM, которая взяла бы на себя весь этот сбор параметров, чтобы в обработчике запроса уже иметь все данные введёнными. Для Jetforce описание параметров можно будет оформить как "eDSL #Python-style" — на декораторах.
Также было бы интересно попробовать воплотить в Gemini интерактивность, подобную той, что использует #Racket в своих Stateful Servlets. Там вы тоже пишете код так, что данные вводятся "запросами к пользователю": вы отравляете пользователю форму, и получаете результат тут же в коде. Внутри всё реализовано на сохранении состояния в URL и рэкетовых continuations, снаружи же выглядит как магия — люблю такое
;) А уж насколько магически выглядят Web Cells: вы имеете переменную, которая помнит состояние между вызовами обработчика запроса, и таких переменных даже может быть несколько!Сил оформлять полноценные статьи пока нет, выдам выжимку того, чем я сейчас занимаюсь. Может быть что-то из этого и разовьётся в статью-другую!
Продолжаю приобщаться к #Gemini, почитываю чужие gemlogs, подумываю поднять зеркало этого канала в рамках своей капсулы (в Gemini так называют серверы, участвующие в сети). Выгрузил данные канала в виде JSON, накидал транскодер в Org mode. Но тут оказало, что Telegram не выгружает изображения, добавляемые к сообщениям ботом — не "фото + заметка", а именно "сопровождающее изображение". Я таких написал достаточно, чтобы жалеть о потере. Поэтому поднятие зеркала пока подвисло, может пройдусь вручную по публикациям и утяну картинки, а может и так оставлю — посмотрим.
Членствую в чате RuGemini, "нас мало, но мы в тельняшках". Кто-то хочет "вернуть себе Web", кто-то хочет уйти из Web совсем и делает proxy для поиска по StackOverflow, чтобы читать из Acme) под Plan9 — развлекаются, проще говоря. В Gemini контента, как ни странно, чатовцы мало генерят.
А в самой Geminispace есть, что почитать перед сном. Вот только находить контент мне удобно на ПК, а читать удобно с телефона! Вот и подумываю о том, чтобы сделать свой сервис закладок или даже аналог Pocket. Может даже с проксированием HTML, чтобы читать в text-only виде и околотекстовый Web. Коль скоро в Gemini нет cookies, а аутентификация делается с помощью SSL-ключей, приватный proxy не выглядит таким уж небезопасным решением.
Помимо Gemini, закопался ещё и в IndieWeb — тоже занятный островок для недовольных современным Web. Эти недовольны его хрупкостью и зависимостью от больших корпораций (коих пренебрежительно называют silos) и призывают владеть своими данными, а так же развивать свою identity в сети. Микроформаты вроде h-card, публикация через MicroPub — есть, про что почитать и что себе на сайт утащить. Можно послушать подкаст — неплохая вводная в IndieWeb!
Вот как-то так. Может ещё и про железки напишу (пополнил коллекцию интересными штучкам от M5Stack) — в такой же манере, а может и более структурировано.
Продолжаю приобщаться к #Gemini, почитываю чужие gemlogs, подумываю поднять зеркало этого канала в рамках своей капсулы (в Gemini так называют серверы, участвующие в сети). Выгрузил данные канала в виде JSON, накидал транскодер в Org mode. Но тут оказало, что Telegram не выгружает изображения, добавляемые к сообщениям ботом — не "фото + заметка", а именно "сопровождающее изображение". Я таких написал достаточно, чтобы жалеть о потере. Поэтому поднятие зеркала пока подвисло, может пройдусь вручную по публикациям и утяну картинки, а может и так оставлю — посмотрим.
Членствую в чате RuGemini, "нас мало, но мы в тельняшках". Кто-то хочет "вернуть себе Web", кто-то хочет уйти из Web совсем и делает proxy для поиска по StackOverflow, чтобы читать из Acme) под Plan9 — развлекаются, проще говоря. В Gemini контента, как ни странно, чатовцы мало генерят.
А в самой Geminispace есть, что почитать перед сном. Вот только находить контент мне удобно на ПК, а читать удобно с телефона! Вот и подумываю о том, чтобы сделать свой сервис закладок или даже аналог Pocket. Может даже с проксированием HTML, чтобы читать в text-only виде и околотекстовый Web. Коль скоро в Gemini нет cookies, а аутентификация делается с помощью SSL-ключей, приватный proxy не выглядит таким уж небезопасным решением.
Помимо Gemini, закопался ещё и в IndieWeb — тоже занятный островок для недовольных современным Web. Эти недовольны его хрупкостью и зависимостью от больших корпораций (коих пренебрежительно называют silos) и призывают владеть своими данными, а так же развивать свою identity в сети. Микроформаты вроде h-card, публикация через MicroPub — есть, про что почитать и что себе на сайт утащить. Можно послушать подкаст — неплохая вводная в IndieWeb!
Вот как-то так. Может ещё и про железки напишу (пополнил коллекцию интересными штучкам от M5Stack) — в такой же манере, а может и более структурировано.
Ох, что-то я совсем канал подзабросил, и нельзя сказать, что не о чем было писать… Будем считать, что я брал академический отпуск.
Нужно будет как-то снова набрать обороты, и при этом делать записи вида "ссылка плюс абзац текста" мне не хочется. Да и накопившиеся темя я так не разгребу, потому что поигрался за полгода с самими разными штуками. Можно попробовать в каком-то смысле интерактивный режим: я перечислю то, о чём мог бы написать, а вы сможете понажимать кнопки
Итак, за полгода я, помимо прочего, делал нижеперечисленное.
1
Наконец-то добрался до полноценного погружения в Common Lisp, прочитал пару книжек, сделал игрушечный проект в виде веб-приложения с БД, шаблонами и HTTP-запросами к API внешнего сервиса. Понравилось!
2
Переехал на Debian c Ubuntu (что само по себе скучно описывать), в связи с чем пришлось какое-то время посидеть на XFCE, который я постарался довести до некоторого подобия старого окружения вокруг i3.
В процессе чуть было не начал писать на Vala — вот про это уже можно написать! А толчком стало желание заиметь индикатор работы Yandex.Disk. Упаковал весь инструментарий в Docker-контейнер, собрал "Hello World" с интерфейсом на GTK3, начал радоваться. А потом оказалось, что нынче пиктограммы в system tray, это устаревшая идея и в GTK саму возможность делать таковые убрали!
В итоге обошёлся обвязыванием виджета XFCE Generic Monitor — это такая основа для самодельных индикаторов. Про это тоже может получиться запись.
3
В очередной раз глянул на Kotlin, после того, как наткнулся на видео о http4k. Это такая библиотечка, авторы которой открыли для себя, что web-сервер, это функция из request в response и преподносят теперь это их открытие, как новшество
В результате знакомства сделал Kotlin-версию того же проекта, который делал на Common Lisp. Решать знакомую задачу новым способом — это вообще неплохой способ знакомиться с языками, как вы знаете. Тоже HTTP API client + SQLite в роли БД + http4k в роли сервера и вёрстка на DSL. Большие фреймворки трогать не хотел, поэтому выбрал библиотеки "по примерам в README". В итоге впечатления, что называется, смешанные.
4
Из того, что началось раньше, но продолжалось в эти полгода, ещё есть проект, который мы на пару с товарищем, реализуем в рамках серии видео-трансляций. Это игрушечный движок для текстовых Interactive Fiction, построенный на базе… Django (это такой большой Web-фреймворк для Python, если кто вдруг не знал). Выбран Django потому, что я его умею использовать, а тема IF выбрана потому, что такое на Django обычно не делают
Было ещё что-то более мелкое, буду вспоминать
Нужно будет как-то снова набрать обороты, и при этом делать записи вида "ссылка плюс абзац текста" мне не хочется. Да и накопившиеся темя я так не разгребу, потому что поигрался за полгода с самими разными штуками. Можно попробовать в каком-то смысле интерактивный режим: я перечислю то, о чём мог бы написать, а вы сможете понажимать кнопки
:Р.Итак, за полгода я, помимо прочего, делал нижеперечисленное.
1
Наконец-то добрался до полноценного погружения в Common Lisp, прочитал пару книжек, сделал игрушечный проект в виде веб-приложения с БД, шаблонами и HTTP-запросами к API внешнего сервиса. Понравилось!
2
Переехал на Debian c Ubuntu (что само по себе скучно описывать), в связи с чем пришлось какое-то время посидеть на XFCE, который я постарался довести до некоторого подобия старого окружения вокруг i3.
В процессе чуть было не начал писать на Vala — вот про это уже можно написать! А толчком стало желание заиметь индикатор работы Yandex.Disk. Упаковал весь инструментарий в Docker-контейнер, собрал "Hello World" с интерфейсом на GTK3, начал радоваться. А потом оказалось, что нынче пиктограммы в system tray, это устаревшая идея и в GTK саму возможность делать таковые убрали!
В итоге обошёлся обвязыванием виджета XFCE Generic Monitor — это такая основа для самодельных индикаторов. Про это тоже может получиться запись.
3
В очередной раз глянул на Kotlin, после того, как наткнулся на видео о http4k. Это такая библиотечка, авторы которой открыли для себя, что web-сервер, это функция из request в response и преподносят теперь это их открытие, как новшество
:). Функциональщики посмеиваются, знакомые с WSGI питонисты — тоже.В результате знакомства сделал Kotlin-версию того же проекта, который делал на Common Lisp. Решать знакомую задачу новым способом — это вообще неплохой способ знакомиться с языками, как вы знаете. Тоже HTTP API client + SQLite в роли БД + http4k в роли сервера и вёрстка на DSL. Большие фреймворки трогать не хотел, поэтому выбрал библиотеки "по примерам в README". В итоге впечатления, что называется, смешанные.
4
Из того, что началось раньше, но продолжалось в эти полгода, ещё есть проект, который мы на пару с товарищем, реализуем в рамках серии видео-трансляций. Это игрушечный движок для текстовых Interactive Fiction, построенный на базе… Django (это такой большой Web-фреймворк для Python, если кто вдруг не знал). Выбран Django потому, что я его умею использовать, а тема IF выбрана потому, что такое на Django обычно не делают
:). Причём Web-приложение получившееся пока даже не предполагает использование несколькими пользователями, что тоже для Django не характерно. Зато используется админка, в которую я протащил Graphwiz для построения интерактивной карты игрового мира.Было ещё что-то более мелкое, буду вспоминать
:P.Если что, уточняю: я напишу про всё упомянутое, скорее всего. Кнопочки влияют на порядок следования будущих тем :)
Итак, Common Lisp. Знаю я про его существование с того момента, как вообще узнал про существование лиспов. Но непосредственно CL я до этой весны не трогал, будучи пот этом знаком с кучкой диалектов Scheme, с Clojure и, по очевидным причинам, с Emacs Lisp.
Как-то раз я даже ставил SBCL (о нём ниже), но дальше пары команд в REPL дело не пошло. Думается, что именно это самое знакомство с семейством в целом мне и мешало: "ну что я там не видел, те же скобки, только в профиль". Несколько фундаментальных книжек в списке на прочтение висят уже давно. Статейки же про CL я никогда не читал — зачем, если я всё равно не собирался начинать знакомство.
Но статью "A road to Common Lisp" я почему-то прочитал, "и заверте…". У автора получилось обратить внимание именно на интересные для меня свойства языка и экосистемы. И я решил таки погрузиться, более того — следуя указанному в статье пути. Установил SBCL — компилятор CL в native code, живой и активно развивающийся. Завёл REPL, подключил к Emacs с помощью sly. Настало время почитать книги.
Прочитал рекомендованную "Common Lisp: A Gentle Introduction to Symbolic Computation", прорешал задания. Книга старая (переиздавалась в 2013, но основной материал ещё в 80х написан), но мне очень понравилась! Было интересно представлять себе то, как разные вещи появлялись в языке и почему это было сделано: наконец-то я увидел, откуда растут ноги у eLisp'овских
Задачки из книги, это хорошо. Но реальные проекты, состоящие более чем из одного модуля, нужно как-то оформлять и вообще управлять загрузкой кода в образ. Дело в том, что модули в CL, это, условно, команды, который выполняются в REPL и изменяют состояние виртуальной машины. Даже менеджер пакетов quicklisp выполняется как скрипт в рамках сеанса работы виртуальной машины и модифицирует её состояние так, чтобы VM понимала концепцию этих самых пакетов. Нет никаких конфигурационных файлов, есть программки на Lisp, меняющие систему до тех пор, пока та не придёт в требуемое состояние. После чего обычно делают снимок образа, хранящего состояние машины, и помечают "при восстановлении вызвать такую-то функцию". И если к снимку прилинковать бинарник виртуальной машины, то получится самодостаточный исполняемый файл! Причём этот файл может нести на борту и REPL, к которому можно будет подключиться из редактора и пообщаться с живой системой!
Кроме quicklisp есть ещё концепция систем, которые описывают то, какие модули и в каком порядке должны загружаться. За это отвечает ASDF, средство для описания систем. ASDF обычно идёт в комплекте с конкретной реализацией CL, но умеет лишь работать с модулями, которые у вас в системе уже лежат в привычных местах вроде пользовательской директории со всеми lisp-проектами. quicklisp же умеет скачать пакеты из сети и подать код так, что ASDF не заметит разницы. Надо ли упоминать, что и ASDF, и quicklisp написаны на CL?
Последний момент стоит отметить особо: в сообществе CL большинство авторов хоть сколько-нибудь открытого кода крайне озабочено тем, чтобы их код работал на разных реализациях CL, а в идеале — на любых! И большая часть публично доступных пакетов не привязана к конкретной реализации или паре таковых. Можно считать, что если у вашей реализации есть ASDF, то и quicklisp будет работать, и любые, установленные им, пакеты. Да, можно встретить код, который использует свойства конкретных реализаций, чтобы работать более эффективно, но обычно этот же код может работать в "режиме совместимости". Лично мне было крайне интересно познакомиться с такой экосистемой!
Продолжение следует…
Как-то раз я даже ставил SBCL (о нём ниже), но дальше пары команд в REPL дело не пошло. Думается, что именно это самое знакомство с семейством в целом мне и мешало: "ну что я там не видел, те же скобки, только в профиль". Несколько фундаментальных книжек в списке на прочтение висят уже давно. Статейки же про CL я никогда не читал — зачем, если я всё равно не собирался начинать знакомство.
Но статью "A road to Common Lisp" я почему-то прочитал, "и заверте…". У автора получилось обратить внимание именно на интересные для меня свойства языка и экосистемы. И я решил таки погрузиться, более того — следуя указанному в статье пути. Установил SBCL — компилятор CL в native code, живой и активно развивающийся. Завёл REPL, подключил к Emacs с помощью sly. Настало время почитать книги.
Прочитал рекомендованную "Common Lisp: A Gentle Introduction to Symbolic Computation", прорешал задания. Книга старая (переиздавалась в 2013, но основной материал ещё в 80х написан), но мне очень понравилась! Было интересно представлять себе то, как разные вещи появлялись в языке и почему это было сделано: наконец-то я увидел, откуда растут ноги у eLisp'овских
prog1 и прочих setq.Задачки из книги, это хорошо. Но реальные проекты, состоящие более чем из одного модуля, нужно как-то оформлять и вообще управлять загрузкой кода в образ. Дело в том, что модули в CL, это, условно, команды, который выполняются в REPL и изменяют состояние виртуальной машины. Даже менеджер пакетов quicklisp выполняется как скрипт в рамках сеанса работы виртуальной машины и модифицирует её состояние так, чтобы VM понимала концепцию этих самых пакетов. Нет никаких конфигурационных файлов, есть программки на Lisp, меняющие систему до тех пор, пока та не придёт в требуемое состояние. После чего обычно делают снимок образа, хранящего состояние машины, и помечают "при восстановлении вызвать такую-то функцию". И если к снимку прилинковать бинарник виртуальной машины, то получится самодостаточный исполняемый файл! Причём этот файл может нести на борту и REPL, к которому можно будет подключиться из редактора и пообщаться с живой системой!
Кроме quicklisp есть ещё концепция систем, которые описывают то, какие модули и в каком порядке должны загружаться. За это отвечает ASDF, средство для описания систем. ASDF обычно идёт в комплекте с конкретной реализацией CL, но умеет лишь работать с модулями, которые у вас в системе уже лежат в привычных местах вроде пользовательской директории со всеми lisp-проектами. quicklisp же умеет скачать пакеты из сети и подать код так, что ASDF не заметит разницы. Надо ли упоминать, что и ASDF, и quicklisp написаны на CL?
Последний момент стоит отметить особо: в сообществе CL большинство авторов хоть сколько-нибудь открытого кода крайне озабочено тем, чтобы их код работал на разных реализациях CL, а в идеале — на любых! И большая часть публично доступных пакетов не привязана к конкретной реализации или паре таковых. Можно считать, что если у вашей реализации есть ASDF, то и quicklisp будет работать, и любые, установленные им, пакеты. Да, можно встретить код, который использует свойства конкретных реализаций, чтобы работать более эффективно, но обычно этот же код может работать в "режиме совместимости". Лично мне было крайне интересно познакомиться с такой экосистемой!
Продолжение следует…
Меж тем выложили запись моего доклада на SECON'21, в рамках которого я пугал людей примерами кода на Haskell, Elm, PureScript и Clojure применительно к frontend development. Я-то хотел сначала рассказать сильно более пространный доклад про разные фишечки в нескучных языках — про акторы, true-прототипное ООП в Io, ленивость хаскельную. Но программный комитет решил, что это будет слишком сурово и выдать нужно что-то более приземлённо-насущное и при этом расширяющее кругозор. В итоге аудитория была в лёгком шоке, но доклад оценила положительно
:)Лиспоприключения, часто вторая.
Книжку я прочитал, SBCL/ASDF/quicklisp завёл, собрал несколько программок в "самодостаточные exe-шники". А потом передо мной встал вопрос: а что бы такое написать сравнительно большое и разностороннее? Решил написать web-приложение вокруг PokeAPI — это такой сервис, предоставляющий самую разную информацию о контенте большей части игр из серии "Pokemon" в виде HTTP JSON API. Авторы PokeAPI призывают пользователей вести себя хорошо, что означает необходимость кэшировать всё и всегда. А это уже подразумевает работу с персистентным хранилищем, например, с SQLite.
Чтобы реализовать нарисованный в уме проект, мне нужно было выбрать:
- Web-фреймворк
- HTML-шаблонизатор
- HTTP-клиент
- парсер JSON
- коннектор к SQLite3
- генератор SQL-запросов
С первым пунктом всё более-менее очевидно: наиболее популярен hunchentoot, поэтому и я его взял. Приложение с его помощью выглядит как один или несколько так называемых acceptors, которые нужно создать и запустить на нужных портах. Запуск акцептора не блокирует текущий поток, то есть всё сделано для того, чтобы запускать акцепторы один раз и потом только модифицировать их состояние без останова — разумеется, через eval S-выражений по горячей клавише прямо не выходя из Emacs, ведь у нас REPL-driven development, как-никак!
Роуты навешиваются через макросы, модифицирующие акцептор. У меня роутинг был простой: либо список покемонов вывожу, если параметра "?name=" нет, либо карточку покемона с указанным именем, поэтому и роут у меня всего один
Выглядит разметка так:
Книжку я прочитал, SBCL/ASDF/quicklisp завёл, собрал несколько программок в "самодостаточные exe-шники". А потом передо мной встал вопрос: а что бы такое написать сравнительно большое и разностороннее? Решил написать web-приложение вокруг PokeAPI — это такой сервис, предоставляющий самую разную информацию о контенте большей части игр из серии "Pokemon" в виде HTTP JSON API. Авторы PokeAPI призывают пользователей вести себя хорошо, что означает необходимость кэшировать всё и всегда. А это уже подразумевает работу с персистентным хранилищем, например, с SQLite.
Чтобы реализовать нарисованный в уме проект, мне нужно было выбрать:
- Web-фреймворк
- HTML-шаблонизатор
- HTTP-клиент
- парсер JSON
- коннектор к SQLite3
- генератор SQL-запросов
С первым пунктом всё более-менее очевидно: наиболее популярен hunchentoot, поэтому и я его взял. Приложение с его помощью выглядит как один или несколько так называемых acceptors, которые нужно создать и запустить на нужных портах. Запуск акцептора не блокирует текущий поток, то есть всё сделано для того, чтобы запускать акцепторы один раз и потом только модифицировать их состояние без останова — разумеется, через eval S-выражений по горячей клавише прямо не выходя из Emacs, ведь у нас REPL-driven development, как-никак!
Роуты навешиваются через макросы, модифицирующие акцептор. У меня роутинг был простой: либо список покемонов вывожу, если параметра "?name=" нет, либо карточку покемона с указанным именем, поэтому и роут у меня всего один
:)
HTML в лиспах генерят из S-выражений, но разные библиотеки делают это по-разному. Я попробовал несколько, в итоге остановился на spinneret, которая использует списки с keywords. Последнее было несколько досадно, потому что я-то хотел символы писать! Но зато в остальном библиотека приятная и не заставляет использовать какие-то сложные конструкции для подстановки одних фрагментов разметки в другие: вы одни макросы вызываете в теле других, дерево из списков растёт, но не "выполняется" до самого последнего момента — удобно, композируемо!Выглядит разметка так:
(:headПродолжение следует...
(:meta :name "viewport"
:content "width=device-width, initial-scale=1")
(:link :rel "stylesheet"
:href "https://unpkg.com/blocks.css/dist/blocks.min.css"
:media "screen")
(:title ,title " (CL-Pokedex)"))
Лиспоприключения, часть третья.
Работу с API я решил оформить как набор обычных функций, которые бы прятали от внешнего наблюдателя само наличие кэширования и отдавали бы уже разобранные данные вне зависимости от того, откуда те были взяты — из кэша или путём хождения в сеть.
Кэширование сделал максимально прямолинейно: приложение на старте открывает файл SQLite3 и запоминает подключение в глобальной переменной — да, в лиспах так делают! — и любые запросы к API используют URI в качестве ключа и извлекают blob с ответом, если запрос с этим URI уже когда-то делался. Никакой инвалидации или обновления, на всю базу одна таблица в два столбца! Если кэшированного результата нет, то делается API-запрос и результат в сыром виде кладётся в БД. Да, приходится декодировать JSON каждый раз, но зато никаких миграций ибо схема никогда не меняется!
К базе подключаться я решил силами cl-dbi, поскольку это, опять же, достаточно популярный вариант. SQL эта библиотека не генерирует, есть только подстановка параметров. Но тут я поленился ходить ещё и за полновесным генератором запросов и написал все четыре руками: создание таблицы, добавление индекса, один INSERT и один SELECT. База, как я выше сказал, подключена постоянно, но SQLite вполне неплох с точки зрения атомарности записи на диск. Дёшево и сердито!
На роль HTTP-клиента был выбран dexador в связке с quri в качестве генератора URI с параметрами. Тут даже и сказать нечего: оно просто работает. Вот выбрать способ работы с JSON было сложнее: тут люди целые трактаты пишут вокруг сравнения разных кодеков — отличная демонстрация того самого "проклятия лиспа"! Я не стал погружаться слишком глубоко и просто взял jsown: этот не пытается декодировать в пользовательские структуры и типы, а вместо этого даёт доступ к самому AST. Мне как раз и нужны-то были из каждого JSON одно-два поля, поэтому я с AST и работал, слегка подсахарив обход дерева парочкой макросов.
PokeAPI рассчитан на активное использование пагинации, помимо кэширования. И пагинация включена всегда: если вы не укажете limit в запросе, получите первые цать. Вот только мне именно с пагинацией работать очень не хотелось. Поэтому я сделал так: запрашиваю первую страницу с
Вот, в сущности, и все компоненты системы, которые я в итоге склеил, обернул "просто работающий" blocks.css и докеризовал. В целом процесс разработки мне понравился, хоть мне и пришлось местами поперебирать несколько библиотек, прежде чем я находил удобную лично для меня. Были интересные приключения с докеризацией готового приложения, но об этом я как-нибудь напишу отдельно. А пока — спасибо за внимание!
На код поделки можно посмотреть тут.
Работу с API я решил оформить как набор обычных функций, которые бы прятали от внешнего наблюдателя само наличие кэширования и отдавали бы уже разобранные данные вне зависимости от того, откуда те были взяты — из кэша или путём хождения в сеть.
Кэширование сделал максимально прямолинейно: приложение на старте открывает файл SQLite3 и запоминает подключение в глобальной переменной — да, в лиспах так делают! — и любые запросы к API используют URI в качестве ключа и извлекают blob с ответом, если запрос с этим URI уже когда-то делался. Никакой инвалидации или обновления, на всю базу одна таблица в два столбца! Если кэшированного результата нет, то делается API-запрос и результат в сыром виде кладётся в БД. Да, приходится декодировать JSON каждый раз, но зато никаких миграций ибо схема никогда не меняется!
К базе подключаться я решил силами cl-dbi, поскольку это, опять же, достаточно популярный вариант. SQL эта библиотека не генерирует, есть только подстановка параметров. Но тут я поленился ходить ещё и за полновесным генератором запросов и написал все четыре руками: создание таблицы, добавление индекса, один INSERT и один SELECT. База, как я выше сказал, подключена постоянно, но SQLite вполне неплох с точки зрения атомарности записи на диск. Дёшево и сердито!
На роль HTTP-клиента был выбран dexador в связке с quri в качестве генератора URI с параметрами. Тут даже и сказать нечего: оно просто работает. Вот выбрать способ работы с JSON было сложнее: тут люди целые трактаты пишут вокруг сравнения разных кодеков — отличная демонстрация того самого "проклятия лиспа"! Я не стал погружаться слишком глубоко и просто взял jsown: этот не пытается декодировать в пользовательские структуры и типы, а вместо этого даёт доступ к самому AST. Мне как раз и нужны-то были из каждого JSON одно-два поля, поэтому я с AST и работал, слегка подсахарив обход дерева парочкой макросов.
PokeAPI рассчитан на активное использование пагинации, помимо кэширования. И пагинация включена всегда: если вы не укажете limit в запросе, получите первые цать. Вот только мне именно с пагинацией работать очень не хотелось. Поэтому я сделал так: запрашиваю первую страницу с
limit=1, в ответе нахожу полное количество, потом делаю второй запрос с limit=count. Да, не очень-то вежливо. Но в PokeAPI просто нет ресурсов, данных у которых было бы настолько много, чтобы однократный (кэширование!) запрос "всего, что есть" настолько бы напряг держателей сервиса.Вот, в сущности, и все компоненты системы, которые я в итоге склеил, обернул "просто работающий" blocks.css и докеризовал. В целом процесс разработки мне понравился, хоть мне и пришлось местами поперебирать несколько библиотек, прежде чем я находил удобную лично для меня. Были интересные приключения с докеризацией готового приложения, но об этом я как-нибудь напишу отдельно. А пока — спасибо за внимание!
На код поделки можно посмотреть тут.
Рубрика "ссылка, которую я посчитал интересной сегодня" плюс немного "текста для контекста"
Взять тот же Dr.Racket: да, это просто окно с меню и панелью инструментов, но вы только на сам редактор посмотрите — тут вам и встраивание rich content, и всякие стрелочки поверх текста. И, представьте себе, это самое редакторостроение авторы не оставили в рамках одного проекта, а оформили в виде, не побоюсь этого слова, фреймворка! Можно даже drag'n'drop редакторы с графическими элементами делать, а не только текстовые, пусть даже и rich text.
Вот чего Racket не хватает, так это хорошей витрины готовых приложений. Автор этой статьи тоже сетует на нехватку хороших примеров и показывает, как выглядит его приложение. А выглядит оно очень даже прилично и сложно: тут и карта местности есть в виде виджета, и всяческие диаграммы, и календарь с карточками в ячейках. Пройдите и посмотрите картинки. Далеко не каждый доморощенный проект настолько глубоко проработан в том числе и в области визуализации, особенно — проект на непопулярном языке! Да, автор, как говорят, собаку съел на визуализациях в Racket и те же редакторы с drag'n'drop в игры превращал. Но приложение, тем не менее, хорошо показывает, что инструментарий вас не слишком ограничивает и всё в ваших руках!
Несколько картинок из статьи прикреплю.
:)
У #racket есть свой набор инструментов для создания GUI. Причём интерфейсы могут быть достаточно сложными.Взять тот же Dr.Racket: да, это просто окно с меню и панелью инструментов, но вы только на сам редактор посмотрите — тут вам и встраивание rich content, и всякие стрелочки поверх текста. И, представьте себе, это самое редакторостроение авторы не оставили в рамках одного проекта, а оформили в виде, не побоюсь этого слова, фреймворка! Можно даже drag'n'drop редакторы с графическими элементами делать, а не только текстовые, пусть даже и rich text.
Вот чего Racket не хватает, так это хорошей витрины готовых приложений. Автор этой статьи тоже сетует на нехватку хороших примеров и показывает, как выглядит его приложение. А выглядит оно очень даже прилично и сложно: тут и карта местности есть в виде виджета, и всяческие диаграммы, и календарь с карточками в ячейках. Пройдите и посмотрите картинки. Далеко не каждый доморощенный проект настолько глубоко проработан в том числе и в области визуализации, особенно — проект на непопулярном языке! Да, автор, как говорят, собаку съел на визуализациях в Racket и те же редакторы с drag'n'drop в игры превращал. Но приложение, тем не менее, хорошо показывает, что инструментарий вас не слишком ограничивает и всё в ваших руках!
Несколько картинок из статьи прикреплю.
Теперь расскажу про то, как я делал движок для Interactive Fiction на Django.
IF — это такой вид текстовых игр с нелинейным сюжетом, управляемым активными действиями пользователя, который вводит команды, использует меню, кидает кубики или просто переходит по ссылкам. От прочих текстовых игр IF отличаются соотношением интерактива и нарратива: второго в IF обычно сильно больше. Подробнее про явление можно почитать в Wikipedia.
Очень часто код для IF игр или не пишут вообще, или скриптуют отдельные нестандартные взаимодействия с пользователем, а большую часть работы берёт на себя некий движок. Если игра подразумевает анализ вводимого игроком текста, то движок может быть достаточно сложным. А вот "гипер-книжки" на основе текста со ссылками вполне могут быть просто Web-приложениями.
Последний вариант и был мною выбран в качестве идеи для проекта, который мог бы показать Django с разных сторон. Дело в том, что я сделал для Hexlet несколько обучающих курсов по Python в целом и по Django в частности. Вот и подобная демонстрация фреймворка в бою показалась стоящей затеей. Которая воплотилась в серию видео и сопутствующий репозиторий.
Брать какой-то движок вроде INSTEAD или TADS в качестве образца я не стал и решил делать всё с нуля, в процессе изобретая новые функции. Но часть свойств будущего движка всё же была задумана заранее:
- Интерфейс администратора Django ("админка") в роли редактора
- Интенсивное использование ORM
- SQLite в роли БД
SQLite был выбран потому, что проект должен был стать чем-то вроде desktop application, пусть и с web-интерфейсом. Даже наличие нескольких пользователей не предполагалось, поэтому я и воткнул первым делом такую middleware, чтобы единственный пользователь admin всегда оставался в системе. Всё это не очень похоже на то, как Django принято готовить, но так ведь интереснее!
Начался проект с того, что были описаны "книги", "страницы" в них и "переходы между страницами". Всё максимально просто и отлично ложится как на админку, так и на Django ORM. Для каждой книги было решено хранить состояние, в котором находится читающий — по одной "закладке" на книгу, для простоты.
Затем был добавлен инвентарь, в который можно было подбирать предметы, на которые могли быть завязаны переходы между страницами: ключи и двери, реки и плоты для их преодоления, и всё тому подобное. На таком базисе уже можно строить игры с не самыми простыми правилами!
В рамках очередного похода в админку за правками контента возникла идея: а не добавить ли в интерфейс управления книгой карту мест и переходов? И я, конечно же, вспомнил Graphviz — "дёшево и сердито", как говорится. Так появилась интерактивная (конечно же!) карта книги со всплывающими подсказками и ссылками для перехода к соответствующим страницам админки — удобно!
Продолжение следует…
IF — это такой вид текстовых игр с нелинейным сюжетом, управляемым активными действиями пользователя, который вводит команды, использует меню, кидает кубики или просто переходит по ссылкам. От прочих текстовых игр IF отличаются соотношением интерактива и нарратива: второго в IF обычно сильно больше. Подробнее про явление можно почитать в Wikipedia.
Очень часто код для IF игр или не пишут вообще, или скриптуют отдельные нестандартные взаимодействия с пользователем, а большую часть работы берёт на себя некий движок. Если игра подразумевает анализ вводимого игроком текста, то движок может быть достаточно сложным. А вот "гипер-книжки" на основе текста со ссылками вполне могут быть просто Web-приложениями.
Последний вариант и был мною выбран в качестве идеи для проекта, который мог бы показать Django с разных сторон. Дело в том, что я сделал для Hexlet несколько обучающих курсов по Python в целом и по Django в частности. Вот и подобная демонстрация фреймворка в бою показалась стоящей затеей. Которая воплотилась в серию видео и сопутствующий репозиторий.
Брать какой-то движок вроде INSTEAD или TADS в качестве образца я не стал и решил делать всё с нуля, в процессе изобретая новые функции. Но часть свойств будущего движка всё же была задумана заранее:
- Интерфейс администратора Django ("админка") в роли редактора
- Интенсивное использование ORM
- SQLite в роли БД
SQLite был выбран потому, что проект должен был стать чем-то вроде desktop application, пусть и с web-интерфейсом. Даже наличие нескольких пользователей не предполагалось, поэтому я и воткнул первым делом такую middleware, чтобы единственный пользователь admin всегда оставался в системе. Всё это не очень похоже на то, как Django принято готовить, но так ведь интереснее!
;)Начался проект с того, что были описаны "книги", "страницы" в них и "переходы между страницами". Всё максимально просто и отлично ложится как на админку, так и на Django ORM. Для каждой книги было решено хранить состояние, в котором находится читающий — по одной "закладке" на книгу, для простоты.
Затем был добавлен инвентарь, в который можно было подбирать предметы, на которые могли быть завязаны переходы между страницами: ключи и двери, реки и плоты для их преодоления, и всё тому подобное. На таком базисе уже можно строить игры с не самыми простыми правилами!
В рамках очередного похода в админку за правками контента возникла идея: а не добавить ли в интерфейс управления книгой карту мест и переходов? И я, конечно же, вспомнил Graphviz — "дёшево и сердито", как говорится. Так появилась интерактивная (конечно же!) карта книги со всплывающими подсказками и ссылками для перехода к соответствующим страницам админки — удобно!
Продолжение следует…
Продолжение истории про движок для историй.
Предметы "в ранних версиях" про дублировались в момент поднятия и при показе описания локации не показывались при наличии копий в инвентаре. Это позволило имитировать "подбирание" без добавления какого-либо состояния самим локациям. Зато при наличии предметов стало возможно добавить оные в виде зависимостей к нужным переходам. Так появились переходы у предусловием. Вообще, подобные связи вида "предмет—страница" или "предмет—инвентарь" очень легко оформлять в виде соотношений "многие ко многим" и особенно быстро это делается силами Django ORM!
При наличии хоть каких-то условных переходов можно реализовывать изрядную такую нелинейность. Пример: проход к боссу закрытый с помощью трёх ключей, каждый из которых получаешь в отдельном квесте, но порядок выполнения квестов не влияет на результат. Или можно метроидванию смоделировать, описав приобретаемые навыки персонажа как предметы, и требовать "умения летать" в отдельных регионах мира!
А потом мы решили (я видео пишу с напарником, он мой код комментирует, всем весело) сделать механизм сохранений. И вот тут стало сложновато
Подбор предметов намекает на выбрасывание оных, а это уже отслеживание того, где и что было выброшено. Но сделали! И, разумеется, учли в подсистеме сохранений. А ещё добавили ведение заметок, сквозное для всех сохранений (не изменяется при загрузке старых). Заметки можно прикреплять к локациям или просто "пришпиливать" чтобы всегда были на виду — всё это влияет на то, что и когда отображается.
Пока это всё, что уже реализовано. Но есть и планы на будущее.
Пока выброшенные предметы нельзя использовать в переходах, но если такое сделать, то это уже будет заготовка для "применения", а потом можно уже будет продумать "трату" предмета в процессе "применения к месту": "потратил доски, сделал мост" и тому подобное.
Есть ещё мысль помимо "требования наличия" предметов добавить переходам возможность давать игроку предметы и/или отбирать их. Во-первых, это позволит выкинуть отдельные механизмы подбора и выбрасывания. А во-вторых открывает целое море возможностей!
Вы только представьте локацию-магазин, описанную в виде набора переходов обратно в этот же магазин, но с тратой денег и получением покупок? Можно даже реализовать "продажи не для всех", просто требуя наличия предмета-разрешения или предмета-билета (с отбиранием оного на выходе). Целая система экономики, не поддерживаемая отдельным кодом, а просто "сделанная в редакторе"!
Предметы "в ранних версиях" про дублировались в момент поднятия и при показе описания локации не показывались при наличии копий в инвентаре. Это позволило имитировать "подбирание" без добавления какого-либо состояния самим локациям. Зато при наличии предметов стало возможно добавить оные в виде зависимостей к нужным переходам. Так появились переходы у предусловием. Вообще, подобные связи вида "предмет—страница" или "предмет—инвентарь" очень легко оформлять в виде соотношений "многие ко многим" и особенно быстро это делается силами Django ORM!
При наличии хоть каких-то условных переходов можно реализовывать изрядную такую нелинейность. Пример: проход к боссу закрытый с помощью трёх ключей, каждый из которых получаешь в отдельном квесте, но порядок выполнения квестов не влияет на результат. Или можно метроидванию смоделировать, описав приобретаемые навыки персонажа как предметы, и требовать "умения летать" в отдельных регионах мира!
А потом мы решили (я видео пишу с напарником, он мой код комментирует, всем весело) сделать механизм сохранений. И вот тут стало сложновато
;) Прогресс прохождения конкретной игры конкретным пользователем было решено не дублировать, вместо этого я придумал создавать завязанные на прогресс сохранения, которые бы хранили снимки прогресса на момент сохранения. Это вылилось в жонглирование связями в стиле "привязать все предметы, привязанные к прогрессу, к сохранению, а от прогресса отвязать". Хорошо хоть, что транзакции есть! В какой-то момент получился пресловутый CRUD: список сохранений, сортировка по времени последнего изменения, типичные Django-задачи :)Подбор предметов намекает на выбрасывание оных, а это уже отслеживание того, где и что было выброшено. Но сделали! И, разумеется, учли в подсистеме сохранений. А ещё добавили ведение заметок, сквозное для всех сохранений (не изменяется при загрузке старых). Заметки можно прикреплять к локациям или просто "пришпиливать" чтобы всегда были на виду — всё это влияет на то, что и когда отображается.
Пока это всё, что уже реализовано. Но есть и планы на будущее.
Пока выброшенные предметы нельзя использовать в переходах, но если такое сделать, то это уже будет заготовка для "применения", а потом можно уже будет продумать "трату" предмета в процессе "применения к месту": "потратил доски, сделал мост" и тому подобное.
Есть ещё мысль помимо "требования наличия" предметов добавить переходам возможность давать игроку предметы и/или отбирать их. Во-первых, это позволит выкинуть отдельные механизмы подбора и выбрасывания. А во-вторых открывает целое море возможностей!
Вы только представьте локацию-магазин, описанную в виде набора переходов обратно в этот же магазин, но с тратой денег и получением покупок? Можно даже реализовать "продажи не для всех", просто требуя наличия предмета-разрешения или предмета-билета (с отбиранием оного на выходе). Целая система экономики, не поддерживаемая отдельным кодом, а просто "сделанная в редакторе"!
Рубрика "позабавило в интернете".
Тут для Nintendo Switch выпустили карандаш для рисования с соответствующим ПО. Но в Switch стоит обычный емкостной сенсор, никакого специального слоя от Wacom нет. Можно лишь "эмулировать палец", чем карандаш упомянутый и занимается: не зря у карандаша на конце "присоска" площадью с пол-пальца.
Вот только как передать силу нажатия (для рисования это важно)? И зачем карандашу провод, второй конец которого подключается к консоли через разъём для гарнитуры? Пока это тайна! Но я уже пофантазировал на эту тему
Косвенно моя догадка подтверждается тем, что карандаш заявлен под названием "sonarpen" и работает "with no batteries or bluetooth required"! То самое "дешёвое и сердитое" решение, какие я люблю!
И пусть карандаш подключается проводом, что выглядит не очень современно, да и точности особой не даст из-за технологии touch-сенсора, но японцы любят всякое эдакое. Вспомните хоть радар для рыболовов и швейно-вышивальную машинку, в роли аксессуаров для GameBoy
Тут для Nintendo Switch выпустили карандаш для рисования с соответствующим ПО. Но в Switch стоит обычный емкостной сенсор, никакого специального слоя от Wacom нет. Можно лишь "эмулировать палец", чем карандаш упомянутый и занимается: не зря у карандаша на конце "присоска" площадью с пол-пальца.
Вот только как передать силу нажатия (для рисования это важно)? И зачем карандашу провод, второй конец которого подключается к консоли через разъём для гарнитуры? Пока это тайна! Но я уже пофантазировал на эту тему
:)
Моя версия такая: программа для рисования выдаёт некий звук на выход для наушников, а карандаш измеряет силу нажатия пьезоэлементом или чем-то подобным и возвращает в виде того же звука переменной громкости (амплитудная модуляция!) в микрофонный вход консоли!Косвенно моя догадка подтверждается тем, что карандаш заявлен под названием "sonarpen" и работает "with no batteries or bluetooth required"! То самое "дешёвое и сердитое" решение, какие я люблю!
И пусть карандаш подключается проводом, что выглядит не очень современно, да и точности особой не даст из-за технологии touch-сенсора, но японцы любят всякое эдакое. Вспомните хоть радар для рыболовов и швейно-вышивальную машинку, в роли аксессуаров для GameBoy
%)Ха, "так и вышло"!
> "It is a half-meter cable that plugs into the audio jack, and technically it pretends to be a headset. So, in Colors, it actually plays a tone through the audio. A tone of about 8000 hertz goes through the cable to the tip of the pen. There is a resistor in there as well that dampens the tone depending on the amount of pressure that is applied to the tip of the pen. This vibration goes back to the Switch through the internal microphone of the pen, which measures the volume of the sound. That calculates the pressure used on the screen. So the touch is recorded by the screen as normal, but the pressure goes through that thing. It is an ingenious idea." (источник)
Ох уж эти рекламные словечки вроде "ingenious"
> "It is a half-meter cable that plugs into the audio jack, and technically it pretends to be a headset. So, in Colors, it actually plays a tone through the audio. A tone of about 8000 hertz goes through the cable to the tip of the pen. There is a resistor in there as well that dampens the tone depending on the amount of pressure that is applied to the tip of the pen. This vibration goes back to the Switch through the internal microphone of the pen, which measures the volume of the sound. That calculates the pressure used on the screen. So the touch is recorded by the screen as normal, but the pressure goes through that thing. It is an ingenious idea." (источник)
Ох уж эти рекламные словечки вроде "ingenious"
:РРубрика "проект выходного дня"
Я имею определённый опыт рисования процедурной графики на комбинаторах. Даже стримил это дело, и сюда тоже писал. Как-то раз я сидел в Haskell REPL и писал комбинаторы, чтобы ASCII-Art композить в таком же стиле: отражения, масштабирование, стыковка "в ряд" и "в столбик". Результат получился небезынтересный, отложил в ящик, чтобы когда-нибудь сделать маленький DSL, который можно было бы вводить и сразу видеть результат в реальном времени. Предполагалось что-то вроде правил для L-систем, если вдруг кто знаком. Вот только то, что попадает в мой ящик, очень редко из него достаётся
На днях копался в закромах и нашёл ту самую заготовку. Решил, что хочу доделать, но копаться с TUI в Haskell что-то не очень хочется, поэтому волевым усилием переписал всё на Elm. Вот результат: <https://github.com/astynax/elm-charart>
Буквально сегодня добавил процедурное раскрашивание. Которое можно использовать напрямую для рисования, если не хочется из кусочков композить: вы просто описываете функцию "координаты -> символ+цвет". Получается что-то вроде tixy.land за вычетом анимации (её, при желании, тоже можно добавить!).
Визуализация есть, теперь остаётся сделать уже язык, которым можно было бы вводить правила построения картинок. Пока делал демки, подумал, что тут будет хорошо смотреться конкатенативный язык (ага, Forth!) с примесью ФВП.
А потом можно будет организовать выгрузку правил в виде большой строки, которую можно будет скормить голому движку (через flags) и получить встраиваемый элемент для сайтиков в стиле старой школы. Я такое уже делал в меньших масштабах, когда захотелось "ASCII-дождик" воткнуть на одну страничку.
Я имею определённый опыт рисования процедурной графики на комбинаторах. Даже стримил это дело, и сюда тоже писал. Как-то раз я сидел в Haskell REPL и писал комбинаторы, чтобы ASCII-Art композить в таком же стиле: отражения, масштабирование, стыковка "в ряд" и "в столбик". Результат получился небезынтересный, отложил в ящик, чтобы когда-нибудь сделать маленький DSL, который можно было бы вводить и сразу видеть результат в реальном времени. Предполагалось что-то вроде правил для L-систем, если вдруг кто знаком. Вот только то, что попадает в мой ящик, очень редко из него достаётся
:(На днях копался в закромах и нашёл ту самую заготовку. Решил, что хочу доделать, но копаться с TUI в Haskell что-то не очень хочется, поэтому волевым усилием переписал всё на Elm. Вот результат: <https://github.com/astynax/elm-charart>
Буквально сегодня добавил процедурное раскрашивание. Которое можно использовать напрямую для рисования, если не хочется из кусочков композить: вы просто описываете функцию "координаты -> символ+цвет". Получается что-то вроде tixy.land за вычетом анимации (её, при желании, тоже можно добавить!).
Визуализация есть, теперь остаётся сделать уже язык, которым можно было бы вводить правила построения картинок. Пока делал демки, подумал, что тут будет хорошо смотреться конкатенативный язык (ага, Forth!) с примесью ФВП.
А потом можно будет организовать выгрузку правил в виде большой строки, которую можно будет скормить голому движку (через flags) и получить встраиваемый элемент для сайтиков в стиле старой школы. Я такое уже делал в меньших масштабах, когда захотелось "ASCII-дождик" воткнуть на одну страничку.
👍1
Что-то заработался, едва хватило сил успеть для Hacktoberfest накоммитить в опенсорсы. Но вы меня не теряйте, что-то да выдам в ближайшее время: пул тем есть, нужно слегка разгрести.
Сейчас по работе пишу на Kotlin, так уж вышло. И всё какие-то eDSL. А в такое если погрузишься, начинаешь везде видеть всё те же DSL :)
На днях увидел примерчик BASIC-подобного языка прямо внутри кода на Scala. Подумал, "а ведь на Kotlin оно делается просто"! Вот и получилось такое:
https://gist.github.com/astynax/76b09a79f2cf31f4ee55d384f3bc8f7a
Не идеально и не похоже на настоящий BASIC на 100%, но тут уже накладываются ограничения буковки "e" в "eDSL". Впрочем, у меня получилось более близко, чем в скаловском оригинале.
Интерпретатор сам по себе "тривиален", так что я только "рыбу" накидал, чтобы код на DSL проходил проверку типов, если вдруг кто захочет доделать - милости прошу! Заодно сможете проверить, работает ли мой пример (на настоящем BASIC я проверял – с точностью до синтаксиса, код рабочий!).
Сейчас по работе пишу на Kotlin, так уж вышло. И всё какие-то eDSL. А в такое если погрузишься, начинаешь везде видеть всё те же DSL :)
На днях увидел примерчик BASIC-подобного языка прямо внутри кода на Scala. Подумал, "а ведь на Kotlin оно делается просто"! Вот и получилось такое:
https://gist.github.com/astynax/76b09a79f2cf31f4ee55d384f3bc8f7a
Не идеально и не похоже на настоящий BASIC на 100%, но тут уже накладываются ограничения буковки "e" в "eDSL". Впрочем, у меня получилось более близко, чем в скаловском оригинале.
Интерпретатор сам по себе "тривиален", так что я только "рыбу" накидал, чтобы код на DSL проходил проверку типов, если вдруг кто захочет доделать - милости прошу! Заодно сможете проверить, работает ли мой пример (на настоящем BASIC я проверял – с точностью до синтаксиса, код рабочий!).
Интересно получается, что я только-только начал на Kotlin писать как на основном языке и уже упёрся в нехватку "множественных receivers" в языке. К счастью, этот же вопрос уже давно заботит многих и подвижки в нужную сторону есть, так что я не теряю надежду. Вот уж тогда я развернусь!
В чём же суть? В возможности при объявлении extension method указать не только объект, к которому вызов метода будет адресован, но и объект, определяющий контекст — своего рода "область видимости", в которой метод будет доступен.
Для людей со стороны расшифрую.
Kotlin позволяет добавить метод к имеющемуся типу (это и есть тот самый receiver, упомянутый выше) постфактум:
А вот те самые multiple receivers разрешат добавить расширение для одного типа в контексте другого — также постфактум, не владея кодом ни того класса, ни другого! Это чем-то похоже на инстенциирование класса типов в Haskell — вы добавляете поведение к типу, не изменяя ни объявление поведения, ни объявление типа.
Не то чтобы эта языковая возможность нужна всем и каждому, но на концепцию языка она ложится хорошо и пользу авторам библиотек она точно принесёт. Так что ждём'c :)
P.S. в той части Fleet (новая IDE, которую я пилю в JetBrains — да, теперь мне можно про это говорить) у меня как раз такие DSL имеются, и они ощутимо выиграют от multiple receivers :Р
P.P.S. Кому-то вся эта история может напомнить язык Io, в котором посылка сообщений сделана с передачей не только получателя, но и автора. И пользуются этой возможностей примерно так же — делают контекстно-зависимые расширения для языка. Почитать об этом можно в "Семь языков за семь недель" — крайне советую!
В чём же суть? В возможности при объявлении extension method указать не только объект, к которому вызов метода будет адресован, но и объект, определяющий контекст — своего рода "область видимости", в которой метод будет доступен.
Для людей со стороны расшифрую.
Kotlin позволяет добавить метод к имеющемуся типу (это и есть тот самый receiver, упомянутый выше) постфактум:
fun String.repeatTimes(n: Int) = ...
"foo" repeatTimes 5 // здесь скобки и точку можно опустить
Если объявить такую функцию-расширение внутри тела класса, то использовать это расширение можно будет только в методах этого же класса:class Context {
operator fun String.invoke() { // перегружаем вызов в роли функции
...
}
fun buzz() {
"a"() // тут это можно!
"b"()
}
}
Сама по себе такая локализация интересна постольку-поскольку. Но в Kotlin есть ещё "лямбды последним аргументом", которые позволяют функцию видаfun loop(n: Int, body: () -> Unit) {...}
вызывать с выносом последнего аргумента за скобки:loop(5) {
...
}
// вместо loop(5, {...})
Так вот, такую лямбду можно описать с привязкой к контексту. Тогда в области видимости функции будет присутствовать неявный this, ссылающийся на объект контекста. Это позволяет использовать те самые локальные расширения в теле этой лямбды!fun inContext(body: Context.() -> Unit) { ... }
inContext {
"a"()
"b"()
}
Применив капельку фантазии и упомянутые приёмы, легко сделать, скажем, роутер для вашего micro-framework:routes {
"/" { // строка вызывается с лямбдой в роли аргумента
GET {
+"Hello, " // перегрузка унарного плюса для строк,
+"World!" // который для них обычно не реализован
}
}
"login" {
GET { ... }
POST { ... }
}
}
У данного подхода есть недостаток: нужно владеть кодом класса контекста, чтобы мочь добавить ограниченные по видимости расширения для других типов. В какой-нибудь stringBuilder добавить своих "глаголов" не получится — это final класс, его не расширить и не унаследовать.А вот те самые multiple receivers разрешат добавить расширение для одного типа в контексте другого — также постфактум, не владея кодом ни того класса, ни другого! Это чем-то похоже на инстенциирование класса типов в Haskell — вы добавляете поведение к типу, не изменяя ни объявление поведения, ни объявление типа.
Не то чтобы эта языковая возможность нужна всем и каждому, но на концепцию языка она ложится хорошо и пользу авторам библиотек она точно принесёт. Так что ждём'c :)
P.S. в той части Fleet (новая IDE, которую я пилю в JetBrains — да, теперь мне можно про это говорить) у меня как раз такие DSL имеются, и они ощутимо выиграют от multiple receivers :Р
P.P.S. Кому-то вся эта история может напомнить язык Io, в котором посылка сообщений сделана с передачей не только получателя, но и автора. И пользуются этой возможностей примерно так же — делают контекстно-зависимые расширения для языка. Почитать об этом можно в "Семь языков за семь недель" — крайне советую!
👍2
Дополнение: нет, multiple receiveres, это не "тайпклассы для бедных" и не "как типажи в Rust" (ох уж эти кулики и их желание похвалить своё болото через насмешки над чужими!).
Языки бывают разные, с разными свойствами и подходами к решению задач. Одного универсального решения для каждого класса задач нет и не предвидится. И тем более не будет языка, который бы любую задачу решал строго наиболее подходящим для всех и каждого способом.
Расширяйте кругозор, мыслите шире, будьте открыты к новому или просто иному!
Языки бывают разные, с разными свойствами и подходами к решению задач. Одного универсального решения для каждого класса задач нет и не предвидится. И тем более не будет языка, который бы любую задачу решал строго наиболее подходящим для всех и каждого способом.
Расширяйте кругозор, мыслите шире, будьте открыты к новому или просто иному!
Ух, опять большая пауза вышла. Буду писать про то, что сделал буквально вот прямо сейчас, а то так и не соберусь…
Время от времени приходится смотреть перевод на русский или расшифровку того или иного английского слова. Проще говоря, мне нужен словарь.
В xfce есть программка-словарь в виде диалогового окна. Вот только сценарий "скопировал слово, переключился на окно словаря, вставил, пролистал выдачу в поисках нужного, переключился обратно к тексту" слишком уж длинноват. Мне же хочется по сочетанию клавиш видеть перевод во всплывающем окне. Решил это желание запрограммировать.
Словарь из xfce использует протокол DICT — отдельный сетевой протокол для запроса словарных карточек. Я с этим протоколом раньше сталкивался, когда использовал в консоли программу
Помимо
- Rofi для показа экранного меню
- Zenity для запроса ввода
-
Получился скрипт vortaro.py ("vortaro" — это "словарь" на Эсперанто). Да-да, ужас-ужас, питонолапша. Я знаю. Зато я решил задачу быстро! Ибо plumbum сильно упрощает написание сценариев, склеивающих воедино пачку внешних программ. Хотя бы на bash писать не пришлось. Может быть потом перепишу на Haskell и turtle.
Сейчас скрипт смотрит в primary selection слово, пробует поискать его в первом словаре из списка, затем во втором и так далее. Если карточка найдена, то текст показывается во всплывающем сообщении. В том случае, когда конкретного слова в словаре не оказывается, но есть похожие на него слова, то сначала показывается меню, позволяющее выбрать одно из таких слов. Довольно таки специфическое поведение, готовую программу я бы скорее всего не нашёл. А тут полчаса и готово!
Время от времени приходится смотреть перевод на русский или расшифровку того или иного английского слова. Проще говоря, мне нужен словарь.
В xfce есть программка-словарь в виде диалогового окна. Вот только сценарий "скопировал слово, переключился на окно словаря, вставил, пролистал выдачу в поисках нужного, переключился обратно к тексту" слишком уж длинноват. Мне же хочется по сочетанию клавиш видеть перевод во всплывающем окне. Решил это желание запрограммировать.
Словарь из xfce использует протокол DICT — отдельный сетевой протокол для запроса словарных карточек. Я с этим протоколом раньше сталкивался, когда использовал в консоли программу
dict(1). Вот только переключаться в консоль и запрашивать перевод там столь же непозволительно долго :( Но зато утилиту командной строки уже можно взять за основу моей поделки!Помимо
dict(1) взял- Rofi для показа экранного меню
- Zenity для запроса ввода
-
notify-send для показа всплывающих сообщенийПолучился скрипт vortaro.py ("vortaro" — это "словарь" на Эсперанто). Да-да, ужас-ужас, питонолапша. Я знаю. Зато я решил задачу быстро! Ибо plumbum сильно упрощает написание сценариев, склеивающих воедино пачку внешних программ. Хотя бы на bash писать не пришлось. Может быть потом перепишу на Haskell и turtle.
Сейчас скрипт смотрит в primary selection слово, пробует поискать его в первом словаре из списка, затем во втором и так далее. Если карточка найдена, то текст показывается во всплывающем сообщении. В том случае, когда конкретного слова в словаре не оказывается, но есть похожие на него слова, то сначала показывается меню, позволяющее выбрать одно из таких слов. Довольно таки специфическое поведение, готовую программу я бы скорее всего не нашёл. А тут полчаса и готово!
👍11🔥4