mirror of
https://github.com/adambard/learnxinyminutes-docs.git
synced 2024-12-01 09:23:03 +03:00
705 lines
28 KiB
Common Lisp
705 lines
28 KiB
Common Lisp
|
---
|
|||
|
|
|||
|
language: "Common Lisp"
|
|||
|
filename: commonlisp.lisp
|
|||
|
contributors:
|
|||
|
- ["Paul Nathan", "https://github.com/pnathan"]
|
|||
|
- ["Rommel Martinez", "https://ebzzry.io"]
|
|||
|
translators:
|
|||
|
- ["Michael Filonenko", "https://github.com/filonenko-mikhail"]
|
|||
|
lang: ru-ru
|
|||
|
---
|
|||
|
|
|||
|
Common Lisp - мультипарадигменный язык программирования общего назначения, подходящий для широкого
|
|||
|
спектра задач.
|
|||
|
Его частенько называют программируемым языком программирования.
|
|||
|
|
|||
|
Идеальная отправная точка - книга [Common Lisp на практике (перевод)](http://lisper.ru/pcl/).
|
|||
|
Ещё одна популярная книга [Land of Lisp](http://landoflisp.com/).
|
|||
|
И одна из последних книг [Common Lisp Recipes](http://weitz.de/cl-recipes/) вобрала в себя лучшие
|
|||
|
архитектурные решения на основе опыта коммерческой работки автора.
|
|||
|
|
|||
|
|
|||
|
|
|||
|
```common-lisp
|
|||
|
|
|||
|
;;;-----------------------------------------------------------------------------
|
|||
|
;;; 0. Синтаксис
|
|||
|
;;;-----------------------------------------------------------------------------
|
|||
|
|
|||
|
;;; Основные формы
|
|||
|
|
|||
|
;;; Существует два фундамента CL: АТОМ и S-выражение.
|
|||
|
;;; Как правило, сгруппированные S-выражения называют `формами`.
|
|||
|
|
|||
|
10 ; атом; вычисляется в самого себя
|
|||
|
:thing ; другой атом; вычисляется в символ :thing
|
|||
|
t ; ещё один атом, обозначает `истину` (true)
|
|||
|
(+ 1 2 3 4) ; s-выражение
|
|||
|
'(4 :foo t) ; ещё одно s-выражение
|
|||
|
|
|||
|
;;; Комментарии
|
|||
|
|
|||
|
;;; Однострочные комментарии начинаются точкой с запятой. Четыре знака подряд
|
|||
|
;;; используют для комментария всего файла, три для раздела, два для текущего
|
|||
|
;;; определения; один для текущей строки. Например:
|
|||
|
|
|||
|
;;;; life.lisp
|
|||
|
|
|||
|
;;; То-сё - пятое-десятое. Оптимизировано для максимального бадабума и ччччч.
|
|||
|
;;; Требуется для функции PoschitatBenzinIsRossiiVBelarus
|
|||
|
|
|||
|
(defun meaning (life)
|
|||
|
"Возвращает смысл Жизни"
|
|||
|
(let ((meh "abc"))
|
|||
|
;; Вызывает бадабум
|
|||
|
(loop :for x :across meh
|
|||
|
:collect x))) ; сохранить значения в x, и потом вернуть
|
|||
|
|
|||
|
;;; А вот целый блок комментария можно использовать как угодно.
|
|||
|
;;; Для него используются #| и |#
|
|||
|
|
|||
|
#| Целый блок комментария, который размазан
|
|||
|
на несколько строк
|
|||
|
#|
|
|||
|
которые могут быть вложенными!
|
|||
|
|#
|
|||
|
|#
|
|||
|
|
|||
|
;;; Чем пользоваться
|
|||
|
|
|||
|
;;; Существует несколько реализаций: и коммерческих, и открытых.
|
|||
|
;;; Все они максимально соответствуют стандарту языка.
|
|||
|
;;; SBCL, например, добротен. А за дополнительными библиотеками
|
|||
|
;;; нужно ходить в Quicklisp
|
|||
|
|
|||
|
;;; Обычно разработка ведется в текстовом редакторе с запущенным в цикле
|
|||
|
;;; интерпретатором (в CL это Read Eval Print Loop). Этот цикл (REPL)
|
|||
|
;;; позволяет интерактивно выполнять части программы вживую сразу наблюдая
|
|||
|
;;; результат.
|
|||
|
|
|||
|
;;;-----------------------------------------------------------------------------
|
|||
|
;;; 1. Базовые типы и операторы
|
|||
|
;;;-----------------------------------------------------------------------------
|
|||
|
|
|||
|
;;; Символы
|
|||
|
|
|||
|
'foo ; => FOO Символы автоматически приводятся к верхнему регистру.
|
|||
|
|
|||
|
;;; INTERN создаёт символ из строки.
|
|||
|
|
|||
|
(intern "AAAA") ; => AAAA
|
|||
|
(intern "aaa") ; => |aaa|
|
|||
|
|
|||
|
;;; Числа
|
|||
|
|
|||
|
9999999999999999999999 ; целые
|
|||
|
#b111 ; двоичные => 7
|
|||
|
#o111 ; восьмеричные => 73
|
|||
|
#x111 ; шестнадцатиричные => 273
|
|||
|
3.14159s0 ; с плавающей точкой
|
|||
|
3.14159d0 ; с плавающей точкой с двойной точностью
|
|||
|
1/2 ; рациональные)
|
|||
|
#C(1 2) ; комплексные
|
|||
|
|
|||
|
;;; Вызов функции пишется как s-выражение (f x y z ....), где f это функция,
|
|||
|
;;; x, y, z, ... аругменты.
|
|||
|
|
|||
|
(+ 1 2) ; => 3
|
|||
|
|
|||
|
;;; Если вы хотите просто представить код как данные, воспользуйтесь формой QUOTE
|
|||
|
;;; Она не вычисляет аргументы, а возвращает их как есть.
|
|||
|
;;; Она даёт начало метапрограммированию
|
|||
|
|
|||
|
(quote (+ 1 2)) ; => (+ 1 2)
|
|||
|
(quote a) ; => A
|
|||
|
|
|||
|
;;; QUOTE можно сокращенно записать знаком '
|
|||
|
|
|||
|
'(+ 1 2) ; => (+ 1 2)
|
|||
|
'a ; => A
|
|||
|
|
|||
|
;;; Арифметические операции
|
|||
|
|
|||
|
(+ 1 1) ; => 2
|
|||
|
(- 8 1) ; => 7
|
|||
|
(* 10 2) ; => 20
|
|||
|
(expt 2 3) ; => 8
|
|||
|
(mod 5 2) ; => 1
|
|||
|
(/ 35 5) ; => 7
|
|||
|
(/ 1 3) ; => 1/3
|
|||
|
(+ #C(1 2) #C(6 -4)) ; => #C(7 -2)
|
|||
|
|
|||
|
;;; Булевые
|
|||
|
|
|||
|
t ; истина; любое не-NIL значение `истинно`
|
|||
|
nil ; ложь; а ещё пустой список () тоже `ложь`
|
|||
|
(not nil) ; => T
|
|||
|
(and 0 t) ; => T
|
|||
|
(or 0 nil) ; => 0
|
|||
|
|
|||
|
;;; Строковые символы
|
|||
|
|
|||
|
#\A ; => #\A
|
|||
|
#\λ ; => #\GREEK_SMALL_LETTER_LAMDA
|
|||
|
#\u03BB ; => #\GREEK_SMALL_LETTER_LAMDA
|
|||
|
|
|||
|
;;; Строки это фиксированные массивы символов
|
|||
|
|
|||
|
"Hello, world!"
|
|||
|
"Тимур \"Каштан\" Бадтрудинов" ; экранировать двойную кавычку обратным слешом
|
|||
|
|
|||
|
;;; Строки можно соединять
|
|||
|
|
|||
|
(concatenate 'string "ПРивет, " "мир!") ; => "ПРивет, мир!"
|
|||
|
|
|||
|
;;; Можно пройтись по строке как по массиву символов
|
|||
|
|
|||
|
(elt "Apple" 0) ; => #\A
|
|||
|
|
|||
|
;;; Для форматированного вывода используется FORMAT. Он умеет выводить, как просто значения,
|
|||
|
;;; так и производить циклы и учитывать условия. Первый агрумент указывает куда отправить
|
|||
|
;;; результат. Если NIL, FORMAT вернет результат как строку, если T результат отправиться
|
|||
|
;;; консоль вывода а форма вернет NIL.
|
|||
|
|
|||
|
(format nil "~A, ~A!" "Привет" "мир") ; => "Привет, мир!"
|
|||
|
(format t "~A, ~A!" "Привет" "мир") ; => NIL
|
|||
|
|
|||
|
|
|||
|
;;;-----------------------------------------------------------------------------
|
|||
|
;;; 2. Переменные
|
|||
|
;;;-----------------------------------------------------------------------------
|
|||
|
|
|||
|
;;; С помощью DEFVAR и DEFPARAMETER вы можете создать глобальную (динамческой видимости)
|
|||
|
;;; переменную.
|
|||
|
;;; Имя переменной может состоять из любых символов кроме: ()",'`;#|\
|
|||
|
|
|||
|
;;; Разница между DEFVAR и DEFPARAMETER в том, что повторное выполнение DEFVAR
|
|||
|
;;; переменную не поменяет. А вот DEFPARAMETER меняет переменную при каждом вызове.
|
|||
|
|
|||
|
;;; Обычно глобальные (динамически видимые) переменные содержат звездочки в имени.
|
|||
|
|
|||
|
(defparameter *some-var* 5)
|
|||
|
*some-var* ; => 5
|
|||
|
|
|||
|
;;; Можете использовать unicode.
|
|||
|
(defparameter *КУКУ* nil)
|
|||
|
|
|||
|
;;; Доступ к необъявленной переменной - это непредсказуемое поведение. Не делайте так.
|
|||
|
|
|||
|
;;; С помощью LET можете сделать локальное связывание.
|
|||
|
;;; В следующем куске кода, `я` связывается с "танцую с тобой" только
|
|||
|
;;; внутри формы (let ...). LET всегда возвращает значение последней формы.
|
|||
|
|
|||
|
(let ((я "танцую с тобой")) я) ; => "танцую с тобой"
|
|||
|
|
|||
|
|
|||
|
;;;-----------------------------------------------------------------------------;
|
|||
|
;;; 3. Структуры и коллекции
|
|||
|
;;;-----------------------------------------------------------------------------;
|
|||
|
|
|||
|
|
|||
|
;;; Структуры
|
|||
|
|
|||
|
(defstruct dog name breed age)
|
|||
|
(defparameter *rover*
|
|||
|
(make-dog :name "rover"
|
|||
|
:breed "collie"
|
|||
|
:age 5))
|
|||
|
*rover* ; => #S(DOG :NAME "rover" :BREED "collie" :AGE 5)
|
|||
|
(dog-p *rover*) ; => T
|
|||
|
(dog-name *rover*) ; => "rover"
|
|||
|
|
|||
|
;;; DEFSTRUCT автоматически создала DOG-P, MAKE-DOG, и DOG-NAME
|
|||
|
|
|||
|
|
|||
|
;;; Пары (cons-ячейки)
|
|||
|
|
|||
|
;;; CONS создаёт пары. CAR и CDR возвращают начало и конец CONS-пары.
|
|||
|
|
|||
|
(cons 'SUBJECT 'VERB) ; => '(SUBJECT . VERB)
|
|||
|
(car (cons 'SUBJECT 'VERB)) ; => SUBJECT
|
|||
|
(cdr (cons 'SUBJECT 'VERB)) ; => VERB
|
|||
|
|
|||
|
|
|||
|
;;; Списки
|
|||
|
|
|||
|
;;; Списки это связанные CONS-пары, в конце самой последней из которых стоит NIL
|
|||
|
;;; (или '() ).
|
|||
|
|
|||
|
(cons 1 (cons 2 (cons 3 nil))) ; => '(1 2 3)
|
|||
|
|
|||
|
;;; Списки с произвольным количеством элементов удобно создавать с помощью LIST
|
|||
|
|
|||
|
(list 1 2 3) ; => '(1 2 3)
|
|||
|
|
|||
|
;;; Если первый аргумент для CONS это атом и второй аргумент список, CONS
|
|||
|
;;; возвращает новую CONS-пару, которая представляет собой список
|
|||
|
|
|||
|
(cons 4 '(1 2 3)) ; => '(4 1 2 3)
|
|||
|
|
|||
|
;;; Чтобы объединить списки, используйте APPEND
|
|||
|
|
|||
|
(append '(1 2) '(3 4)) ; => '(1 2 3 4)
|
|||
|
|
|||
|
;;; Или CONCATENATE
|
|||
|
|
|||
|
(concatenate 'list '(1 2) '(3 4)) ; => '(1 2 3 4)
|
|||
|
|
|||
|
;;; Списки это самый используемый элемент языка. Поэтому с ними можно делать
|
|||
|
;;; многие вещи. Вот несколько примеров:
|
|||
|
|
|||
|
(mapcar #'1+ '(1 2 3)) ; => '(2 3 4)
|
|||
|
(mapcar #'+ '(1 2 3) '(10 20 30)) ; => '(11 22 33)
|
|||
|
(remove-if-not #'evenp '(1 2 3 4)) ; => '(2 4)
|
|||
|
(every #'evenp '(1 2 3 4)) ; => NIL
|
|||
|
(some #'oddp '(1 2 3 4)) ; => T
|
|||
|
(butlast '(subject verb object)) ; => (SUBJECT VERB)
|
|||
|
|
|||
|
|
|||
|
;;; Вектора
|
|||
|
|
|||
|
;;; Вектора заданные прямо в коде - это массивы с фиксированной длинной.
|
|||
|
|
|||
|
#(1 2 3) ; => #(1 2 3)
|
|||
|
|
|||
|
;;; Для соединения векторов используйте CONCATENATE
|
|||
|
|
|||
|
(concatenate 'vector #(1 2 3) #(4 5 6)) ; => #(1 2 3 4 5 6)
|
|||
|
|
|||
|
|
|||
|
;;; Массивы
|
|||
|
|
|||
|
;;; И вектора и строки это подмножества массивов.
|
|||
|
|
|||
|
;;; Двухмерные массивы
|
|||
|
|
|||
|
(make-array (list 2 2)) ; => #2A((0 0) (0 0))
|
|||
|
(make-array '(2 2)) ; => #2A((0 0) (0 0))
|
|||
|
(make-array (list 2 2 2)) ; => #3A(((0 0) (0 0)) ((0 0) (0 0)))
|
|||
|
|
|||
|
;;; Внимание: значение по-умолчанию элемента массива зависит от реализации.
|
|||
|
;;; Лучше явно указывайте:
|
|||
|
|
|||
|
(make-array '(2) :initial-element 'unset) ; => #(UNSET UNSET)
|
|||
|
|
|||
|
;;; Для доступа к элементу в позиции 1, 1, 1:
|
|||
|
|
|||
|
(aref (make-array (list 2 2 2)) 1 1 1) ; => 0
|
|||
|
|
|||
|
|
|||
|
;;; Вектора с изменяемой длиной
|
|||
|
|
|||
|
;;; Вектора с изменяемой длиной при выводе на консоль выглядят также,
|
|||
|
;;; как и вектора, с константной длиной
|
|||
|
|
|||
|
(defparameter *adjvec* (make-array '(3) :initial-contents '(1 2 3)
|
|||
|
:adjustable t :fill-pointer t))
|
|||
|
*adjvec* ; => #(1 2 3)
|
|||
|
|
|||
|
;;; Добавление новых элементов
|
|||
|
|
|||
|
(vector-push-extend 4 *adjvec*) ; => 3
|
|||
|
*adjvec* ; => #(1 2 3 4)
|
|||
|
|
|||
|
|
|||
|
;;; Множества, это просто списки:
|
|||
|
|
|||
|
(set-difference '(1 2 3 4) '(4 5 6 7)) ; => (3 2 1)
|
|||
|
(intersection '(1 2 3 4) '(4 5 6 7)) ; => 4
|
|||
|
(union '(1 2 3 4) '(4 5 6 7)) ; => (3 2 1 4 5 6 7)
|
|||
|
(adjoin 4 '(1 2 3 4)) ; => (1 2 3 4)
|
|||
|
|
|||
|
;;; Несмотря на все, для действительно больших объемов данных, вам нужно что-то
|
|||
|
;;; лучше, чем просто связанные списки
|
|||
|
|
|||
|
;;; Словари представлены хеш таблицами.
|
|||
|
|
|||
|
;;; Создание хеш таблицы:
|
|||
|
|
|||
|
(defparameter *m* (make-hash-table))
|
|||
|
|
|||
|
;;; Установка пары ключ-значение
|
|||
|
|
|||
|
(setf (gethash 'a *m*) 1)
|
|||
|
|
|||
|
;;; Возврат значения по ключу
|
|||
|
|
|||
|
(gethash 'a *m*) ; => 1, T
|
|||
|
|
|||
|
;;; CL выражения умеют возвращать сразу несколько значений.
|
|||
|
|
|||
|
(values 1 2) ; => 1, 2
|
|||
|
|
|||
|
;;; которые могут быть распределены по переменным с помощью MULTIPLE-VALUE-BIND
|
|||
|
|
|||
|
(multiple-value-bind (x y)
|
|||
|
(values 1 2)
|
|||
|
(list y x))
|
|||
|
|
|||
|
; => '(2 1)
|
|||
|
|
|||
|
;;; GETHASH как раз та функция, которая возвращает несколько значений. Первое
|
|||
|
;;; значение - это значение по ключу в хеш таблицу. Если ключ не был найден,
|
|||
|
;;; возвращает NIL.
|
|||
|
|
|||
|
;;; Второе возвращаемое значение, указывает был ли ключ в хеш таблице. Если ключа
|
|||
|
;;; не было, то возвращает NIL. Таким образом можно проверить, это значение
|
|||
|
;;; NIL, или ключа просто не было.
|
|||
|
|
|||
|
;;; Вот возврат значений, в случае когда ключа в хеш таблице не было:
|
|||
|
|
|||
|
(gethash 'd *m*) ;=> NIL, NIL
|
|||
|
|
|||
|
;;; Можете задать значение по умолчанию.
|
|||
|
|
|||
|
(gethash 'd *m* :not-found) ; => :NOT-FOUND
|
|||
|
|
|||
|
;;; Давайте обработаем возврат несколько значений.
|
|||
|
|
|||
|
(multiple-value-bind (a b)
|
|||
|
(gethash 'd *m*)
|
|||
|
(list a b))
|
|||
|
; => (NIL NIL)
|
|||
|
|
|||
|
(multiple-value-bind (a b)
|
|||
|
(gethash 'a *m*)
|
|||
|
(list a b))
|
|||
|
; => (1 T)
|
|||
|
|
|||
|
|
|||
|
;;;-----------------------------------------------------------------------------
|
|||
|
;;; 3. Функции
|
|||
|
;;;-----------------------------------------------------------------------------
|
|||
|
|
|||
|
;;; Для создания анонимных функций используйте LAMBDA. Функций всегда возвращают
|
|||
|
;;; значение последнего своего выражения. Как выглядит функция при выводе в консоль
|
|||
|
;;; зависит от реализации.
|
|||
|
|
|||
|
(lambda () "Привет Мир") ; => #<FUNCTION (LAMBDA ()) {1004E7818B}>
|
|||
|
|
|||
|
;;; Для вызова анонимной функции пользуйтесь FUNCALL
|
|||
|
|
|||
|
(funcall (lambda () "Привет Мир")) ; => "Привет мир"
|
|||
|
(funcall #'+ 1 2 3) ; => 6
|
|||
|
|
|||
|
;;; FUNCALL сработает и тогда, когда анонимная функция стоит в начале
|
|||
|
;;; неэкранированного списка
|
|||
|
|
|||
|
((lambda () "Привет Мир")) ; => "Привет Мир"
|
|||
|
((lambda (val) val) "Привет Мир") ; => "Привет Мир"
|
|||
|
|
|||
|
;;; FUNCALL используется, когда аргументы заранее известны.
|
|||
|
;;; В противном случае используйте APPLY
|
|||
|
|
|||
|
(apply #'+ '(1 2 3)) ; => 6
|
|||
|
(apply (lambda () "Привет Мир") nil) ; => "Привет Мир"
|
|||
|
|
|||
|
;;; Для обычной функции с именем используйте DEFUN
|
|||
|
|
|||
|
(defun hello-world () "Привет Мир")
|
|||
|
(hello-world) ; => "Привет Мир"
|
|||
|
|
|||
|
;;; Выше видно пустой список (), это место для определения аргументов
|
|||
|
|
|||
|
(defun hello (name) (format nil "Hello, ~A" name))
|
|||
|
(hello "Григорий") ; => "Привет, Григорий"
|
|||
|
|
|||
|
;;; Можно указать необязательные аргументы. По умолчанию они будут NIL
|
|||
|
|
|||
|
(defun hello (name &optional from)
|
|||
|
(if from
|
|||
|
(format t "Приветствие для ~A от ~A" name from)
|
|||
|
(format t "Привет, ~A" name)))
|
|||
|
|
|||
|
(hello "Георгия" "Василия") ; => Приветствие для Георгия от Василия
|
|||
|
|
|||
|
;;; Можно явно задать значения по умолчанию
|
|||
|
|
|||
|
(defun hello (name &optional (from "Мира"))
|
|||
|
(format nil "Приветствие для ~A от ~A" name from))
|
|||
|
|
|||
|
(hello "Жоры") ; => Приветствие для Жоры от Мира
|
|||
|
(hello "Жоры" "альпаки") ; => Приветствие для Жоры от альпаки
|
|||
|
|
|||
|
;;; Можно также задать именованные параметры
|
|||
|
|
|||
|
(defun generalized-greeter (name &key (from "Мира") (honorific "Господин"))
|
|||
|
(format t "Здравствуйте, ~A ~A, от ~A" honorific name from))
|
|||
|
|
|||
|
(generalized-greeter "Григорий")
|
|||
|
; => Здравствуйте, Господин Григорий, от Мира
|
|||
|
|
|||
|
(generalized-greeter "Григорий" :from "альпаки" :honorific "гражданин")
|
|||
|
; => Здравствуйте, Гражданин Григорий, от альпаки
|
|||
|
|
|||
|
|
|||
|
;;;-----------------------------------------------------------------------------
|
|||
|
;;; 4. Равенство или эквивалентность
|
|||
|
;;;-----------------------------------------------------------------------------
|
|||
|
|
|||
|
;;; У CL сложная система эквивалентности. Взглянем одним глазом.
|
|||
|
|
|||
|
;;; Для чисел используйте `='
|
|||
|
(= 3 3.0) ; => T
|
|||
|
(= 2 1) ; => NIL
|
|||
|
|
|||
|
;;; Для идентичености объектов используйте EQL
|
|||
|
(eql 3 3) ; => T
|
|||
|
(eql 3 3.0) ; => NIL
|
|||
|
(eql (list 3) (list 3)) ; => NIL
|
|||
|
|
|||
|
;;; Для списков, строк, и битовых векторов - EQUAL
|
|||
|
(equal (list 'a 'b) (list 'a 'b)) ; => T
|
|||
|
(equal (list 'a 'b) (list 'b 'a)) ; => NIL
|
|||
|
|
|||
|
|
|||
|
;;;-----------------------------------------------------------------------------
|
|||
|
;;; 5. Циклы и ветвления
|
|||
|
;;;-----------------------------------------------------------------------------
|
|||
|
|
|||
|
;;; Ветвления
|
|||
|
|
|||
|
(if t ; проверямое значение
|
|||
|
"случилась истина" ; если, оно было истинно
|
|||
|
"случилась ложь") ; иначе, когда оно было ложно
|
|||
|
; => "случилась истина"
|
|||
|
|
|||
|
;;; В форме ветвления if, все не-NIL значения это `истина`
|
|||
|
|
|||
|
(member 'Groucho '(Harpo Groucho Zeppo)) ; => '(GROUCHO ZEPPO)
|
|||
|
(if (member 'Groucho '(Harpo Groucho Zeppo))
|
|||
|
'yep
|
|||
|
'nope)
|
|||
|
; => 'YEP
|
|||
|
|
|||
|
;;; COND это цепочка проверок для нахождения искомого
|
|||
|
(cond ((> 2 2) (error "мимо!"))
|
|||
|
((< 2 2) (error "опять мимо!"))
|
|||
|
(t 'ok)) ; => 'OK
|
|||
|
|
|||
|
;;; TYPECASE выбирает ветку исходя из типа выражения
|
|||
|
(typecase 1
|
|||
|
(string :string)
|
|||
|
(integer :int))
|
|||
|
; => :int
|
|||
|
|
|||
|
|
|||
|
;;; Циклы
|
|||
|
|
|||
|
;;; С рекурсией
|
|||
|
|
|||
|
(defun fact (n)
|
|||
|
(if (< n 2)
|
|||
|
1
|
|||
|
(* n (fact(- n 1)))))
|
|||
|
|
|||
|
(fact 5) ; => 120
|
|||
|
|
|||
|
;;; И без
|
|||
|
|
|||
|
(defun fact (n)
|
|||
|
(loop :for result = 1 :then (* result i)
|
|||
|
:for i :from 2 :to n
|
|||
|
:finally (return result)))
|
|||
|
|
|||
|
(fact 5) ; => 120
|
|||
|
|
|||
|
(loop :for x :across "abc" :collect x)
|
|||
|
; => (#\a #\b #\c #\d)
|
|||
|
|
|||
|
(dolist (i '(1 2 3 4))
|
|||
|
(format t "~A" i))
|
|||
|
; => 1234
|
|||
|
|
|||
|
|
|||
|
;;;-----------------------------------------------------------------------------
|
|||
|
;;; 6. Установка значений в переменные (и не только)
|
|||
|
;;;-----------------------------------------------------------------------------
|
|||
|
|
|||
|
;;; Для присвоения переменной нового значения используйте SETF. Это уже было
|
|||
|
;;; при работе с хеш таблицами.
|
|||
|
|
|||
|
(let ((variable 10))
|
|||
|
(setf variable 2))
|
|||
|
; => 2
|
|||
|
|
|||
|
;;; Для функционального подхода в программировании, старайтесь избегать измений
|
|||
|
;;; в переменных.
|
|||
|
|
|||
|
;;;-----------------------------------------------------------------------------
|
|||
|
;;; 7. Классы и объекты
|
|||
|
;;;-----------------------------------------------------------------------------
|
|||
|
|
|||
|
;;; Никаких больше животных в примерах. Берем устройства приводимые в движение
|
|||
|
;;; мускульной силой человека.
|
|||
|
|
|||
|
(defclass human-powered-conveyance ()
|
|||
|
((velocity
|
|||
|
:accessor velocity
|
|||
|
:initarg :velocity)
|
|||
|
(average-efficiency
|
|||
|
:accessor average-efficiency
|
|||
|
:initarg :average-efficiency))
|
|||
|
(:documentation "Устройство движимое человеческой силой"))
|
|||
|
|
|||
|
;;; Аргументы DEFCLASS:
|
|||
|
;;; 1. Имя класса
|
|||
|
;;; 2. Список родительских классов
|
|||
|
;;; 3. Список полей
|
|||
|
;;; 4. Необязательная метаинформация
|
|||
|
|
|||
|
;;; Если родительские классы не заданы, используется "стандартный" класс
|
|||
|
;;; Это можно *изменить*, но хорошенько подумайте прежде. Если все-таки
|
|||
|
;;; решились вам поможет "Art of the Metaobject Protocol"
|
|||
|
|
|||
|
(defclass bicycle (human-powered-conveyance)
|
|||
|
((wheel-size
|
|||
|
:accessor wheel-size
|
|||
|
:initarg :wheel-size
|
|||
|
:documentation "Diameter of the wheel.")
|
|||
|
(height
|
|||
|
:accessor height
|
|||
|
:initarg :height)))
|
|||
|
|
|||
|
(defclass recumbent (bicycle)
|
|||
|
((chain-type
|
|||
|
:accessor chain-type
|
|||
|
:initarg :chain-type)))
|
|||
|
|
|||
|
(defclass unicycle (human-powered-conveyance) nil)
|
|||
|
|
|||
|
(defclass canoe (human-powered-conveyance)
|
|||
|
((number-of-rowers
|
|||
|
:accessor number-of-rowers
|
|||
|
:initarg :number-of-rowers)))
|
|||
|
|
|||
|
;;; Если вызвать DESCRIBE для HUMAN-POWERED-CONVEYANCE то получите следующее:
|
|||
|
|
|||
|
(describe 'human-powered-conveyance)
|
|||
|
|
|||
|
; COMMON-LISP-USER::HUMAN-POWERED-CONVEYANCE
|
|||
|
; [symbol]
|
|||
|
;
|
|||
|
; HUMAN-POWERED-CONVEYANCE names the standard-class #<STANDARD-CLASS
|
|||
|
; HUMAN-POWERED-CONVEYANCE>:
|
|||
|
; Documentation:
|
|||
|
; A human powered conveyance
|
|||
|
; Direct superclasses: STANDARD-OBJECT
|
|||
|
; Direct subclasses: UNICYCLE, BICYCLE, CANOE
|
|||
|
; Not yet finalized.
|
|||
|
; Direct slots:
|
|||
|
; VELOCITY
|
|||
|
; Readers: VELOCITY
|
|||
|
; Writers: (SETF VELOCITY)
|
|||
|
; AVERAGE-EFFICIENCY
|
|||
|
; Readers: AVERAGE-EFFICIENCY
|
|||
|
; Writers: (SETF AVERAGE-EFFICIENCY)
|
|||
|
|
|||
|
;;; CL задизайнен как интерактивная система. В рантайме доступна информация о
|
|||
|
;;; типе объектов.
|
|||
|
|
|||
|
;;; Давайте посчитаем расстояние, которое пройдет велосипед за один оборот колеса
|
|||
|
;;; по формуле C = d * pi
|
|||
|
|
|||
|
(defmethod circumference ((object bicycle))
|
|||
|
(* pi (wheel-size object)))
|
|||
|
|
|||
|
;;; PI - это константа в CL
|
|||
|
|
|||
|
;;; Предположим мы нашли, что критерий эффективности логарифмически связан
|
|||
|
;;; с гребцами каноэ. Тогда вычисление можем сделать сразу при инициализации.
|
|||
|
|
|||
|
;;; Инициализируем объект после его создания:
|
|||
|
|
|||
|
(defmethod initialize-instance :after ((object canoe) &rest args)
|
|||
|
(setf (average-efficiency object) (log (1+ (number-of-rowers object)))))
|
|||
|
|
|||
|
|
|||
|
;;; Давайте проверим что получилось с этой самой эффективностью...
|
|||
|
|
|||
|
(average-efficiency (make-instance 'canoe :number-of-rowers 15))
|
|||
|
; => 2.7725887
|
|||
|
|
|||
|
|
|||
|
;;;-----------------------------------------------------------------------------
|
|||
|
;;; 8. Макросы
|
|||
|
;;;-----------------------------------------------------------------------------
|
|||
|
|
|||
|
;;; Макросы позволяют расширить синаксис языка. В CL нет например цикла WHILE,
|
|||
|
;;; но его проще простого реализовать на макросах. Если мы отбросим наши
|
|||
|
;;; ассемблерные (или алгольные) инстинкты, мы взлетим на крыльях:
|
|||
|
|
|||
|
(defmacro while (condition &body body)
|
|||
|
"Пока `условие` истинно, выполняется `тело`.
|
|||
|
`Условие` проверяется перед каждым выполнением `тела`"
|
|||
|
(let ((block-name (gensym)) (done (gensym)))
|
|||
|
`(tagbody
|
|||
|
,block-name
|
|||
|
(unless ,condition
|
|||
|
(go ,done))
|
|||
|
(progn
|
|||
|
,@body)
|
|||
|
(go ,block-name)
|
|||
|
,done)))
|
|||
|
|
|||
|
;;; Взглянем на более высокоуровневую версию этого макроса:
|
|||
|
|
|||
|
(defmacro while (condition &body body)
|
|||
|
"Пока `условие` истинно, выполняется `тело`.
|
|||
|
`Условие` проверяется перед каждым выполнением `тела`"
|
|||
|
`(loop while ,condition
|
|||
|
do
|
|||
|
(progn
|
|||
|
,@body)))
|
|||
|
|
|||
|
;;; В современных комиляторах LOOP так же эффективен как и приведенный
|
|||
|
;;; выше код. Поэтому используйте его, его проще читать.
|
|||
|
|
|||
|
;;; В макросах используются символы ```, `,` и `@`. ``` - это оператор
|
|||
|
;;; квазиквотирования - это значит что форма исполнятся не будет, а вернется
|
|||
|
;;; как данные. Оператаор `,` позволяет исполнить форму внутри
|
|||
|
;;; квазиквотирования. Оператор `@` исполняет форму внутри квазиквотирования
|
|||
|
;;; но полученный список вклеивает по месту.
|
|||
|
|
|||
|
;;; GENSYM создаёт уникальный символ, который гарантировано больше нигде в
|
|||
|
;;; системе не используется. Так надо потому, что макросы разворачиваются
|
|||
|
;;; во время компиляции и переменные объявленные в макросе могут совпасть
|
|||
|
;;; по имени с переменными в обычном коде.
|
|||
|
|
|||
|
;;; Дальнйешую информацию о макросах ищите в книгах Practical Common Lisp
|
|||
|
;;; и On Lisp
|
|||
|
```
|
|||
|
|
|||
|
## Для чтения
|
|||
|
|
|||
|
На русском
|
|||
|
- [Practical Common Lisp](http://www.gigamonkeys.com/book/)
|
|||
|
|
|||
|
На английском
|
|||
|
- [Practical Common Lisp](http://www.gigamonkeys.com/book/)
|
|||
|
- [Common Lisp: A Gentle Introduction to Symbolic Computation](https://www.cs.cmu.edu/~dst/LispBook/book.pdf)
|
|||
|
|
|||
|
|
|||
|
## Дополнительная информация
|
|||
|
|
|||
|
На русском
|
|||
|
|
|||
|
- [Lisper.ru](http://lisper.ru/)
|
|||
|
|
|||
|
На английском
|
|||
|
|
|||
|
- [CLiki](http://www.cliki.net/)
|
|||
|
- [common-lisp.net](https://common-lisp.net/)
|
|||
|
- [Awesome Common Lisp](https://github.com/CodyReichert/awesome-cl)
|
|||
|
- [Lisp Lang](http://lisp-lang.org/)
|
|||
|
|
|||
|
|
|||
|
## Благодарности в английской версии
|
|||
|
|
|||
|
Спасибо людям из Scheme за отличную статью, взятую за основу для
|
|||
|
Common Lisp.
|
|||
|
|
|||
|
|
|||
|
- [Paul Khuong](https://github.com/pkhuong) за хорошую вычитку.
|