Если это сработало для них - может сработать и для нас
Если структурные программы более читабельны чем машинный код, а художественная и техническая литература - более читабельна чем код программы - может быть стоит писать программы как литературу?
Давайте изменим традиционные приоритеты в создании программ: вместо представления о нашей задаче как о создании инструкций "Что делать?" для компьютера сконцентрируемся на объяснении другим людям описаний нашего видения того, что под управлением программы должен делать компьютер.
Главное в работоспособной программе - не управляющие инструкции компьютеру, а знания разработчика программы, выраженные в максимально удобной для понимания сторонним человеком форме
Дональд Кнут
Если проект будет провален - это будет уже трагедия :)
История - это не даты революций и войн!
Я хочу написать захватывающий детектив о программе, которая спасает мир. Для этого я должен описать каждую деталь этой программы. Но как мне потом получить работающий код из этого детектива?
* Описание задачи
Начнем писать на HTML и будем вставлять именованные блоки кода в него вот так:
* Описание задачи...
* Язык разметки
<html>
<head><title>Описание программы</title></head>
<body>
<p>А это точка входа в программу:</p>
<pre id="main.c">
int main(int argc, char *argv[]) {...}
</pre>
</body>
</html>
А когда нам нужно будет получить блок будем использовать
getchunk:
* Описание задачи...
* Язык разметки
...
<getchunk id="main.c">
</body>
</html>
Вот наш цикл разработки:
do forever { edit the html file tangle thehtmlfile sub1.c >sub1.c tangle thehtmlfile sub2.c >sub2.c tangle thehtmlfile main.c >main.c gcc -o myfile sub1.c sub2.c main.c ./myfile }
* Описание задачи...
* Язык разметки
* Компиляция и сборка
** Цикл разработки
... В каждый момент времени этого цикла ваша программа правильно объясняет саму себя и работает надлежащим образом.
Если вы все сделали правильно, вы можете просто послать ссылку на HTML-файл коллеге.. Когда он вернется - он уже понимает достаточно о вашей программе, чтобы взять на себя весь проект.
В самом деле, сложность заключается в том, что вам нужно сообщить свои идеи другим человекам. И вы должны сделать это таким образом, чтобы упростить их понимание.
* Компиляция и сборка
Главное, что мы должны сделать, это найти кусок кода где-то в нашем файле. Мы указали имя файла и имя куска кода в командной строке.
Там может быть только два параметра и оба они требуются, так что сначала мы проверяем это условие. Если это не так, то мы печатаем пользователю стандартное сообщение об использовании программы.
* Главная задача
Переменная буфер описывается как указатель на первый символ строки
<pre id="main.c">
/* a memory mapped buffer copy of the file */ char *buffer; int bufsize;
</pre>
Мы определяем его глобально, в верхней части файла, за рамками всех других функций.
* Главная задача
Мы можем загрузить файл в один блок с помощью функции READ. Для этого нужно чтобы мы могли открыть файл, а это в свою очередь, дает нам дескриптор файла, который мы получаем от вызова OPEN. Нам нужен размер файла, так что мы должны выделить память, так что мы используем FSTAT..
int main(int argc, char *argv[]) { int fd; struct stat filestat; if ((argc != 2)) { perror("Usage: tangle filename chunkname"); exit(-1); }
...
* Главная задача
fd = open(argv[1], O_RDONLY); if (fd == -1) { perror("Error opening file for reading"); exit(-2); } if (fstat(fd,&filestat) < 0) { perror("Error getting input file size"); exit(-3); } bufsize = (int)filestat.st_size; buffer = (char *)malloc(bufsize); read(fd,buffer,bufsize); fixHTMLcode(); getchunk(argv[2]); close(fd); return(0); }
* Главная задача
* Описание задачи
* Язык разметки
* Компиляция и сборка
** Makefile
* Дедлайн
* Планы по расширению функционала
** Поддержка markdown
** Создание PDF
* Интеграция с другими программами
* Зависимости от других библиотек
Вставим один кусочек в другой:
(labels ((match-tree (tree f-predict &optional (if-match :return-first-match))
(cond ((null tree) nil)
((atom tree) nil)
(t
<<cons>>))))
<<call>>)
cons:
(if (funcall f-predict tree)
<<match_ok>>
<<sub_trees>>)
sub_trees:
(cons
(funcall #'match-tree (car tree) f-predict if-match)
(funcall #'match-tree (cdr tree) f-predict if-match))
А в другой - третий:
Получим возможность составлять сложные алгоритмы из простых кусочков - это рекурсивный поиск в дереве, параметризованный стратегией возврата (ад отладки)
(defun tree-match (tree predict &optional (if-match :return-first-match))
(let ((collect))
(labels ((match-tree (tree f-predict &optional (if-match :return-first-match))
(cond ((null tree) nil)
((atom tree) nil)
(t
(if (funcall f-predict tree)
(cond ((equal if-match :return-first-match)
(return-from tree-match tree))
((equal if-match :return-first-level-match)
(setf collect
(append collect (list tree))))
((equal if-match :return-all-match)
(progn
(setf collect
(append collect (list tree)))
(cons
(funcall #'match-tree (car tree) f-predict if-match)
(funcall #'match-tree (cdr tree) f-predict if-match))))
((equal 'function (type-of if-match))
(funcall if-match tree))
(t (error 'strategy-not-implemented)))
(cons
(funcall #'match-tree (car tree) f-predict if-match)
(funcall #'match-tree (cdr tree) f-predict if-match)))))))
(match-tree tree predict if-match)
collect)))
Представим кусочек
#+NAME: sub_trees
#+BEGIN_SRC lisp
(cons
(funcall #'match-tree (car tree) f-predict if-match)
(funcall #'match-tree (cdr tree) f-predict if-match))
#+END_SRC
В виде функции, которая возвращает его содержимое:
function sub_trees () {
return "
(cons
(funcall #'match-tree (car tree) f-predict if-match)
(funcall #'match-tree (cdr tree) f-predict if-match))
";
}
Тогда кусочек, в который может быть вставлен другой кусочек - будет функцией с параметрами:
#+NAME: cons
#+BEGIN_SRC lisp
(if (funcall f-predict tree)
<<match_ok>>
<<sub_trees>>)
#+END_SRC
Таким образом, каждый кусочек выполняет подстановку. Может ли он делать что-то еще?
function sub_trees (match_ok, sub_trees) {
return "
(if (funcall f-predict tree)
<<match_ok>>
<<sub_trees>>)
";
}
#+NAME: cons
#+BEGIN_SRC lisp
(if (funcall f-predict tree)
<<match_ok>>
<<sub_trees>>)
#+END_SRC
function sub_trees (match_ok, sub_trees) {
return "
(if (funcall f-predict tree)
<<match_ok>>
<<sub_trees>>)
";
}
Потому в каждом кусочке - на самом деле тьюринг-полный язык программирования
#+CAPTION: Данные вакансии
#+NAME: vacancy_flds
| field name | field type | note |
|-------------+----------------------+--------------------|
| id | serial | идентификатор |
| name | varchar | название вакансии |
| currency | (or db-null varchar) | валюта зарплаты |
| salary | (or db-null integer) | размер компенсации |
| emp-id | (or db-null integer) | работодатель |
| descr | varchar | описание вакансии |
#+results: asm_vacancy
#+begin_example
(define-entity vacancy
((id serial)
(name varchar)
(currency (or db-null varchar))
(salary (or db-null integer))
(emp-id (or db-null integer))
(descr varchar)))
(make-vacancy-table)
#+NAME: asm_vacancy
#+BEGIN_SRC emacs-lisp :var flds=vacancy_flds
(gen-entity "vacancy" flds)
#+END_SRC
#+NAME: gen_entity
#+BEGIN_SRC emacs-lisp
(defun gen-entity (nameflds)
(let ((result))
(push (format "(define-entity %s" name) result)
(push (gen-fields flds) result)
(push ")\n" result)
(push "\n" result)
(push (format "(make-%s-table)\n" name) result)
(mapconcat 'identity (reverse result) "")))
#+END_SRC
#+NAME: entityes
#+BEGIN_SRC lisp
<<asm_vacancy>
<<asm_employer>>
<<asm_applicant>>
...
#+END_SRC
Это программирование программирования!
<<*>>=
MODULE quick;
IMPORT IO;
<<Constants, types, and global variables>>
<<The QuickSort Procedure>>
BEGIN
<<Get some numbers>>
<<Sort the Numbers>>
<<Print the numbers>>
END quick.
@ This program demonstrates the {\tt QuickSort} algorithm. It reads
a list of numbers from the standard input, sorts them, and writes the
sorted results to standard output.
<<The QuickSort Procedure>>=
PROCEDURE QuickSort(VAR a : ARRAY OF INTEGER; left, right: INTEGER);
<<Local variables>>
BEGIN
<<Sort and divide until there's nothing left to do>>
END QuickSort;
@ Recursively sort an array {\tt a} of integers. {\tt left} and
{\tt right} denote the leftmost and rightmost elements of the array.
<<Sort and divide until there's nothing left to do>>=
IF right > left THEN
<<Get set by guessing a cut value and initialising indexes>>
<<Sort array with respect to cut value>>
<<Recursively sort array left of cut value>>
<<Recursively sort array right of cut value>>
END (* if *);
@ When we make a recursive call where the right and left indexes are
the same, then we've divided down to nothing and we're done with this
recursive thread.
<<Get set by guessing a cut value and initialising indexes>>=
cutval := a[right]; (*arbitrary start for partition*)
lo := left - 1;
hi := right;
@ Arbitrarily pick the rightmost element of the array as the cut value
for this pass.
<<Sort array with respect to cut value>>=
REPEAT
<<Find an out of order number from left>>
<<Find an out of order number from right>>
<<Swap them>>
UNTIL hi <= lo;
<<Undo Extra Swap>>
<<Sort and divide until there's nothing left to do>>=
IF right > left THEN
<<Get set by guessing a cut value and initialising indexes>>
<<Sort array with respect to cut value>>
<<Recursively sort array left of cut value>>
<<Recursively sort array right of cut value>>
END (* if *);
@ When we make a recursive call where the right and left indexes are
the same, then we've divided down to nothing and we're done with this
recursive thread.
<<Get set by guessing a cut value and initialising indexes>>=
cutval := a[right]; (*arbitrary start for partition*)
lo := left - 1;
hi := right;
@ Arbitrarily pick the rightmost element of the array as the cut value
for this pass.
<<Sort array with respect to cut value>>=
REPEAT
<<Find an out of order number from left>>
<<Find an out of order number from right>>
<<Swap them>>
UNTIL hi <= lo;
<<Undo Extra Swap>>
LP - это макропроцессор над семантической сетью концепций
LP - это макропроцессор над семантической сетью концепций
Поддержка серверов состоит из двух фаз:
C-c C-c
(https://github.com/ieure/ssh-el)
(org-mode Contrib collection)
Поиск в пространстве состояний
* Бизнес-логика:
Заказчик хочет переправиться на другой берег реки с козой, капустой и волком
На берегу реки находится лодка, которой должен управлять человек
Лодка одновременно может перевозить не более 2-х пассажиров (включая человека)
Если оставить волка на берегу с козой в отсутствии человека - он ее сьест
Если оставить козу на берегу с капустой в отсутствии человека - она ее сьест
Требуется выработать последовательность переправ через реку, чтобы перевезти всех
* Состояния мира:
Представим состояния мира как список из 4-х элементов в котором каждый компонент обозначает положение человека, волка, козы и капусты соответственно:
(e w e w) - человек и коза на левом берегу
* Абстракция состояния:
Определим конструктор типа "состояние" и функции доступа:
(defun make-state (f w g s)
(list f w g s))
(defun farmer-side (state)
(nth 0 state))
(defun wolf-side (state)
(nth 1 state))
(defun goat-side (state)
(nth 2 state))
(defun cabbage-side (state)
(nth 3 state))
Оставшаяся часть программы основыватся на этих функциях
* Абстракция состояния:
При каждом переходе в новое состояние функции доступа используются для разделения состояния на его компоненты. Функция opposite, которая будет вскоре определена, меняет положение объекта, пересекающего реку, а функция make-state формирует новое состояние.
* Абстракция состояния:
Например функцию farmer-takes-self можно определить так:
(defun farmer-takes-self (state)
(make-state (opposite (farmer-side state))
(wolf-side state)
(goat-side state)
(cabbage-side state)))
Заметим, что эта функция возвращает состояние без учета его безопасности. Состояние является небезопасным, если человек оставит на берегу волка с козой или козу с капустой.
Мы будем выполнять проверку состяния на безопасность в момент переправы - перехода из одного состояния в другое с помощью формы safe, которая вскоре будет определена:
(safe '(w w w w)) ;; состяние безопасно
(safe '(e w w e)) ;; волк сьест козу, вернуть ложь
(safe '(w w e e)) ;; коза сьест капусту, вернуть ложь
* Абстракция состояния:
Функция safe используется в каждой функции перехода для фильтрации опасных состояний. Любой переход, приводящий к небезопасному состоянию возвращает не это состояние, а ложь
Рекурсивный алгоритм поиска с возвратами проверяет возвращаемое значение, и если оно = ложь - отбрасывает это состояние
С учетом safe четыре возможных формы перехода можно определить следующим образом:
(defun farmer-takes-self (state)
(safe (make-state (opposite (farmer-side state))
(wolf-side state)
(goat-side state)
(cabbage-side state))))
(defun farmer-takes-wolf (state)
(cond ((equal (farmer-side state) (wolf-side state))
(safe (make-state (opposite (farmer-side state))
(opposite (wolf-side state))
(goat-side state)
(cabbage-side state))))
(t nil)))
(defun farmer-takes-goat (state)
(cond ((equal (farmer-side state) (goat-side state))
(safe (make-state (opposite (farmer-side state))
(wolf-side state)
(opposite (goat-side state))
(cabbage-side state))))
(t nil)))
(defun farmer-takes-cabbage (state)
(cond ((equal (farmer-side state) (cabbage-side state))
(safe (make-state (opposite (farmer-side state))
(wolf-side state)
(goat-side state)
(opposite (cabbage-side state)))))
(t nil)))
* Абстракция состояния:
В функциях перехода используется функция opposite, возвращающая местоположение, противоположное текущему:
(defun opposite (side)
(cond ((equal side 'e) 'w)
((equal side 'w) 'e)))
* Абстракция состояния:
Функция safe позволяет проверить два опасных условия:
(defun safe (state)
(cond ((and (equal (goat-side state) (wolf-side state))
(not (equal (farmer-side state) (wolf-side state))))
nil)
((and (equal (goat-side state) (cabbage-side state))
(not (equal (farmer-side state) (goat-side state))))
nil)
(t state)))
* Поиск с возвратами:
Функция path реализует поиск с возвратами в пространстве состояний. Ее аргументами являются исходное и целевое состояния. Если они не равны, функция генерирует все четыре соседних состояния и вызывает себя для каждого из них, пытаясь найти путь к целевому состоянию:
(defun path (state goal)
(cond ((equal state goal) 'success)
(t (or (path (farmer-takes-self state) goal)
(path (farmer-takes-wolf state) goal)
(path (farmer-takes-goat state) goal)
(path (farmer-takes-cabbage state) goal)))))
Неудобство этого определения в том, что функция может возвращать значение nil если переход невозможен или ведет к опасному состоянию
* Поиск с возвратами:
Еще одним вопросом, требущим решения является выявление циклов в пространстве поиска. Добавляем список посещенных состояний
(defun path (state goal been-list)
(cond ((null state) nil)
((equal state goal)
(reverse (cons state been-list)))
((member state been-list :test #'equal)
(prog1 nil
(format t "~%fail: this is old path~%")))
((not (member state been-list :test #'equal))
(or (path (farmer-takes-self state) goal (cons state been-list))
(path (farmer-takes-wolf state) goal (cons state been-list))
(path (farmer-takes-goat state) goal (cons state been-list))
(path (farmer-takes-cabbage state) goal (cons state been-list))))))
* Запуск:
Попробуем найти решение, с помощью нашей программы:
success!
| | | | |
| FARMER | WOLF | GOAT | CABBAGE |
| FARMER | | GOAT | |
| | WOLF | | CABBAGE |
| | | GOAT | |
| FARMER | WOLF | | CABBAGE |
| FARMER | WOLF | GOAT | |
| | | | CABBAGE |
| | WOLF | | |
| FARMER | | GOAT | CABBAGE |
| FARMER | WOLF | | CABBAGE |
| | | GOAT | |
| | WOLF | | CABBAGE |
| FARMER | | GOAT | |
| FARMER | WOLF | GOAT | CABBAGE |
| | | | |
mailto://i.am.rigidus@gmail.com
https://github.com/rigidus
http://rigidus.ru