summaryrefslogtreecommitdiffhomepage
path: root/ru-ru/common-lisp-ru.html.markdown
diff options
context:
space:
mode:
authorMichael Filonenko <filonenko.mikhail@gmail.com>2018-04-15 20:46:42 +0300
committerMichael Filonenko <filonenko.mikhail@gmail.com>2018-04-15 20:46:42 +0300
commit3fd6697c5eba4cb4fd6aff8ac58109d2cd79dbbb (patch)
tree73d767cfcf86119dd9bdf455e30d8ac59365aaec /ru-ru/common-lisp-ru.html.markdown
parent71389bd42374df02504c3eac31491672ce894ab5 (diff)
Common Lisp: ru-ru
Diffstat (limited to 'ru-ru/common-lisp-ru.html.markdown')
-rw-r--r--ru-ru/common-lisp-ru.html.markdown704
1 files changed, 704 insertions, 0 deletions
diff --git a/ru-ru/common-lisp-ru.html.markdown b/ru-ru/common-lisp-ru.html.markdown
new file mode 100644
index 00000000..d5f9bf0e
--- /dev/null
+++ b/ru-ru/common-lisp-ru.html.markdown
@@ -0,0 +1,704 @@
+---
+
+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) за хорошую вычитку.