Как использовать библиотеки в Common Lisp?

Я новичок в Common Lisp и хочу использовать библиотеку.

Я не могу найти ни одного простого примера загрузки/требования/использования модуля. Я установил cl-ppcre следующим образом:

$ sbcl --non-interactive --eval '(ql:quickload "cl-ppcre")'
To load "cl-ppcre":
  Load 1 ASDF system:
    cl-ppcre
; Loading "cl-ppcre"
..

Но я не знаю, как на самом деле использовать его. Я пробовал следующее и дюжину других вещей, и ни одна из них не работает.

$ sbcl --noinform --non-interactive --eval '(progn (require "cl-ppcre") (cl-ppcre:split "\s+" "1 2 3"))'
Unhandled SB-INT:SIMPLE-READER-PACKAGE-ERROR in thread #<SB-THREAD:THREAD "main thread" RUNNING
                                                          {1004DB8073}>:
  Package CL-PPCRE does not exist.

    Stream: #<dynamic-extent STRING-INPUT-STREAM (unavailable) from "(progn (...">

Backtrace for: #<SB-THREAD:THREAD "main thread" RUNNING {1004DB8073}>
0: (SB-DEBUG::DEBUGGER-DISABLED-HOOK #<SB-INT:SIMPLE-READER-PACKAGE-ERROR "Package ~A does not exist." {1003640A83}> #<unused argument> :QUIT T)
1: (SB-DEBUG::RUN-HOOK *INVOKE-DEBUGGER-HOOK* #<SB-INT:SIMPLE-READER-PACKAGE-ERROR "Package ~A does not exist." {1003640A83}>)
2: (INVOKE-DEBUGGER #<SB-INT:SIMPLE-READER-PACKAGE-ERROR "Package ~A does not exist." {1003640A83}>)
3: (ERROR #<SB-INT:SIMPLE-READER-PACKAGE-ERROR "Package ~A does not exist." {1003640A83}>)

Итак, как я могу заставить его работать?

РЕДАКТИРОВАТЬ 1: Я не уточнил, что моя проблема с использованием библиотек связана как со скриптами, так и с терминалом. Это было неявно для меня. Это из-за моего опыта работы с Perl, в котором все, что можно сделать с файлом, можно делать в командной строке, в том числе с помощью библиотек.

Обновлено еще раз: Вот мое рабочее решение. Как оказалось, были 2 вещи не так. Моя проблема требует:

  1. используя несколько --eval

Как и сказали Сванте и Игнис Воленс.

  1. (load "~/.quicklisp/setup.lisp")

Что мне уже объяснили здесь:

Запутался в ``ql:quickload`` и исполняемых скриптах в SBCL

Это терминальное решение:

sbcl --non-interactive --eval '(load "~/.quicklisp/setup.lisp")' --eval '(require :cl-ppcre)' --eval '(princ (cl-ppcre:split "\\s+" "1  2 3"))'

С той оговоркой, что на stderr выводится куча предупреждений, вроде этого, и я не знаю, почему так.

WARNING: redefining QL-SETUP:QMERGE in DEFUN

А это скриптовое решение:

#!/usr/bin/sbcl --script

(load "~/.quicklisp/setup.lisp")
(require :cl-ppcre)

(princ (cl-ppcre:split "\\s+" "1    2 3"))
(terpri)
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
0
98
5
Перейти к ответу Данный вопрос помечен как решенный

Ответы 5

Ты почти там.

Вы можете использовать (ql:quickload package) для установки и использования. Если его нет в вашей системе, он будет загружен.

Если вы также не импортируете пакет, вы должны указать префикс функции с именем библиотеки.

Можете ли вы заставить это работать в вашей системе разработки?

 (ql:quickload :arrow-macros)

 (defun do-it ()
     (arrow-macros:->>
         '(1 2 3 4)
          (mapcar #'1+)
          (reduce #'+)
          ))

Да, в REPL все работало. Но это не работает в командной строке, я получаю «Пакет ARROW-MACROS не существует».

WhiteMist 29.03.2022 10:10

Хорошо, я укушу. Почему вы не хотите использовать REPL ??

Francis King 29.03.2022 15:38

Загружаются системы, а не пакеты. «пакет» в Common Lisp — это термин для его конструкций пространства имен.

Rainer Joswig 29.03.2022 21:20

@FrancisKing В конце концов я буду использовать REPL, и очень скоро. Но поскольку мой предыдущий рабочий процесс с языком сценариев был полностью выполнен с помощью терминала и файлов, я хотел, чтобы это было охвачено. Но это нечто большее. Сначала я не уточнял, но это была проблема не только с использованием библиотек в однострочниках, но и в простых файлах. В конце концов, продуктом программирования являются файлы и сценарии, поэтому, как вы знаете, возможность вызывать библиотеки из файловых сценариев невозможна.

WhiteMist 29.03.2022 22:07

@RainerJoswig Я понятия не имею, в чем разница. Я посмотрю. Каждый язык имеет более или менее разные способы управления пространствами имен, временем компиляции/временем выполнения, ранними/поздними связываниями и т. д. Что не помогает, так это то, что каждый использует разные слова для обозначения таких вещей. Слова почти лишены своего значения, они не содержат значений напрямую, только ссылки на значения в неизвестном месте.

WhiteMist 29.03.2022 22:13

@WhiteMist Common Lisp SYSTEM - это загружаемое программное обеспечение: библиотека или программа. ПАКЕТ Common Lisp — это пространство имен для символов. Common Lisp унаследовал эти названия «система» и «пакет» от более раннего Lisp 70-х годов.

Rainer Joswig 29.03.2022 22:19

@WhiteMist Идея такой системы разработки, как Portacle, заключается в том, что вы создаете изображение, которое добавляете в интерактивном режиме. Наконец, вы выгружаете образ как исполняемый файл, который вы запускаете из файла сценария. Настоятельно рекомендуется использовать Portacle.

Francis King 29.03.2022 22:40

@Rainer Joswig Я думал, что это будет что-то вроде этого, но полезно знать наверняка. Думаю, сегодня мы бы назвали SYSTEM либо библиотекой, либо модулем компиляции.

WhiteMist 29.03.2022 22:56

@FrancisKing Спасибо за рекомендацию, я проверю ее, когда буду немного более продвинутым. Но насколько распространено создание скомпилированных двоичных файлов на Common Lisp? Это классная возможность или это действительно стандартный и практичный способ создания готовых программ? Я спрашиваю об этом, потому что однажды нашел способ создать скомпилированный двоичный файл из «образа» состояния интерпретатора (SBCL), и он создал двоичный файл размером 30 МБ только для Hello World.

WhiteMist 29.03.2022 23:01

Запуск программ из параметров bash в качестве источника — не самый простой способ начать. Проблема заключается в порядке компиляции, загрузки и выполнения. Грубо говоря, части, которые вы вводите в командной строке, сначала компилируются, а затем загружаются, но сначала необходимо загрузить библиотеку, прежде чем она сможет скомпилировать остальные. Это можно обойти, например. грамм. с несколькими параметрами -e, но я не думаю, что это полезное упражнение для новичка.

Если вы просто хотите попробовать что-то из командной строки, запустите SBCL без параметров, что даст вам приглашение REPL. Это командная строка, которая принимает код Лиспа. Похоже на * в ванильном SBCL.

[user@home]> sbcl
This is SBCL …
*

В этом приглашении загрузите свою библиотеку:

* (ql:quickload "cl-ppcre")
To load "cl-ppcre":
  Load 1 ASDF system:
    cl-ppcre
; Loading "cl-ppcre"
[package cl-ppcre]................................
..........................
("cl-ppcre")
* 

Затем используйте его:

* (cl-ppcre:split "\s+" "1 2 3")
("1 2 3")
* 

Затем вы можете выйти из REPL:

* (quit)
[user@home]>

Для серьезной работы используйте файлы.

Ответ принят как подходящий

Это пример общей проблемы, с которой сталкиваются люди с CL.

Работа CL (и других Лиспов) состоит из трех фаз (на самом деле их больше трех, но трех достаточно):

  1. считывается последовательность символов, чтобы превратить ее в форму;
  2. с этой формой происходит различное волшебство;
  3. оценивается результат этапа 2 и, возможно, результаты распечатываются.

Обычно этот процесс итерируется для обработки, скажем, файла или потока ввода.

Важно то, что (1), (2) и (3) происходят в последовательности: (1) завершается до начала (2), и оба (1) и (2) завершаются до начала (3).

Это означает, что (1) должно быть возможно до того, как произойдет что-либо из (2) или (3).

Итак, рассмотрим эту форму:

(progn 
  (ql:quickload "cl-ppcre")
  (cl-ppcre:split "\s+" "1 2 3"))

(Это в значительной степени одна из форм, которые вы пытаетесь оценить.)

Итак, вопрос: что нужно, чтобы (1) произошло? Ну, это требует двух вещей:

  • пакет QL (который, по сути, является пространством имен: см. ниже подробнее о том, что означает «пакет» в CL) должен существовать, чтобы читать ql:quickload;
  • пакет CL-PPCRE должен существовать, чтобы читать cl-ppcre:split.

И теперь вы видите проблему: (ql:quickload "cl-ppcre")создает пакет CL-PPCRE, и это не происходит до (3). Это означает, что эта форма не может быть прочитана.

Обойти это можно, в отчаянии, разными богатырскими уловками. Однако на самом деле вам это не нужно: вы можете сделать что-то еще, что (почти: см. ниже) работает:

(ql:quickload "cl-ppcre")
(cl-ppcre:split "\s+" "1 2 3")

И это (почти) хорошо, потому что это не одна форма: их две. Итак, (1)-(3) отлично работает для первой формы, а потом (1)-(3) отлично работает для второй формы.

Таким образом, ответ заключается не в том, чтобы пытаться упаковать все в единую форму. Либо поместите вещи в файл (вероятно, лучший способ), либо, если вы действительно хотите запускать вещи в качестве аргументов командной строки, вам нужно сделать так, чтобы формы были отдельными, например, с несколькими параметрами --eval.


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

(ql:quickload "cl-ppcre")
(cl-ppcre:scan ...)
...

Итак, что делает компилятор, когда компилирует этот файл? Он читает его, форма за формой, но в целом не фактически выполняет код (есть исключения): он организует выполнение этого кода при загрузке файла. Так что компилятор не будет загружать CL-PPCRE: он устроит жизнь так, что при файле загружен будет загружен CL-PPCRE. И теперь у нас есть версия той же проблемы: вторая форма не может быть прочитана компилятором, потому что пакет CL-PPCRE еще не существует.

Что ж, для этого есть решение: вы можете сказать компилятору, что он действительно должен выполнять некоторый код во время компиляции:

(eval-when (:compile-toplevel :load-toplevel :execute)
  (ql:quickload "cl-ppcre"))
(cl-ppcre:scan ...)
...

И теперь благодаря форме eval-when компилятор знает, что он должен вызывать ql:quickload. Так оно и будет, поэтому пакет CL-PPCRE будет определен во время компиляции, и все будет хорошо.


Примечание о пакетах в CL

Термин «пакет» в CL имеет значение, которое, к сожалению, не такое, как во многих других языках: это связано с историей и теперь не может быть изменено.

В обычном использовании пакет — это «некоторый фрагмент кода, который вы, возможно, можете установить и который, возможно, имеет зависимости от других пакетов (фрагментов кода), за которыми может следить какой-либо менеджер пакетов. Вы можете установить Python в виде пакета на свой компьютер с Ubuntu или использовать диспетчер пакетов conda для управления научными пакетами Python (включая сам Python).

В CL пакет — это, по сути, пространство имен. Если я набираю «foo:bar», то это относится к символу с именем BAR, доступному в пакете, одним из имен или псевдонимов которого является FOO. Кроме того, это «внешний» символ, что означает, что он каким-то образом предназначен для публичного использования. Пакеты — это реальные объекты в CL, и программы могут их анализировать. Всегда существует понятие текущего пакета, и этот пакет определяет, какие имена доступны, не требуя префиксов пакетов, как за счет непосредственного содержания некоторых имен, так и за счет поиска («использования») списка других пакетов. Пакетов в CL очень много: гораздо больше, чем я могу упомянуть здесь.

То, что обычно называют пакетами, вероятно, лучше всего называть «библиотеками» в CL: это фрагменты кода, которые вы можете установить и которые могут иметь собственные зависимости. В качестве альтернативы их часто называют «системами», поскольку они часто определяются с помощью инструмента «определения системы». «Библиотека», вероятно, является более подходящим термином: «система» — это опять-таки историческая странность, которую уже нельзя изменить.

Спасибо, чувак, что нашел время объяснить это. Этого недостаточно, чтобы заставить меня понять, но достаточно, чтобы я мог начать задавать правильные вопросы и узнавать то, чего я еще не знаю.

WhiteMist 29.03.2022 22:19

@WhiteMist: я добавил примечание о пакетах в CL, которое, как мне кажется, обычно очень сбивает с толку новичков, и какой термин я раньше просто использовал без объяснения причин, что было плохо.

ignis volens 30.03.2022 11:57

Пакеты

Определения

  • библиотека или приложение называется система.
  • пакеты - это пространства имен для символов
  • символы — это именованные объекты, которые часто находятся в пакете

FOO::BAR обозначает символ с именем BAR в пакете FOO.

FOO:BAR обозначает экспортированный символ с именем BAR в пакете FOO.

BAR обозначает символ BAR в текущем пакете

Чтобы прочитать символ с определенным пакетом, этот пакет должен существовать.

Пример:

В LispWorks есть пакет под названием CAPI -> мы можем его найти.

CL-USER 44 > (find-package "CAPI")
#<The CAPI package, 5109/8192 internal, 880/1024 external>

CL-USER 45 > (read-from-string "CAPI::BAR")
CAPI::BAR
9

выше создает символ с именем BAR в существующем пакете CAPI.

Но нет пакета под названием FOO. Не можем найти:

CL-USER 46 > (find-package "FOO")
NIL

Теперь чтение символа, находящегося в пакете FOO, является ошибкой, так как пакет не существует:

CL-USER 47 > (read-from-string "FOO::BAR")

Error: Reader cannot find package FOO.
  1 (continue) Create the FOO package.
  2 Use another package instead of FOO.
  3 Try finding package FOO again.
  4 (abort) Return to top loop level 0.

Type :b for backtrace or :c <option number> to proceed.
Type :bug-form "<subject>" for a bug report template or :? for other options.

CL-USER 48 : 1 > 

Проблема

Когда у вас есть одна форма Lisp, вам нужно убедиться, что она не содержит неизвестных пакетов. Поэтому сначала загрузите в систему программное обеспечение, которое также определяет пакеты. Затем найдите символ cl-ppcre:split по названию split в упаковке cl-ppcre:

(progn
  (ql:quickload "cl-ppcre")
  (funcall (find-symbol "SPLIT" "CL-PPCRE") "\\s+" "1 2 3"))

Выше избегается упоминание символа cl-ppcre:split. Вместо этого мы просматриваем его во время выполнения через find-symbol.

Или:

(progn
   (ql:quickload "cl-ppcre")
   (eval (read-from-string "(cl-ppcre:split  \"\\\\s+\" \"1 2 3\")")))

Или: сделайте его двумя формами:

  (ql:quickload "cl-ppcre")  ; loading the software
                             ;  also creates the package CL-PPCRE
  (cl-ppcre:split "CL-PPCRE") "\\s+" "1 2 3"))

Да, это работает, но это хак. Мне не нужно возиться с символами только для того, чтобы вызвать функцию библиотеки. Я обнаружил, что замена (ql:quickload "cl-ppcre") на (require "cl-ppcre") также работает в этом примере.

WhiteMist 29.03.2022 22:01

@whitemist: это не взлом. Это просто необходимо, так как в противном случае форма читается, ничего не загрузив. В Common Lisp нельзя интернировать символ в несуществующем пакете. Символы представляют собой структуру данных, поэтому их можно создавать или находить во время выполнения, а FUNCALL может вызывать символьную функцию символа. -- единственная реальная альтернатива состоит в том, чтобы иметь две различные формы. Сначала загружается программное обеспечение. Затем вторая форма считывается и выполняется. Таким образом, во время чтения символ и его пакет уже будут существовать.

Rainer Joswig 29.03.2022 22:06

Я понимаю, на что ты мне намекаешь. Я не считаю это решением проблемы использования библиотеки самым простым способом (вызовом функции), но, по крайней мере, вы учите меня кое-чему о том, как работает Common Lisp.

WhiteMist 29.03.2022 23:06

Я думаю, что понял. В конце нам нужно загрузить библиотеку, которая также создает запись в некоторой таблице символов, а затем нам нужно использовать эту запись пакета в таблице символов, чтобы иметь ссылку на символ /a функции, которую мы хотим вызвать. Это должно происходить последовательно. И поскольку что-то в загрузке библиотеки происходит во время выполнения, мы можем либо иметь 2 формы, вычисляемые последовательно одно за другим, либо мы можем поместить 2 s-выражения в 1 форму. Но поскольку их символы одновременно ограничены, нам приходится вручную задерживать один из них, чтобы сделать привязку последовательной.

WhiteMist 29.03.2022 23:21

Приближенным образом.

WhiteMist 29.03.2022 23:22

@WhiteMist таблица символов является пакет, а символ является записью в этой таблице. также ql:quickload означает "символ quickload в упаковке ql", если вы не знали.

Will Ness 30.03.2022 13:56

@whitemist: проблема не в привязке, проблема в том, что пакет (пространство имен) существует только после загрузки программного обеспечения (поскольку он содержит необходимое объявление пакета, которое генерирует пакет), поэтому символ my-package:my-name не может быть прочитан . Чтение означает вызов функции READ для потока и возврат символа. Решения: A) создайте пространство имен (пакет) перед чтением проблемной формы. Б) не включайте в форму оскорбительный символ. --- Это не зависит от привязок, значений и т. д. Просто чтение (-> разбор) s-выражения уже не работает.

Rainer Joswig 30.03.2022 22:12

На самой странице quicklisp есть пример того, как это сделать: https://www.quicklisp.org/beta/

В примере устанавливается quicklisp и, в конечном итоге, используется библиотека "vecto".

Другие вопросы по теме