По большому-то счёту это не я, а named-readtables. Мой метод не помню зачем понадобился, он кривоват, его, наверное, нужно выкинуть вовсе. Хотя проблем с ним нет, он работает.
Кстати,я уже потратил на документирование данной библиотечки в этом году порядка 10 тыр. Что тем более обидно.
Сделал, чтобы при доступности named-readtables создавалась таблица :advanced. Старый вариант с advanced-readtable:! тоже оставил.
enable-* сделал по-умолчанию (более безопасное поведение). Ещё одно небольшое отклонение от стандарта: при попытке создать символ через package::symbol, происходит cerror с предупреждением и вопросом, действительно ли надо создавать.
1. Нужны ли множественный advanced-readtable с разными параметрами (есть/нет иерархические пакеты, список символов начала пакета, поддержка символов из точек, проверка на наличие символа при доступе через package::symbol)?
2. Имеет ли смысл сделать жёсткую зависимость от named-readtable? В плюсе: чище код, в минусе — появляется зависимость от внешней библиотеки.
1. А вот на это ИМХО ответит только опыт. Если от них будут проблемы, то скорее нужно делать не опциональными, а подумать ещё раз. Настраиваемость для такой библиотеки - недостаток.
2. named-readtables если и не является де-факто стандартом для библиотек, работающих с ридтейблами, было бы неплохо, если бы он им стал.
Тогда нужен всё-таки совет по множеству букв, которые могут быть в именах пакетов/символов. ASCII вроде мало. ASCII+кириллица лично мне хватит, но явно не политкорректно. Полный UCS — миллион с хвостиком в SBCL — загрузка очень долго.
Сейчас сделал константу, в которую можно положить строку нужных символов и перекомпилировать, но «настраиваемость для такой библиотеки - недостаток».
Уточнение: это множество букв, на которые могут начинаться локальные псевдонимы (push-local-nickname) или макро-символы (set-macro-symbol). Для любого вызова иерархическово пакета первая буква по определения ".", а не-иерархические и не-псевдонимы всё равно обрабатываются стандартно.
Такая настраиваемость никого не удивит. :) А вот что в одном файле .test значит глобальный пакет с именем .test, а в другом - относительно текущего - удивит. Лучше сделать гарантированный ASCII и функцию для добавления символов, без возможности удаления.
По поводу локализации, не припомню чтобы ECL, SBCL и CCL выдавали ошибки на чём-то ещё кроме английского, поэтому считаю, что можно не заморачиваться.
Ладно, надежда умирает последней, поэтому всё же расскажу про некоторые грабли.
Предыстория такова: библиотека monk-а - это урезанная старая версия моей библиотеки. У меня довольно большой опыт её использования и я знаю, как она устроена. От этого дизайна я отказался, это решение давно назрело, просто только месяц назад стало нужным и возможным его воплотить.
В библиотеке monk-а используется мухлёж с пакетами и таблицами чтения, путём связывания переменных *package* и *readtable* в момент чтения. Не знаю, как SLIME,а Lispworks запускает READ сразу в момент набора первой буквы в подсказке Listener-а. Это приводит к тому, что во время работы в Listener *package* равен не тому, чему он должен быть равен, а специальному фиксированному пакету, не знаю, как назвал этот пакет monk. Это приводит к тому, что нужно запоминать «настоящий» пакет и подсовывать его множеству команд среды, которое наивно полагаются на *package*. Начинает рости гора костылей. На меня эта гора в конце-концов упала. Так что библиотека monk-а не будет работать в среде Lispworks, даже если она и будет работать в консольном Lispworks. Monk, неплохо бы это озвучить и на главной странице твоей библиотеки. Будет ли она работать в SLIME? Даже если повезёт и не возникнут проблемы с другим *package*, всё равно патчи к SLIME понадобятся для Completion.
С чтением, в новой версии я поступил более правильно: взял кусок ридера из SBCL и портировал его на Lispworks. Усилия для этого понадобились довольно небольшие - нужно было подменить весь доступ к низкоуровневым возможностям таблицы чтения на лиспворковский. Есть версия, что ридер Lispworks - родственник ридера SBCL, поэтому это было несложно. Это решение вызовет, скорее всего, проблемы производительности на CLISP. В остальном оно является нормальным, качественным решением, совместимым со стандартом. А то, что сейчас используется в библиотеке Monk-а - это быстрогрязный хак, которым я пользовался гораздо дольше, чем нужно. Не стоит им пользоваться вообще.
Далее, я уже писал об опасности конструкции
package::(form).
Представьте себе, что form занимает 5 экранов. Вы, не подозревая подвоха, нажимаете M-. на какой-то функции fun и, в лучшем случае, никуда не попадаете, т.к. SLIME ничего не знает о конструкции package::(form). В худшем случае (это зависит от алгоритмов, заложенных в SLIME и я этого не знаю), в *package* появится мусорный символ fun. Даже если вы научите SLIME работать правильно, вы не научите этому Allegro и Lispworks - а значит, получается непереносимое решение.
Поэтому конструкцию package::(form) я исключил из своей библиотеки. Локальных никнеймов вообще вполне достаточно.
Ещё одни возможные грабли связаны с точкой в начале имени символа, хотя я про это уже писал и вы проигнорировали предупреждение. У меня были проблемы, когда я пытался назначить symbol-macro на символ, начинающийся с точки. Конструкция (a .b) в некоторой версии SBCL читалась как-то неправильно, не знаю, как сейчас. Если это всё ещё так, то нельзя будет считать (a .test::c)
Далее, я уже говорил, что нельзя называть пакеты символами. Редактор ищет в файле форму
(in-package :foo)
Вы не сможете писать в файле
(in-package foo)
потому что неизвестно, какой пакет в момент выполнения этой формы.
Если даже вы научите SLIME пользоваться
(in-package cl-user:foo)
то нет никакой гарантии, что это сработает для Allegro и Lispworks. Так что это решение также может убить переносимость.
Я про это уже писал, вы проигнорировали.
Есть проблема и с иерархическими пакетами. Как вы будете обрабатывать alexandria.0.dev::some-fun? Это должно корректно работать и до, и после загрузки вашей библиотеки. Будет ли alexandria.0.dev означать |alexandria.0.dev| или /alexandria/0/dev ?
Наверное, можно привыкнуть писать |alexandria.0.dev| везде, но я старался, чтобы мой ридер не отклонялся от стандарта, а соблюдал его в 99% случаев, во избежание разного рода проблем. Мне это более-менее удалось, и даже CamelCase пока породил не так много проблем, как можно было ожидать. Например, я не трогаю символы a.b, а выбрал символы a^b, причём, если ^ находится не в середине символа, я этот символ тоже не трогаю. Поэтому,в моей таблице чтения можно читать кучу чужих библиотек. Иерархические пакеты заставят в любую секунду думать о том, в каком контексте мы сейчас находимся и что значит a.b. Если бы иерархические пакеты были изначально и alexandria.0.dev всегда значила /alexandria/0/dev, было бы одно. А так вы делаете (вслед за Allegro) кривой костыль.
Я уж не говорю о такой весёлости, как (:in-package .0)
Всё это размывает смысл языка и отвлекает внимание от прикладной задачи, т.е., снижает производительность труда.
По этой причине я исключил иерархические пакеты из своей библиотеки. Опять же, локальные никнеймы, в принципе, решают эту проблему. Если мне понадобятся иерархические пакеты, я найду какую-нибудь ещё букву, но не #\.
Это должно корректно работать и до, и после загрузки вашей библиотеки.
*readtable* - file-local. Полная совместимость со стандартной readtable помогла бы перенесению библиотек на использование advanced-readtable, но решение использовать её или нет для каждой конкретной библиотеки принимает её автор. Достаточно 99% совместимости, которую, впрочем, тоже протестировать надо бы.
Нет. Она file-local только при загрузке файла, но не в процессе интерактивной разработки, когда мы заходим в разные файлы и правим формы по одной. В SWANK есть *readtable-alist*, дающий возможность привязать таблицу чтения к пакету. Тем самым, при переключении в пакет автоматически будет меняться и таблица чтения и EMACS будет это понимать. Не знаю как в Allegro, но в Lispworks этого нет.
Поэтому, для Lispworks надо (в идеале),чтобы была одна таблица чтения, к-рая правильно читает всё стандартное + мои расширения. Я свою библиотеку строил именно так и в целом я своей цели в своё время достиг. Было каких-то две проблемы при загрузке неплохой пачки библиотек.
Насколько сейчас это правило всё ещё выполняется - я не знаю, но я практически никогда не переключаю таблицу чтения во время работы, с момента загрузки образа, а переключение пакета делает среда.
То, что может EMACS/SLIME в плане переключения таблицы вместе с пакетом - с одной стороны, вроде удобнее, чем иметь одну таблицу чтения, с другой - этот список нужно поддерживать и мыслить в терминах разных таблиц чтения, что менее удобно и потенциально это породит баги, если код делает read.
Lispworks запускает READ сразу в момент набора первой буквы в подсказке Listener-а.
А что попадает в этот read? Если я сделаю (with-input-from-stream (s "(c") (read s)), то получу ошибку непарной скобки. А если в read всё-таки попадёт top-level-form, то всё нормально отработает.
Представьте себе, что form занимает 5 экранов. Вы, не подозревая подвоха, нажимаете M-. на какой-то функции fun...
При этом скомпилируется вся top-level-form. Корректно (в предположении, что используется SLIME+named-readtables или иной спопсб сказать IDE использовать текущий *readtable*, а не сбрасывать на стандартный).
то нельзя будет считать (a .test::c)
Может ты путаешь с (a . test::c)? Отдельно точку использовать нельзя (будет точечная пара, возможно незавершённая). Имя с точки может начинаться. Any token that is not a potential number, does not contain a package marker, and does not consist entirely of dots will always be interpreted as a symbol (c) CLHS 2.3.4
Далее, я уже говорил, что нельзя называть пакеты символами
А кто-то пытается? Тут я согласен полностью (find-package «FOO») == (find-package :foo) == (find-package foo).
Есть проблема и с иерархическими пакетами. Как вы будете обрабатывать alexandria.0.dev::some-fun
В любом случае это пакет с полным именем alexandria.0.dev. Единственное отличие в advanced-readtable — если *package* == alexandria, то можно написать .0.dev::some-fun и получить тот же символ. Более того, если кто-то сделает (defpackage .my-package) и будет в нём работать, то изменится только (package-name (find-package ".MY-PACKAGE")) — скорей всего станет CL.MY-PACKAGE. Всё остальное будет работать как и раньше.
(in-package .0)
Если текущий пакет ALEXANDRIA, то это будет значить (in-package alexandria.0). Что в этом плохого?
Поэтому, для Lispworks надо (в идеале),чтобы была одна таблица чтения, к-рая правильно читает всё стандартное + мои расширения.
У меня одна и есть. Стандартное может плохо читать только если злоупотреблять переопределением символов. А как Lispworks понимает какую таблицу чтения использовать? Вот есть у тебя в образе настроенная таблица чтения. Но (compile-file ...) или (asdf:...) определённо будут сбрасывать *readtable* с стандартную. Открыл файл, нажал «перекомпилировать функцию» — какая таблица будет? Из образа или стандартная?
Кстати, отчитываюсь о проделанной работе: named-readtables сделал зависимостью (чтобы не было проблемы при компиляции в его присутвии и загрузке без него). Рефакторил код: проставил типы всех переменных и функций, сделал описание эксопртируемых функций через named-readtables::define-api. Если не обнаружится фатальных недостатков (требующих слома API), то через неделю будет релиз 1.0.
Ну уж точно не скомпилируется, а максимум, прочитается. Но ты это проверял это в SLIME? В Lispworks работа M-. делается по-другому: он ищет подобие символа в окрестности положения курсора.
А что попадает в этот read?
Я не знаю, понял ли ты проблему. Ты ввёл одну букву в listener и все остальные средства типа find-definition, подсказка по функции и т.п. - сошли с ума. А ведь тебе ими и надо пользоваться, пока ты вводишь свою форму. Будет (более-менее) работать то,что в другом треде, но связь потока с графическим элементом неочевидна. То же самое случится и в редакторе.
Может ты путаешь с (a . test::c)
Нет, не путаю. Суть проблемы не помню, но она была достаточно сложной.
CLHS 2.3.4
SBCL раньше не соблюдал. ЕМНИП, назначенный macro-character на #\. просто игнорировался внутри списка, если это был не первый элемент. Может быть, уже поправили.
А кто-то пытается? ... (find-package foo)
Хм... Ну, если это не попытка, то я что-то не понял...
(in-package .0)
Например,тем, что это - число.
можно написать .0.dev::some-fun и получить тот же символ
Ну, может быть, это и правильно. Не знаю, надо подумать. Основная проблема здесь - с чтением точки в начале символа. Если это можно решить, то может быть, всё остальное шоколадно.
Но (compile-file ...) или (asdf:...) определённо будут сбрасывать *readtable* с стандартную.
Вообще-то, compile-file binds *readtable* and *package* to the values they held before processing the file. (function compile-file), и для load то же самое. Т.е.,достаточно один раз сделать (in-readtable), находясь вне контекста компиляции/загрузки и всегда будет своя.
Я в каждом файле пока что пишу (in-readtable ) для ясности. Честно сказать, не помню, зачем. Раньше у меня было две таблицы чтения с разным отношением к CamelCase, и вот я наконец одну из них изжил.
Перепутал. Думал про компиляцию.
А так, да: «named-readtables::(progn (define-api» в REPL при щелчке M-. на define-api даёт No known definition for: define-api (in COMMON-LISP-USER). Более того, аналогичная фигня в файлах:
(defpackage .b)
(in-readtable .b)
(..:car {здесь не работает список аргументов})
Но тут, в случае slime постепенно допилю, в случае закрытых реализаций — если настраивается или можно переопределить CL:FIND-SYMBOL, то кто-нибудь допилит. Надо парсер переписывать (дописывать).
Я имел в виду, что инкрементальная компиляция должна работать, а остальное не критично.
iterate использует defclause, который реализуются не через defmacro
Так я и говорю про DSL, а не про макросы. Могу ещё вспоминть cl-def с его (def definer ...) и, соответственно, проблемой найти этот самый definer, metabang-bind со своими расширениями и т.д.
Всё же есть проблема с alexandria.0.dev. Иерархические пакеты подобны каталогам. Если существует каталог /alexandria/0/dev, то существует и /alexandria, и /alexandria/0. Пакет alexadria.0.dev висит в воздухе и это имеет следствием отсутствие защиты от ошибок при доступе к несуществующему промежуточному каталогу. Именно в этом и состоит неполноценность иерархических пакетов в лиспе.
Кроме того, я думаю, что всё же local-nicknames является наиболее общим решением. В иерархических пакетах я должен говорить «дочь моих родителей», а в local-nicknames я просто назову её сестрой. Таким образом, они гораздо гибче. А если есть два схожих решения для одного и того же, есть тенденция всегда использовать только одно из них.
Хотя, возможно, есть случаи, где иерархические пакеты даже в таком виде полезны. Может быть, при работе с Java? Во всяком случае, мне пока они не нужны, и проблема с точкой так и не выяснена.
Если существует каталог /alexandria/0/dev, то существует и /alexandria, и /alexandria/0.
Неверно. Так же как наличие www.linux.org.ru не требует обязательного наличия linux.org.ru и org.ru.
Разве что, находясь в alexadria.0.dev не сможешь сделать (in-package ..)
Кроме того, я думаю, что всё же local-nicknames является наиболее общим решением.
Одно другому не мешает. local-nicknames требует явного указания псевдонима для каждого пакета. Иерархия автоматическая + позволяет потом удобно использовать пачку пакетов через push-import-prefix.
Может быть, при работе с Java
При работе в Java-стиле: каждый класс имеет собственный пакет.
Без иерархических пакетов (и импорта) пользоваться именами типа com.gigamonkeys.json очень неудобно.
Ладно, это всё можно долго ещё обсуждать. Я только одно скажу: в моей библиотеке решены вопросы, которые ты даже ещё не поставил, например, вопрос непринуждённого управления большим количеством пакетов. К иерархии пакетов этот вопрос, поверь, ортогонален. Этот вопрос я решил с четвёртой или пятой попытки, потратив на него большие усилия. Я не хочу, чтобы это пропало, а при твоём «форке» ты просто выбросил это за борт. Кроме того, моя новая версия библиотеки не является по сути костылём, задача решена правильно. У тебя она решена неправильно. У меня нет ресурсов и желания поддерживать свою библиотеку для тех реализаций, которыми я не пользуюсь. Судя по твоей активности, ты можешь плодотворно трудиться. Есть у меня шансы уломать тебя подключиться к моей библиотеке и если да, то что нужно для этого сделать?
Это значит, мне придётся на нескольких машинах поставить git... Править разного рода батники, занимающиеся переносом репозиториев и осваивать git.
Нормально ли git работает на винде?
Чем плох code.google.com и hg?
Можно ли импортировать репозиторий из hg в git с сохранением истории?
Это заодно позволить тебе контролировать процесс разработки
Это, в общем-то, мне кажется не слишком нужным - мы разумные люди
малость лень смотреть в код и тем более пробовать, посему задам вопрос: использование в одном «name space» твоей библиотеки и «стандартного» iterate не породит новую вселенную?
Из-за его красивостей я и перелез с common-lisp.net + cvs/svn на github. Под линуксом разницы никакой (особенно в emacs).
Чем плох code.google.com и hg?
Как будет процесс работы? В github я делаю clone твоего проекта, делаю кусок работы, делаю pull request. Ты смотришь и подтверждаешь. Здесь как? Дашь доступ на коммит в hg?
Можно ли импортировать репозиторий из hg в git с сохранением истории?
Вообще, у меня пакет называется :iterate-keywords.
Соответственно, следующие решения должны бы сработать:
1. импортировать в твой пакет iterate-keywords:iter, вместо iterate:iter, другие символы из iterate-keywords не нужны. Тогда в других пакетах можно по-старому использовать iterate:iter.
нужно также учитывать, что iterate, хоть и медленно, но развивается. А моя версия отстала на несколько лет. Думаю, 3-4 патча, которые за это время были применены, вряд ли что-то кардинально меняют, неплохо бы перенести из обычного iterate.
Некоторое время назад я предлагал в iterate-devel залить мои изменения в trunc, произошло некоторое обсуждение и всё затихло.
Я c git никогда не работал, но насколько я в курсе, hg и git устроены почти одинаково - они оба являются распределёнными системами с клонированием и умным слиянием изменений из разных источников. Варианты работы - разные:
1. делаешь клон на своём компе, присылаешь патчи мне, я их заливаю в свой локальный клон и потом в интернет.
2. (наилучший, если ты будешь много коммитить) даю доступ на commit. Ты можешь pull-ить изменения и push-ить.
Вот этот вариант поподробней. В github я просто нажимаю «pull request» у владельца и создаётся коммит с разницей между своей и его ветками (= мой комментарий к коммиту). Владелец может принять или отвергнуть. В твоём варианте как? По электронной почте?
Чувак, проект хостится на гуглокоде. И хотя считаю, что это было ошибкой, что главная страница проекта ничего не делает для того чтобы посетитель даже первый раз на него посмотрел, но всё-равно странно требовать от автора переноса на другой кодохостинг. Я, правда, не уверен, что гуглокод вообще умеет форки и мёрж-риквесты. Если не умеет, то можно перенести на битбакет, который умеет и у него получше с планированием странички проекта (там сорцы и ридми, как на гитхабе).
Но отказ от участия в проекте из-за непонятной системы контроля версий, которая на самом деле отличается в сущих мелочах (и не факт, что в худшую строну :P) выглядит как глупая отмазка.
Впрочем, есть альтернативное решение. Таки создать проект на гитхабе и вам работать через hg-git. Он может и репозиторий целиком в гит положить вместе с историей.
я не совсем это имел в виду: в своей библиотеке def-symbol-readmacro ты не «реэкспортишь» часом iter? Если я в своём модуле сделаю use на def-symbol-readmacro и на оригинальный iterate - конфликта имён не будет?
Будет доступ на commit. Этого хватит?
Слияние версий я обычно делаю локально. Тяну со всех репозиториев все ветки, сливаю у себя и делаю в конце концов push в интернет.
Вместо merge-реквеста ты делаешь hg бандл - перечень своих изменений за какое-то время. Присылаешь его, допустим, мне, а я делаю мерж и push-у в интернетный реп.
Всю работу делаю на винде с помощью tortoise-hg, весьма удобно.
Но если у тебя будет доступ на commit в интернетный реп, то тебе нужно будет: стянуть изменения из интернета (pull) на локал, сделать merge с твоими изменениями и сделать push в интернет.