Пакеты, системы, модули, библиотеки — КАКОГО?

http://www.weitz.de/packages.html
  • Перевод


По моим наблюдениям, минимум раз в неделю в списке c.l.l или другом Lisp-списке «новички» путаются в том, что связано с пакетами. Говорят о «загрузке» пакета, «требовании» (requiring) пакета, удивляются тому, что после загрузки системы нужно пользоваться маркерами пакетов и т.д. Меня это раздражает, думаю также, что это может быть одной из причин, почему начинающие считают, что использование библиотек в Lisp сложнее, чем есть на самом деле.

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

Прежде всего следует иметь ясную голову. Термин «пакет» сильно перегружен. В дистрибутивах Linux вроде Debian или Gentoo есть «пакеты», «пакеты» есть в языках программирования Java, Perl или Python. Вполне вероятно, что вы пришли в Lisp с предвзятым мнением относительно того, что такое «пакет» или чем он должен быть.

Пакеты


Пакетом в Common Lisp называется полноправный элемент языка, семантика которого четко определена стандартом. Более того, из всех обсуждаемых на этой странице терминов, этот — единственный, имеющий (в контексте Common Lisp) однозначное определение. Пакеты — это, строго говоря, контейнеры для символов. Можно сказать, что они нужны для помощи в организации отдельных пространств имен в ваших программах.

В Common Lisp есть функции и макросы для создания, изменения, исследования и удаления пакетов. Очень хорошее введение в пакеты (и символы) можно найти в главе 21 великолепной книги Practical Common Lisp Питера Сайбела. Определение термина находится в главе 11 (онлайн-версии) стандарта ANSI Common Lisp specification.

В общем, про пакеты это всё. Говоря технически, вы не загружаете пакеты. Вы можете загрузить (с помощь LOAD) код, который в свою очередь создаст пакет, и это существенное различие.

Кроме того, если ваш Lisp жалуется, что не может найти какой-то пакет, это означает, что пакета как Lisp-объекта нет в образе (т.е. FIND-PACKAGE возвращает NIL), потому что его еще никто не создал. Это не означает, что Lisp-машина поискала в файловой системе и ничего не нашла. (Частая причина такой неудачи состоит в том, что события происходят в неправильном порядке. Об этом ниже.)

Системы


Системы, в отличие от пакетов, даже не упоминаются в стандарте. Тем не менее, опытные Lisp-программисты знают этот термин, поскольку им потребуютется знать и применять какой-то инструмент определения систем. Наиболее заметный сегодня — ASDF (используется большинством Lisp-библиотек с открытым исходным кодом); другой известный инструмент определения систем, гораздо старше ASDF — MK:DEFSYSTEM. Некоторые разработчики также поставляют свои инструменты определения систем вместе с дистрибутивами, см. например, Common Defsystem для LispWorks.

В этом ключе система, строго говоря, это набор кода плюс инструкция по его обработке, например, зависимости от других систем, что следует загрузить/скомпилировать в первую очередь и т.д. Другими словами, инструмент определения систем по своему назначению похож на make или Ant.

Кроме того, инструмент определения систем обычно может намного больше — Common Defsystem может, например, интегрировать файлы библиотек типов COM, ASDF полностью расширяем и использовался, среди прочего, для компиляции файлов на C. Он также часто используется для определения тестовых наборов описываемой системы.

Хотя ASDF и весьма популярен, он не вездесущ. Он идет предустановленным со многими Lisp-системами вроде SBCL, OpenMCL или AllegroCL, вероятнее всего, что он загрузится и в других Lisp-системах, но этот факт не делает его частью Common Lisp. Это набор кода без явной спецификации и с разными версиями, которые бывают несовместимы между собой.
Поди пойми…

Модули


Стандарт определяет модули лишь поверхностно. Есть две вещи, которые нужно знать о REQUIRE, PROVIDE и *MODULES* — эта функциональность не рекомендуется (deprecated) и зависит от реализации. Пусть вас не беспокоит тот факт, что эта функциональность не рекомендуется. Все дистрибутивы сегодня содержат указанные функции, и вероятность того, что появится новый стандарт ANSI и все реализации внезапно уберут их, конечно, мала. Вот о чем стоит беспокоиться, так это о том, что REQUIRE может быть удобным, но не переносимым методом (если вас, конечно, беспокоят механизмы переносимости).

Например, в LispWorks можно использовать

(require "foreign-parser")

для загрузки парсера, способного читать определения на C, но это не сработает на OpenMCL. Также можно вызвать

(require :asdf)

для загрузки ASDF на OpenMCL, но не в LispWorks.

Некоторые дистрибутивы предлагают хуки для настройки работы REQUIRE, и существуют расширения вроде common-lisp-controller, соединяющие REQUIRE с ASDF, однако в общем случае модуль — это такая штуковина, которая зависит от реализации и которую не следует путать с системами (ASDF), и, тем более, с пакетами.

Библиотеки


Скорее всего вы не найдете четкого определения, что такое библиотека. Большинство людей думают об этом как о коллекции кода, предназначенного для выполнения одной или нескольких определенных задач и распространяемого как единое целое, обычно в виде сжатого архива, который можно откуда-то скачать. На самом деле, это неясное определение является, думаю, наиболее подходящим при разговоре о программах, написанных на Lisp. Большинство Lisp-библиотек сегодня включают в себя определение (ASDF) системы, но это вовсе не обязательно. Возможно, в зависимости от способа получения, это будет модуль в вашей Lisp-системе, но и это тоже не обязательно. Кроме того, библиотека обычно определяет один или несколько пакетов, а может и не определять ни одного.

И, по соглашению, а может из-за недостатка фантазии, может сложится и часто складывается ситуация, когда библиотека «Ку» идет с определением системы «Ку», которую можно загрузить как модуль «Ку». После загрузки кода получите новый пакет, называемый «Ку». Четыре разных сущности с одинаковым именем! Я допускаю, что это сбивает с толку, но надеюсь, что несколько предыдущих абзацев помогли слегка прояснить ситуацию.

Но у меня все еще ничего не работает!


Часто люди жалуются, что они не могут скомпилировать файл, содержащий код вроде этого:

;; в этой строчка также может быть написано (require :cl-ppcre)
(asdf:oos 'asdf:load-op :cl-ppcre)

(defun my-simple-number-scanner (string)
  (cl-ppcre:scan "^[0-9]+$" string))

Почему так? Почему я могу загрузить этот файл, но не могу скомпилировать его? И почему я могу скомпилировать его после загрузки? Не странно ли?

Нет, не странно. Компилятор читает первую форму (которая является инструкцией скомпилировать — если необходимо — и загрузить систему CL-PPCRE, но не выполнить ее. В конце концов, компилятор заинтересован лишь в компиляции кода. После выполнения первой формы он переходит ко второй форме, к определению функции. Здесь возможно сообщение об ошибке, так как Lisp-сканер, пытающийся читать эту форму, обнаружит последовательность символов «cl-ppcre:scan», которая должна обозначать внешний символ из пакета CL-PPCRE, но самого пакета CL-PPCRE еще нет. В процессе загрузки системы CL-PPCRE, кроме всего прочего, создается пакет CL-PPCRE, но этого еще не произошло. Читайте главу 3 CLHS.

Можно воспользоваться EVAL-WHEN для указания компилятору загрузить CL-PPCRE перед чтением второй формы. Следует, однако, найти другой способ организации своего кода. Первая форма — это просто обявление того, что ваш код зависит от системы CL-PPCRE. Такое не должно находиться в том же файле, что и Lisp-код. Напишите определение системы для вашей программы и поместите зависимости туда.
Поделиться публикацией
Комментарии 15
    0
    все хочу изучить Lisp, все думаю когда… но иногда пугает некоторая неопределенность. Ведь между Си подобными языками и Lisp пропость, длинною в вечность, словно две разные вселенные, где зная одну, не сразу можно понять другую, а зная вторую, то первая окажется как открытая книга
    • НЛО прилетело и опубликовало эту надпись здесь
        0
        И только CL может без изменения стандарта продолжать тягать фичи из других языков до сих пор. :3
        • НЛО прилетело и опубликовало эту надпись здесь
        0
        Не такая уж и большая пропасть между этими вселенными, на самом деле.

        Во многих случаях можно сравнить вызов (func) в Lisp с вызовом func() в Си. Количество скобок и семантика совпадают :)

        Так что все не так страшно.
          0
          (Эх (Было бы дело лишь в одних скобочках...))
        0
        Пропадает все желание учить лисп после:
        Пакетом в Common Lisp называется полноправный элемент языка, семантика которого четко определена стандартом. Более того, из всех обсуждаемых на этой странице терминов, этот — единственный, имеющий (в контексте Common Lisp) однозначное определение. Пакеты — это, строго говоря, контейнеры для символов. Можно сказать, что они нужны для помощи в организации отдельных пространств имен в ваших программах.
        В тексте присутствует оперирование терминами, с которыми не каждый читатель знаком. ( а статья похоже для новичков в Lisp).

        Совершенно не понятно что есть элемент языка, символ, контейнер. А именно этими терминами автор пытается объяснить смысл понятия пакет.

        Я понимаю, что это не статья «Lisp for dummies», но с тем же успехом можно объяснять блондинке, что ДВС это один из главных агрегатов автомобиля, превращающий энергию от сгорания топлива в крутящий момент, и передающий этот крутящий момент в трансмиссию.
          0
          Package — это хэш-таблица от имён символов к самим символам. Вроде namespace'ов в других языках.

          Symbol — это объект, который имеет имя, может лежать (или не лежать — тогда это неинтернированный символ) в пакете и называть функции, переменные, типы и т.д. Вроде идентификаторов из других языков, только это всё-же объект, а не просто имя.

          И символы и пакеты — сущности первого класса. Их можно изменять как объекты с полями и складывать в переменные.
            0
            Вот, например из SBCL:

            CL-USER> (describe 'list)
            COMMON-LISP:LIST
              [symbol]

            LIST names a compiled function:
              Lambda-list: (&REST ARGS)
              Declared type: (FUNCTION * (VALUES LIST &OPTIONAL))
              Derived type: (FUNCTION (&REST T) (VALUES LIST &OPTIONAL))
              Documentation:
                Return constructs and returns a list of its arguments.
              Known attributes: flushable, unsafely-flushable, movable
              Source file: SYS:SRC;CODE;LIST.LISP

            LIST names the built-in-class #:
              Class precedence-list: LIST, SEQUENCE, T
              Direct superclasses: SEQUENCE
              Direct subclasses: CONS, NULL
              No direct slots.

            LIST names a primitive type-specifier:
              (undocumented)

            Видим, что символ LIST из пакета COMMON-LISP называет функцию и встроенный тип.
              0
              Желающим изучить Common Lisp советую почитать Practical Common Lisp (русский перевод). Книга даёт неплохой начальный пинок в правильном направлении, но требует кое-каких программерских знаний.

              Хотя лично я больше поклонник более цельного и стандартизированного Lisp-языка Scheme, а точнее — его богатейшего диалекта Racket (интервью с одним из разработчиков — здесь).
                +1
                Scheme более цельный и стандартизированный? Racket — да, Scheme вообще — скорее нет.
                  0
                  Согласен.
                    0
                    Скажите, чем отличаются Scheme и Racket? Что есть Racket вообще?
                      0
                      Racket — это, по сути, Scheme с небольшим количеством расширений на уровне языка. Но Racket более богат функционально: там больше удобных макросов (для циклов, например), солидный набор библиотек. Есть приличный редактор DrRacket с отладчиком (хотя Emacs + Quack + Geiser всё равно круче). Там есть ещё специальные учебные языки, в которых убраны все «лишние» на данном этапе обучения возможности. В общем, Racket пока что лидирует среди реализаций Scheme по универсальности и функционалу.
                0
                Думаю, что упоминание вместе блондинки и ДВС несколько некорректно для этой статьи.

                Скорее можно сказать, что здесь идет пояснение для учеников автомобильного техникума, как именно работает ДВС и как энергия преобразуется в движение.

                Т.е. для тех, кто уже знает о существовании comp.lang.lisp и уже что-то попробовал сделать сам.

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое