├── .gitignore ├── 1-intro.md ├── 2-syntax.md ├── 3-macros.md ├── 4-datastructures.md ├── README.md ├── img ├── array.gif ├── collection.jpg ├── compiler-pipeline.jpg ├── dna-seq.jpg ├── dynamic-array.png ├── emacs.jpg ├── emacs.png ├── eval-apply.gif ├── hash-table.png ├── hyperspec.gif ├── linked-list.png ├── ohm.jpg ├── spel-illustration.jpg ├── stmt-vs-expr.gif ├── string.jpg ├── struct.png ├── unwrap.jpg └── yoda.jpg ├── lang-detect ├── calc.lisp ├── lang-detect.asd ├── package.lisp ├── stats.lisp ├── storage.lisp └── test.lisp └── lit.md /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | #* 3 | .#* 4 | .* 5 | !.gitignore 6 | -------------------------------------------------------------------------------- /1-intro.md: -------------------------------------------------------------------------------- 1 | # Как начать? 2 | 3 | ## Почему Lisp? 4 | 5 | - Культура 6 | - Интерактивность 7 | - Простота 8 | - Гибкость (мультипарадигменный + глубоко изменяемый) 9 | - Стабильность и зрелость 10 | - Подходит для очень широкого круга людей: от художников до астрономов 11 | - Светлая сторона силы 12 | 13 | ![yoda](img/yoda.jpg) 14 | 15 | (c) http://raphaelgbr.deviantart.com/art/Yoda-Cartoon-100020385 16 | 17 | 18 | ## Стандарт 19 | 20 | ![Hyperspec](img/hyperspec.gif) 21 | 22 | 1985-1994 23 | 24 | [Hyperspec](http://www.lispworks.com/documentation/HyperSpec/Front/index.htm) (см. также [hyperspec.el](http://www.emacswiki.org/emacs/CommonLispHyperspec)) 25 | 26 | [Quick Reference](http://clqr.boundp.org/) 27 | 28 | [ANSI CL на русском](http://www.books.ru/books/ansi-common-lisp-fail-3127808/) Пола Грема — годный обзор 29 | 30 | 31 | ## Реализации 32 | 33 | ![Реализации](http://sbcl.org/sbclbutton.png) 34 | 35 | Более 20, из которых около 8 активно поддерживаются и развиваются 36 | 37 | В первую очередь: SBCL (на Linux) и CCL (на Mac & Win) 38 | 39 | Также: 40 | 41 | - CLisp — поиграться 42 | - ABCL — на JVM, хорошо развивается 43 | - LispWorks — крутая, но дорогая, включает IDE, кросс-платформенный графический фреймворк и многое другое 44 | 45 | 46 | ## IDE 47 | 48 | ![Emacs](img/emacs.jpg) 49 | 50 | - Notepad (+CLisp) 51 | - [Able](http://common-lisp.net/project/able/) 52 | - Emacs + [SLIME](http://www.common-lisp.net/project/slime/) 53 | - Vim + [SLIMV](http://www.vim.org/scripts/script.php?script_id=2531) 54 | - [LispWorks IDE](http://www.lispworks.com/) 55 | - [MCLIDE](http://mclide.com/) 56 | - [LispIDE](http://www.daansystems.com/lispide/) 57 | 58 | 59 | ## Библиотеки 60 | 61 | ![Xach](http://img.photobucket.com/albums/v473/pufpuf/xach.jpg) 62 | 63 | [Quicklisp](http://www.quicklisp.org/) — проще простого 64 | 65 | [Quickdocs](http://quickdocs.org/) — документация по всем библиотекам в Quicklisp 66 | 67 | [CLiki](http://cliki.net/) — Open Source Lisp Wiki 68 | 69 | 70 | ## Книги и руководства 71 | 72 | ![PCL](http://www.gigamonkeys.com/book/small-cover.gif) 73 | 74 | Начинайте с [Practical Common Lisp](http://gigamonkeys.com/book) — для инженеров, или же с [Land of Lisp]() — для творческих личностей 75 | 76 | Простые рецепты — [CL Cookbook](http://cl-cookbook.sourceforge.net/), слегка устаревшие 77 | 78 | Вопросы стиля — [Google Common Lisp Style Guide](https://google.github.io/styleguide/lispguide.xml), [Tutorial on Good Lisp Style]() 79 | 80 | Код из книг — [lispdoc](http://lispdoc.com/) 81 | 82 | Спрашивайте на Stack Overflow 83 | 84 | Продвинутые книги: 85 | 86 | - [Paradigms of Artificial Intelligence Programming](http://norvig.com/paip.html) 87 | от Питера Норвина 88 | - [Structure and Interpretation of Computer Programs](http://mitpress.mit.edu/sicp/) из MIT 89 | - [On Lisp](http://www.paulgraham.com/onlisp.html) от Пола Грема 90 | - [Let over Lambda](http://letoverlambda.com/) — развитие идей "On Lisp" 91 | 92 | И, вообще: [Lisp Books](http://www.pinterest.com/vseloved/lisp-books/) 93 | 94 | 95 | ## Задание 96 | 97 | 1. Установить одну из реализаций Common Lisp (рекомендую CCL). 98 | 2. Установить Quicklisp. 99 | 3. Выбрать редактор, если вы еще не пользуетесь одной из указанных опций 100 | (рекомендую Emacs). Если нужно, то научиться им пользоваться на базовом уровне. 101 | Если нужно, настроить среду взаимодействия с Lisp'ом. Создать файл `hello.lisp`, 102 | в котором написать `(print "hello world")` и, используя Lisp-среду, его выполнить 103 | (например, с помощью `(load "hello.lisp")`). Увидеть в консоли надпись `hello world`. 104 | 4. Выбрать одну из названных книг и прочитать одну главу из нее. 105 | Кратко описать, какие концепции в ней оказались новыми, 106 | неожиданными, понравились или не понравились, и почему. 107 | -------------------------------------------------------------------------------- /2-syntax.md: -------------------------------------------------------------------------------- 1 | # Синтаксис 2 | 3 | ## Литералы 4 | 5 | ![Атом](http://upload.wikimedia.org/wikipedia/commons/thumb/e/e2/Stylised_Lithium_Atom.png/200px-Stylised_Lithium_Atom.png) 6 | 7 | - Числа: `1`, `0.1`, `1e-1`, `1/10`, `#c(0 -1)` 8 | - Буквы: `#\a`, `#\ї`, `#\Space` 9 | - Строки: `"hello world"` 10 | - Массивы: `#(1 2 3)`, `#2A((1 0) (0 1))` 11 | - Символы: `foo`, `foo:bar`, `foo::bar`, `*standard-output*`, `:bar` (ключи) 12 | - ... 13 | 14 | `#` — спецсимвол, который обычно вводит особый синтаксис для записи литералов. Еще примеры: `#p"/home/user/hello.lisp"` — литерал пути к файлу. 15 | 16 | 17 | ## Список как способ представления кода 18 | 19 | ![Cons cell](http://upload.wikimedia.org/wikipedia/commons/thumb/1/1b/Cons-cells.svg/350px-Cons-cells.svg.png) 20 | 21 | (голова . хвост) - т.н. конс-ячейка 22 | 23 | (print "hello world" *standard-output*) => голова: print, хвост: ("hello world" *standard-output*) 24 | 25 | (1 2) => голова: 1, хвост: (2) 26 | 27 | (1 . 2) => голова: 1, хвост: 2 28 | 29 | Круглые скобочки и пробел — единственные разделители. (Не считая точки, которая не нужна). 30 | 31 | (print "hello world" *standard-output*) 32 | \ | | | | 33 | \-+--\ | | /---------/ 34 | | | | | | 35 | v v v v v 36 | print("hello world", STDOUT); 37 | x x 38 | 39 | x x 40 | (+ 2 2) 41 | \ / | 42 | x | 43 | / \ | 44 | v v v 45 | 2 + 2 46 | 47 | x x 48 | (< a b c) 49 | \ / | | 50 | x | | 51 | / \ | | 52 | v v v v 53 | a > b && b < c 54 | xx x x 55 | 56 | Вложенные списки — это деревья кода (т.н. абстрактные синтаксические деревья — **AST**) — таким образом код представляется для компилятора любого ЯП: 57 | 58 | (print (+ 2 2)) 59 | ^ ^ ^ ^ 60 | | | | | 61 | | | листья 62 | | корень поддерева 63 | корень дерева 64 | 65 | print 66 | | 67 | | 68 | + 69 | / \ 70 | / \ 71 | 2 2 72 | 73 | Исполняемый код: 74 | 75 | CL-USER> (print "hello world" *standard-output*) 76 | hello world 77 | "hello world" 78 | CL-USER> '(print "hello world" *standard-output*) 79 | (PRINT "hello world" *STANDARD-OUTPUT*) 80 | 81 | `(...)` — код, `'(...)` — литерал. **Код — это данные, данные — это код.** 82 | 83 | 84 | ## Символы 85 | 86 | ![Ом](img/ohm.jpg) 87 | 88 | Имена в Lisp'е обозначаются символами. В отличие от большинства ЯП (в которых символы доступны только компилятору, а программа может манипулировать ими только в определенных синтаксических рамках), символы в Lisp'е — это **первоклассный объект**, доступный во время выполнения программы. 89 | 90 | Символ — это интернированная строка и набор свойств. 91 | 92 | **Символические вычисления**. 93 | 94 | Common Lisp относится к т.н. `Lisp-2` семейству, в котором символы могут одновременно обозначать объекты в разных пространствах имен: переменных, функций, типов, классов и т.д. Т.е. функция `print` может одновременно быть и переменной `print` и типом `print`. 95 | 96 | Символы существуют не сами по себе, а относятся к **пакетам**. Запись `common-lisp:print` — это полное имя публичного символа `print` в пакете `common-lisp`. Также в пакете могут быть неэкспортируемые (приватные) символы, которые доступны так: `common-lisp::print`. А еще есть символы в пакете `keyword` — они доступны из любого пакета по сокращенному имени `:key`. 97 | 98 | Lisp reader читает всё в каком-то пакете, который является пакетом по-умолчанию на данный момент. Например, следующий код выполняется в пакете `CL-USER`, поэтому символ `foo`, который мы видим впервые, создается в пакете `cl-user` (т.е. `cl-user::foo`). 99 | 100 | CL-USER> (defvar foo 1) 101 | 102 | 103 | ## Задание 104 | 105 | 1. Перечислите, какие еще литералы в Lisp'е вам встречались, 106 | или найдити какие-то литералы. 107 | 2. Почему в этой форме требуется использовать оператор `progn`? 108 | Можно ли обойтись без него и как? 109 | 110 | (if (some-condition-holds) 111 | (progn (do-this) 112 | (do-that)) 113 | (dont-do-it)) 114 | 115 | 3. Отобразите в Lisp нотации следующую комбинацию логических условий: 116 | 117 | isShort(sentence) && (isEnglish(sentence) || (isLowerCase(sentence.start()) && !matches(sentence))) 118 | 119 | 4. Отобразите в Lisp нотации следующее дерево синтаксического разбора предложения: 120 | 121 | TOP 122 | : 123 | S 124 | .-----------:-------------. 125 | NP VP |.| 126 | : .--------. : 127 | DT VB NP "." 128 | : : .-----. 129 | "This" "is" DT NN 130 | : : 131 | "a" "test" 132 | -------------------------------------------------------------------------------- /3-macros.md: -------------------------------------------------------------------------------- 1 | # Макросы 2 | 3 | ## Выражения vs утверждения 4 | 5 | ![Выражения и утверждения](img/stmt-vs-expr.gif) 6 | 7 | Выражения (expressions) — код, который в результате выполнения возвращает значение 8 | и может быть использован справа в операторе присваивания. Пример: `2 + 2`, `foo(bar)`. 9 | 10 | Утверждения (statements) — код, который только выплняется, но не возвращает значения 11 | и не может быть использован в присваивании. Во многих языках все операторы управления являются утверждениями. Пример: 12 | ``` 13 | if True: 14 | foo() 15 | else 16 | bar() 17 | ``` 18 | 19 | В Lisp'е всё — выражение. Это кардинально упрощает очень многие вещи и является одной из предпосылок для активного использования макросов для определения собственных управляющих конструкций. Вторая предпосылка — функции как первоклассный объект. 20 | 21 | 22 | ## Правило исполнения кода 23 | 24 | ![Eval/apply](img/eval-apply.gif) 25 | 26 | В Lisp'е довольно четко определены 3 "времени" (режима) работы программы: 27 | 28 | - время выполнения (runtime) — обычный режим исполнения программ — такой же, как и для програм на других языках 29 | - время компиляции — время, когда работает компилятор. В отличие от других языков, в Lisp'е есть интерфейс управления работой системой в этот период 30 | - время загрузки кода 31 | 32 | Стандартное правило исполнения кода в Lisp'е, которое работает во время выполнения — форма `(fn arg1 arg2 ...)` вычисляется следующим образом: 33 | 34 | 1. Сначала вычисляются аргументы `arg1`, `arg2` и т.д. (слева-направо): если это атомы — то сами в себя, если переменные — то берется их значение, если формы — вычисляются по тому же правилу. 35 | 2. Затем к ним применяется функция, которая привязана к символу `fn`. 36 | 37 | Однако, стандартное правило работает не всегда. На самом деле, действует более общее правило: 38 | 39 | 1. Сначало определяется, что находится в первой позиции формы. Это может быть: функция, макрос или базовый оператор (`special-operator`). 40 | 2. Для функций применяется стандартное правило. 41 | 3. Базовые операторы — это 25 операторов, на которых построен весь язык. Условно говоря, это аксиомы языка, которые реализуются уже в более низкоуровневом коде. На них строятся все остальные конструкции языка. Правило выполнения у каждого базового оператора — своё. 42 | 4. А для макросов действует такое правило: на место вызова макроса подставляется код, который формируется макросом, а аргументы вызова передаются в этот код без предварительного вычисления. 43 | 44 | Т.е. **макрос** — это конструкция, которая задает преобразование одного кода в другой. Компилятор/интерпретатор любого языка программирования выполняет эту функцию — преобразования кода на этом языке в код (обычно) на другом языке, который будет исполняться. Макрос — это интерфейс к компилятору, который дает возможность выполнять такие же преобразования наравне с компилятором. 45 | 46 | 47 | ## Макрос как шаблон 48 | 49 | ![Макрос](img/spel-illustration.jpg) 50 | 51 | `if` является базовым оператором. Поверх него реализовано множество других управляющих конструкций: `when`, `unless`, `cond`, `case`/`ecase`, ... 52 | 53 | Макрос `cond` — это `if` с возможностью выполнять последовательную проверку нескольких условий: 54 | ```Lisp 55 | (cond 56 | ((> x 0) 1) 57 | ((> y 1) 2) ; переменные x и y описаны где-то за пределами cond 58 | (t 3)) ; t всегда матчится - это вариант по-умолчанию 59 | ``` 60 | Если выразить этот `cond` через `if`, то он будет выглядеть так: 61 | ```Lisp 62 | (if (> x 0) 1 63 | (if (> y 1) 2 64 | 3)) 65 | ``` 66 | Также его можно выразить в более общем виде, который поддерживает любое 67 | количество условий в `cond`. В данном случае для этого можно использовать рекурсию: 68 | ```Lisp 69 | (if (> x 0) 1 70 | (cond 71 | ((> y 1) 2) 72 | (t 3))) 73 | ``` 74 | Макросы позволяют автоматизировать такое приведение одних форм к другим. 75 | Макрос исполняется в 2 этапа: на этапе компиляции его код вычисляется и результат вставляется в код на месте, где был макрос. 76 | На этапе выполнения кода макроса уже не существует, а исполняется уже сгенерированный код. 77 | 78 | Можно создать макрос `cond`, который будет генерировать приведенный выше код так: 79 | ```Lisp 80 | (defmacro cond1 (&body clauses) 81 | `(if ,(first (first clauses)) ,(second (first clauses)) 82 | (cond1 ,@(rest clauses)))) 83 | ``` 84 | В макросах часто используется конструкция 85 | ```Lisp 86 | `(... ,x ...). 87 | ``` 88 | Ее можно назвать шаблоном кода, в котором участки, отмеченные запятыми, вычисляются сразу. 89 | Это аналог обычного экранированного списка `'(1 2 3)` 90 | с той разницей, что в отличие от простой кавычки (') обратная кавчка (\`) 91 | поддерживает операции "разэкранирования" (,) и "разэкранирования с развертыванием(splicing)" (,@), 92 | которые вычисляют форму, находящуюся за ними. 93 | Таким образом можно создавать шаблоны: \``(list 1 ,x ,y)` — 94 | вычислив его мы получим форму с функцией `list` и тремя аргументами: 95 | константой 1, а также двумя занчениями, которые находятся в переменных `x` и `y`, 96 | опеределенных где-то за пределами шаблона. Если значения `x` и `y` — 2 и 3 соответственно, 97 | то результат вычисления этого шаблона: `(list 1 2 3)`. 98 | А шаблон \``(list 0 ,@x)`, который при условии, что `x` — это список `'(1 2 3)`, 99 | развернется в такой код: `(list 0 1 2 3)`. 100 | 101 | 102 | ## Отладка макросов 103 | 104 | ![Macroexpand](img/unwrap.jpg) 105 | 106 | Поскольку макросы не существуют на этапе выполнения, для их отладки нужны отдельные инструменты. 107 | Функция `macroexpand-1` раскрывает макрос и печатает код, который будет им сгененрирован. 108 | ```Lisp 109 | CL-USER> (defmacro cond1 (&body clauses) 110 | `(if ,(first (first clauses)) ,(second (first clauses)) 111 | (cond1 ,@(rest clauses)))) 112 | CL-USER> (macroexpand-1 '(cond1 113 | ((> x 0) 1) 114 | ((> y 1) 2) 115 | (t 3))) 116 | (IF (> X 0) 117 | 1 118 | (COND1 119 | ((> Y 1) 2) 120 | (T 3))) 121 | 122 | ``` 123 | ## Применения макросов 124 | 125 | ![Компилятор](img/compiler-pipeline.jpg) 126 | 127 | Макросы могут применятся для разнообразных задач, хотя общее правило таково, что не стоит писать макрос там, 128 | где можно обойтись обычной функцией. 129 | 130 | Основные применения: 131 | 132 | - Формирование языка. Сам Common Lisp построен во многом из макросов, включая такие его системы, как CLOS (объектная система). 133 | С помощью макросов можно расширять язык новыми управляющими конструкциями и операторами. 134 | Точно также из макросов можно построить язык альтернативный Common Lisp на платформе самого Lisp'а. 135 | В более практичной сфере это позволяет создавать т.н. DSL'и — специфические языки для какой-то сложной предметной области. 136 | - Автоматизация генерации кода. 137 | - Оптимизация скорости работы программы за счет перенесения части вычсилений на этап компиляции. 138 | 139 | 140 | ## Задание 141 | 142 | 1. Прочитайте: 143 | [Macros: Standard Control Constructs](http://www.gigamonkeys.com/book/macros-standard-control-constructs.html) и 144 | [Macros: Defining Your Own](http://www.gigamonkeys.com/book/macros-defining-your-own.html) 145 | 146 | 2. Макрос `doindex` — это аналог `dolist`, который дополнительно дает доступ к индексу текущего элемента в списке. Например: 147 | 148 | ``` Lisp 149 | CL-USER> (dolist (x '(a b c)) 150 | (print x)) 151 | 152 | A 153 | B 154 | C 155 | ``` 156 | ``` Lisp 157 | CL-USER> (doindex (i x '(a b c)) 158 | (print (list i x))) 159 | (0 A) 160 | (1 B) 161 | (2 C) 162 | ``` 163 | Заполните ... в реализации этого макроса: 164 | ```Lisp 165 | (defmacro doindex ((index element) list) 166 | ...) 167 | ``` 168 | 3. Можно ли в Lisp'е создать макрос-аналог выражения `for` в Python, 169 | который бы позволял итерацию по любой структуре, которая реализует итератор? 170 | Если да, то как (в общих чертах)? 171 | 172 | 4. По желанию прочитайте [Casting SPELs in Lisp](http://www.lisperati.com/casting.html) 173 | -------------------------------------------------------------------------------- /4-datastructures.md: -------------------------------------------------------------------------------- 1 | # Базовые структуры данных 2 | 3 | ## Список 4 | 5 | ![Связный список](img/linked-list.png) 6 | 7 | Список — это базовая структура данных Lisp'а. Это классический связный список. 8 | 9 | Создать список можно как константу с помощью буквального представления: `'(1 2 3)`. 10 | Либо с помощью функции `list`: `(list 1 2 3)`. В этом случае его содержимое можно изменять. 11 | 12 | Добавление элементов в голову списка: `(cons 0 '(1 2 3)) => '(0 1 2 3)`. 13 | Сразу несколько элементов добавлять в начало списка можно с помощью: `(list* 0 1 '(2 3)) => '(0 1 2 3)`. 14 | `(push 0 list)` — этот макрос сделает то же, что и `cons`, и при этом обновит значение переменной `list`. 15 | Сложить 2 списка можно с помощью `(append list1 list2)`, которая создаст новый список с элементами сначала из `list1`, а потом `list2`. 16 | Обращение к элементу списка по номеру — `(nth 1 '(1 2 3)) => 2`. 17 | Первый элемент в списке — `(car list)`, хвост списка — `(cdr list)`. 18 | Это исторические названия, у которых есть "современные" аналоги: `first` и `rest`. 19 | Однако, преимущества `car`/`cdr` в том, что у них есть легкая возможность расширения: 20 | например, `cadr` — первый элемент хвоста списка, т.е. второй элемент списка (`second`), 21 | а `cdddr` — это третий хвост списка, т.е. `(cdddr '(0 1 2 3)) => (3)`. И т.п. 22 | 23 | Большинство операций работы со списком рассматривает его как **функциональную**, т.е. неизменяемую структуру данных. 24 | Однако, есть и т.н. **деструктивные** операции, которые меняет значение в структуре. 25 | Например, операция `(nconc list1 list2)` работает так же, как и `append`, 26 | но она вернет не новый список, а изменит `list1`, присоединив к нему `list2`. 27 | Использование деструктивных операций без особой нужды является дурным тоном. 28 | 29 | На основе списков можно строить другие структуры данных. 30 | В частности, в стандатной библиотеке есть операции для работы с ними как: 31 | 32 | - с множествами (хотя для случая больше 10 элементов для этого лучше использовать хеш-таблицы) - `union`, `intersection`, `set-difference` 33 | - со списками пар (`alist`), состоящими из отдельных cons-ячеек (например, `((:foo . 1) (:bar . 2))`) - `assoc`, `acons`,... 34 | - со списками свойств (`plist`), в которых пермежаются ключи и значения (например, `(:foo 1 :bar 2)`) 35 | - с деревьями — `subst` 36 | 37 | Библиотеки утилит, такие как [Alexandria](http://common-lisp.net/project/alexandria/) и [RUTILS](https://github.com/vseloved/rutils) 38 | добавляют много других полезных операций для работы со списками в этих, а также других видах. 39 | Как пример, ще один вариант использования списков — т.н. `dlist` — это список, в котором первым элементом идет список ключей, а хвостом — 40 | список значений (например, `((:foo :bar) 1 2)`). 41 | 42 | 43 | ## Массив 44 | 45 | ![Матрица](img/dynamic-array.png) 46 | 47 | Lisp поддерживает как одномерные массивы (`vector`), так и многомерные. 48 | Создать массив можно с помощью `make-array`. Для векторов эта функция поддерживает аргумент `fill-pointer`, 49 | который позволяет дописывать элементы в конец вектора с помощью `vector-push-extend` (деструкутивная операция), 50 | у которой есть обратная операция `vector-pop`. 51 | Константный массив можно создать с помощью синтаксиса: `#(1 2 3)` для векторов и `#2A((1 2) (3 4))` для многомерных массивов. 52 | 53 | Доступ к элементам вектора — `svref`. Более общая операция доступа к элементу массива любой размерности — `aref`. 54 | Помимо этого для массивов в стандартной бибилиотеке не описано много специализированных операций — 55 | работа с ними поддерживается через обобщенные операции работы с последовательностями. 56 | 57 | Операция `slice` из `RUTILS` дает возможность создавать срезы вектора 58 | (ссылки на части вектора без необходимости его копировать), 59 | которые можно использовать функционально, если нет необходимости изменять элементы в них. 60 | 61 | 62 | ## Строка 63 | 64 | ![Строка](img/string.jpg) 65 | 66 | Строки в Лиспе являются векторами букв. Запись `"abc"` — это синтаксический сахар для вызова 67 | `(make-array 3 :element-type 'character :initial-contents '(#\a #\b #\c))`. 68 | 69 | Помимо операций над векторами и последовательностями специализированные операции работы со строками включают: 70 | 71 | - `(char str 1)` вернет вторую букву строки 72 | - `string=` и `string-equal` сравнивает строки с учетом и без учета регистра 73 | - `string-downcase`, `string-upcase`, `string-capitalize` 74 | - `(string-trim '(#\Space #\Tab) str)` избавит строку от пробельных символов в начале и конце 75 | 76 | Основной способ формирования строк — операция `(format nil ...)`. 77 | 78 | Некоторое количество операций со строками добавляет библиотека `RUTILS`. 79 | 80 | Для работы с регулярными выражениями на основе строк де-факто стандартом является библиотека [CL-PPCRE](http://weitz.de/cl-ppcre/). 81 | 82 | 83 | ## Последовательности 84 | 85 | ![Последовательность](img/dna-seq.jpg) 86 | 87 | Последовательность — это "интерфейс" структуры данных с последовательным доступом к элементам, 88 | под который подпадают списки и массивы (включая строки). 89 | Для последовательностей описаны обобщенные функции, такие как: 90 | `length`, `elt` (доступ к элементу по индексу), `subseq`, `copy-seq`, `concatenate`, 91 | `map`, `reduce`, `find`, `search`, `position`, `mismatch`, `replace`, `substitute`, 92 | `merge`, `remove`, `remove-duplicates`, `sort`/`stable-sort`. Все они, кроме `sort` являются функциональными. 93 | 94 | 95 | ## Хеш-таблица 96 | 97 | ![Хеш-таблица](img/hash-table.png) 98 | 99 | Хеш-таблица — это таблица ключ-значение, позволяющая по ключу получить доступ к элементу за О(1). 100 | В других языках такая структура называется хеш-мапа (Java), словарь (Python), объект (JavaScript), таблица (Lua). 101 | 102 | Хеш-таблицу можно создать с помощью `make-hash-table`. У каждой таблицы есть `test`-предикат, который тестирует на равенство. 103 | Он может быть одним из четырех стандартных предикатов равенства в Lisp'е: 104 | 105 | - `eq` — самый низкоуровневый предикат — использовать его не стоит 106 | - `eql` тестирует на равенство примитивных типов и объектов по указателю (предикат по-умолчанию) 107 | - `equal` тестирует на равенство в том числе и последовательностей (т.е. применим для списков и строк) 108 | - `equalp` тестирует на структурное равенство также хеш-таблиц и структур, а строки сравнивает без учета регистра 109 | 110 | Как правило используется `eql` тест (по-умолчанию) или же `equal`, если нужно использовать ключи строки или составные ключи. 111 | 112 | К сожалению, в стандартной библиотеке нет буквального синтаксиса для записи хеш-таблиц. `RUTILS` добавляет такой синтаксис `#{}`: 113 | `#{equal "foo" 1 :bar 2}` создаст таблицу с ключами `"foo"` и `:bar`, и предикатом равенства `equal`. 114 | 115 | Получение значения по ключу — `(gethash key table)`, запись — `(setf (gethash key table) value)`. 116 | Итерация по таблице — `maphash`. 117 | 118 | 119 | ## Структура 120 | 121 | ![Хеш-таблица](img/struct.png) 122 | 123 | Структуры `struct` в Lisp'е — это объект с заранее заданным набором полей (`slot`). 124 | Это легковесный объект — определение структуры (`defstruct`) также автоматически генерирует сопутствующие определения: 125 | функцию создания (`make-foo` для структуры `foo`), соответствующий тип (`foo`), функцию печати (`print-foo`) 126 | и аксессоры доступа к полям (`foo-bar` для слота `bar`). Пример определения структуры: 127 | 128 | (defstruct foo 129 | bar 130 | (baz 0 :type integer)) 131 | 132 | опишет структуру foo с полями `bar` и `baz` (причем, `baz` будет иметь ограничение по типу — только `integer` и значение по-умолчанию 0). 133 | 134 | CL-USER> (print (make-foo :bar "quux")) 135 | #S(FOO :BAR "quux" :BAZ 0) 136 | 137 | Доступ к полям структуры можно осуществлять с помощью функции `(slot-value struct 'slot)` или же с помощью макросов `with-slots`/`with-accessors`. 138 | 139 | Структуры также поддерживают наследование. 140 | 141 | 142 | ## Коллекции 143 | 144 | ![Коллекции](img/collection.jpg) 145 | 146 | Основных применения для различных структур данных — два: 147 | 148 | - собственно, структуризация информации для более удобной работы с ней. Тут разные представления дают разный уровень семантической структуризации: стуктуры — наиболее строгий и семантичный, а списки — наиболее слабый, хотя и они могут использоваться как ad hoc структуры. 149 | - в качестве способа создавать коллекции или наборы значений. Тут, наоборот, стандартным вариантом являются списки/вектора, хотя даже структуры могут быть использованы для таких задач в некоторых особых случаях. 150 | 151 | Основные характеристики коллекций — как они поддерживают доступ к своим элементам: упорядоченно (список) или случайно (хеш-таблица), последовательно (список, вектор) или по ключу (вектор, хеш-таблица), и т.д. 152 | 153 | Основная операция работы с коллекцией — это применение какой-то функции ко всем ее элементам в определенном порядке и сбор результатов тем или иным образом (например, в такую же или другую коллекцию). В Lisp'е одновременно применяются 2 подхода к работе с коллекциями: функции высших порядков (`map`, `reduce`, `find`, и т.д.) и итерация в различных циклах (`dolist`, `dotable`, `doindex`, `loop` и `iter` — часть из этих операций не входят в стандартную бибилиотеку). Принцип хорошего тона здесь такой — функции высших порядков проще и легко комбинируются и лучше всего подходят для случаев преобразования данных. Циклы лучше применять, когда операций выполняется в основном ради побочного эффекта (например, вывода куда-то), а также, когда требуется одновременная итерция по нескольких коллекциям или другой нестандартный сценарий итерации. 154 | 155 | 156 | ## Задание 157 | 158 | 1. Опишите разные способы, с помощью которых можно представить следующие пары ключ-значения в виде различных вариантов списков и операции, с помощью которых можно найти значение по ключу: `:foo` - `42`, `:bar` - `43`. Например, классический вариант представления — `alist`: `'((:foo . 42) (:bar . 43))`, доступ с помощью функции `assoc`: `(cdr (assoc :foo '((:foo . 42) (:bar . 43)))) => 42`. 159 | 160 | 2. Сколькими способами можно преобразовать список в вектор и обратно? Приведите примеры кода, которые это делают. 161 | 162 | 3. С помощью операций работы с последовательностями оставьте в этой строке только гласные буквы и приведите их к верхнему регистру: `foobarbaz` (результат: `OOAA`). 163 | 164 | 4. Найдите самый компактный способ заменить все нечетные числа на символ `t` в следующем списке: `((1 :foo) (2 (3 (4 (5 :bar :baz))) 6 7 8 9))` (результат: `((T :foo) (2 (T (4 (T :bar :baz))) 6 T 8 T))`). 165 | 166 | 5. Задайте структуру `quux` с полями `foo` и `bar`, которые могут быть строками или `nil`, аксессоры которых называются `qfoo` и `qbar`, и которая печатается на экран следующим образом: `@qUx(<значение поля foo>,<значение поля bar>)`. 167 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Креш-курс по Lisp 2 | 3 | Быстрое введение в практическое использование Lisp'а для тех, у кого есть опыт программирования 4 | -------------------------------------------------------------------------------- /img/array.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vseloved/lisp-crash-ru/c0a09cf9e4d56839f2e3f2432e7229ef75f15e7d/img/array.gif -------------------------------------------------------------------------------- /img/collection.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vseloved/lisp-crash-ru/c0a09cf9e4d56839f2e3f2432e7229ef75f15e7d/img/collection.jpg -------------------------------------------------------------------------------- /img/compiler-pipeline.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vseloved/lisp-crash-ru/c0a09cf9e4d56839f2e3f2432e7229ef75f15e7d/img/compiler-pipeline.jpg -------------------------------------------------------------------------------- /img/dna-seq.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vseloved/lisp-crash-ru/c0a09cf9e4d56839f2e3f2432e7229ef75f15e7d/img/dna-seq.jpg -------------------------------------------------------------------------------- /img/dynamic-array.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vseloved/lisp-crash-ru/c0a09cf9e4d56839f2e3f2432e7229ef75f15e7d/img/dynamic-array.png -------------------------------------------------------------------------------- /img/emacs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vseloved/lisp-crash-ru/c0a09cf9e4d56839f2e3f2432e7229ef75f15e7d/img/emacs.jpg -------------------------------------------------------------------------------- /img/emacs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vseloved/lisp-crash-ru/c0a09cf9e4d56839f2e3f2432e7229ef75f15e7d/img/emacs.png -------------------------------------------------------------------------------- /img/eval-apply.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vseloved/lisp-crash-ru/c0a09cf9e4d56839f2e3f2432e7229ef75f15e7d/img/eval-apply.gif -------------------------------------------------------------------------------- /img/hash-table.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vseloved/lisp-crash-ru/c0a09cf9e4d56839f2e3f2432e7229ef75f15e7d/img/hash-table.png -------------------------------------------------------------------------------- /img/hyperspec.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vseloved/lisp-crash-ru/c0a09cf9e4d56839f2e3f2432e7229ef75f15e7d/img/hyperspec.gif -------------------------------------------------------------------------------- /img/linked-list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vseloved/lisp-crash-ru/c0a09cf9e4d56839f2e3f2432e7229ef75f15e7d/img/linked-list.png -------------------------------------------------------------------------------- /img/ohm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vseloved/lisp-crash-ru/c0a09cf9e4d56839f2e3f2432e7229ef75f15e7d/img/ohm.jpg -------------------------------------------------------------------------------- /img/spel-illustration.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vseloved/lisp-crash-ru/c0a09cf9e4d56839f2e3f2432e7229ef75f15e7d/img/spel-illustration.jpg -------------------------------------------------------------------------------- /img/stmt-vs-expr.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vseloved/lisp-crash-ru/c0a09cf9e4d56839f2e3f2432e7229ef75f15e7d/img/stmt-vs-expr.gif -------------------------------------------------------------------------------- /img/string.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vseloved/lisp-crash-ru/c0a09cf9e4d56839f2e3f2432e7229ef75f15e7d/img/string.jpg -------------------------------------------------------------------------------- /img/struct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vseloved/lisp-crash-ru/c0a09cf9e4d56839f2e3f2432e7229ef75f15e7d/img/struct.png -------------------------------------------------------------------------------- /img/unwrap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vseloved/lisp-crash-ru/c0a09cf9e4d56839f2e3f2432e7229ef75f15e7d/img/unwrap.jpg -------------------------------------------------------------------------------- /img/yoda.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vseloved/lisp-crash-ru/c0a09cf9e4d56839f2e3f2432e7229ef75f15e7d/img/yoda.jpg -------------------------------------------------------------------------------- /lang-detect/calc.lisp: -------------------------------------------------------------------------------- 1 | (in-package :lang-detect) 2 | 3 | (defun detect-lang (text) 4 | (let ((words (split-words text)) 5 | (rez (make-hash-table)) 6 | (max 0) 7 | argmax) 8 | (dolist (lang *langs*) 9 | (let ((freqs (gethash lang *freqs*)) 10 | (total (gethash lang *totals*)) 11 | (prob 1)) 12 | (dolist (word words) 13 | (let ((freq (gethash word freqs 0))) 14 | (setf prob (* prob (/ (+ 1 freq) total))))) 15 | (setf (gethash lang rez) prob))) 16 | (rutil:dotable (lang prob rez) 17 | (when (> prob max) 18 | (setf max prob 19 | argmax lang))) 20 | (values argma 21 | rez))) 22 | -------------------------------------------------------------------------------- /lang-detect/lang-detect.asd: -------------------------------------------------------------------------------- 1 | (in-package :asdf-user) 2 | 3 | (defsystem #:lang-detect 4 | :depends-on (#:should-test #:cxml #:rutils) 5 | :serial t 6 | :components ((:file "package") 7 | (:file "storage") 8 | (:file "stats") 9 | (:file "calc") 10 | (:file "test"))) 11 | -------------------------------------------------------------------------------- /lang-detect/package.lisp: -------------------------------------------------------------------------------- 1 | (defpackage #:lang-detect 2 | (:use #:common-lisp #:should-test) 3 | (:export #:detect-lang)) 4 | -------------------------------------------------------------------------------- /lang-detect/stats.lisp: -------------------------------------------------------------------------------- 1 | (in-package :lang-detect) 2 | 3 | (defparameter *raw* ()) 4 | 5 | (defclass ld-sax (sax:sax-parser-mixin) 6 | ((ready :accessor sax-ready :initform nil))) 7 | 8 | (defmethod sax:start-document ((sax ld-sax)) 9 | (setf *raw* nil)) 10 | 11 | (defmethod sax:start-element ((sax ld-sax) 12 | namespace-uri local-name qname attributes) 13 | (when (string= "text" local-name) 14 | (setf (sax-ready sax) t))) 15 | 16 | (defmethod sax:characters ((sax ld-sax) data) 17 | (when (sax-ready sax) 18 | (dolist (line (rutil:split #\Newline data :remove-empty-subseqs t)) 19 | (when (and (rutil:starts-with "#" line) 20 | (> (length line) 2)) 21 | (push (subseq line 1) *raw*))))) 22 | 23 | (defmethod sax:end-element ((sax ld-sax) 24 | namespace-uri local-name qname) 25 | (when (string= "text" local-name) 26 | (setf (sax-ready sax) nil))) 27 | 28 | (defmethod sax:end-document ((sax ld-sax)) 29 | ) 30 | 31 | (defun split-words (line) 32 | (cl-ppcre:all-matches-as-strings "\\w+" line)) 33 | 34 | (defun calc-stats (lines) 35 | (let ((rez (make-hash-table :test 'equal))) 36 | (dolist (line lines) 37 | (dolist (word (split-words line)) 38 | (incf (gethash word rez 0)))) 39 | rez)) 40 | 41 | (defparameter *langs* '(:ru :uk)) 42 | 43 | (defparameter *freqs* (make-hash-table)) 44 | (defparameter *totals* (make-hash-table)) 45 | 46 | (defun word-prob (lang word) 47 | (float 48 | (/ (gethash word (gethash lang *freqs* 49 | (make-hash-table)) 50 | 0) 51 | (gethash lang *totals*)))) 52 | 53 | ;; (setf (gethash :ru *freqs*) (calc-stats *ru*)) 54 | ;; (setf (gethash :uk *freqs*) (calc-stats *uk*)) 55 | ;; (dolist (lang *langs*) 56 | ;; (let ((total 0)) 57 | ;; (rutil:dotable (_ freq (gethash lang *freqs*)) 58 | ;; (incf total freq)) 59 | ;; (setf (gethash lang *totals*) total))) 60 | 61 | (defun detect-lang (text) 62 | (let ((words (split-words text)) 63 | (rez (make-hash-table)) 64 | (max 0) 65 | argmax) 66 | (dolist (lang *langs*) 67 | (let ((freqs (gethash lang *freqs*)) 68 | (total (gethash lang *totals*)) 69 | (prob 1)) 70 | (dolist (word words) 71 | (let ((freq (gethash word freqs 0))) 72 | (setf prob (* prob (/ (+ 1 freq) total))))) 73 | (setf (gethash lang rez) prob))) 74 | (rutil:dotable (lang prob rez) 75 | (when (> prob max) 76 | (setf max prob 77 | argmax lang))) 78 | (values argma 79 | rez))) 80 | -------------------------------------------------------------------------------- /lang-detect/storage.lisp: -------------------------------------------------------------------------------- 1 | (in-package :lang-detect) 2 | 3 | (defun save-model (model) 4 | ) 5 | 6 | (defun load-model (model) 7 | ) 8 | 9 | (defparameter *model* nil) 10 | -------------------------------------------------------------------------------- /lang-detect/test.lisp: -------------------------------------------------------------------------------- 1 | (in-package :lang-detect) 2 | 3 | 4 | (deftest detect-lang () 5 | (should be eql :ru 6 | (detect-lang "Это тест.")) 7 | (should be eql :uk 8 | (detect-lang "Це тест.")) 9 | (should be eql :uk 10 | (detect-lang "Як умру то поховайте")) 11 | (should be eql :ru 12 | (detect-lang "Я помню чужное мгновенье")) 13 | (should be eql :ru 14 | (detect-lang "что")) 15 | (should be eql :uk 16 | (detect-lang "що")) 17 | (should be eql :ru 18 | (detect-lang "глокая куздра"))) 19 | 20 | (deftest cacl-stats () 21 | (should be = 1 22 | (gethash "маленький" 23 | (calc-stats '(" [[бегемотовый]]" 24 | " —" 25 | " {{устар.|ru}} маленький [[ребёнок]]"))))) 26 | 27 | (deftest split-words () 28 | (should be equal '("устар" "ru" "маленький" "ребёнок") 29 | (split-words " {{устар.|ru}} маленький [[ребёнок]]"))) 30 | -------------------------------------------------------------------------------- /lit.md: -------------------------------------------------------------------------------- 1 | # Литература к мастер-классу по Common Lisp 2 | 3 | ## Вопросы по языку 4 | 5 | - [Common Lisp Hyperspec](http://www.lispworks.com/documentation/lw70/CLHS/Front/Contents.htm) 6 | - [Common Lisp Quick Reference](http://clqr.boundp.org/) 7 | - [Practical Common Lisp](http://www.gigamonkeys.com/book/small-cover.gif) 8 | - [ANSI CL на русском](http://www.books.ru/books/ansi-common-lisp-fail-3127808/) 9 | 10 | ## Вопросы стиля 11 | 12 | - [Google Common Lisp Style Guide](http://google-styleguide.googlecode.com/svn/trunk/lispguide.xml) 13 | - [Tutorial on Good Lisp Style](https://www.cs.umd.edu/~nau/cmsc421/norvig-lisp-style.pdf) 14 | - [CALL-WITH-*](http://random-state.net/log/3390120648.html) 15 | 16 | ## Книги 17 | 18 | - [Paradigms of Artificial Intelligence Programming](http://norvig.com/paip.html) 19 | - [Structure and Interpretation of Computer Programs](http://mitpress.mit.edu/sicp/) из MIT 20 | - [On Lisp](http://www.paulgraham.com/onlisp.html) от Пола Грема 21 | - [Let over Lambda](http://letoverlambda.com/) — развитие идей "On Lisp" 22 | 23 | И, вообще: [Lisp Books](http://www.pinterest.com/vseloved/lisp-books/) 24 | --------------------------------------------------------------------------------