Возможно, что вам приходилось когда-нибудь писать код создания окна на чистом WinApi. Наверно, он выглядел примерно так:
function CreateWindow(Width, Height : Integer; Fullscreen : Boolean; PixelDepth : Integer) : Boolean;
// ...
begin
// ...
if (RegisterClass(wndClass) = 0) then // Attemp to register the window class
begin
MessageBox(0, 'Failed to register the window class!', 'Error', MB_OK or MB_ICONERROR);
Exit
end;
// ...
h_Wnd := CreateWindowEx(dwExStyle, // Extended window styles
'OpenGL', // Class name
WND_TITLE, // Window title (caption)
dwStyle, // Window styles
0, 0, // Window position
Width, Height, // Size of window
0, // No parent window
0, // No menu
h_Instance, // Instance
nil); // Pass nothing to WM_CREATE
if h_Wnd = 0 then
begin
MessageBox(0, 'Unable to create window!', 'Error', MB_OK or MB_ICONERROR);
Exit;
end;
// Try to get a device context
h_DC := GetDC(h_Wnd);
if (h_DC = 0) then
begin
MessageBox(0, 'Unable to get a device context!', 'Error', MB_OK or MB_ICONERROR);
Exit;
end;
// ...
PixelFormat := ChoosePixelFormat(h_DC, @pfd);
if (PixelFormat = 0) then
begin
MessageBox(0, 'Unable to find a suitable pixel format', 'Error', MB_OK or MB_ICONERROR);
Exit;
end;
if (not SetPixelFormat(h_DC, PixelFormat, @pfd)) then
begin
MessageBox(0, 'Unable to set the pixel format', 'Error', MB_OK or MB_ICONERROR);
Exit;
end;
// Create a OpenGL rendering context
h_RC := wglCreateContext(h_DC);
if (h_RC = 0) then
begin
MessageBox(0, 'Unable to create an OpenGL rendering context', 'Error', MB_OK or MB_ICONERROR);
Exit;
end;
if not wglMakeCurrent(h_DC, h_RC) then
begin
MessageBox(0, 'Unable to activate OpenGL rendering context', 'Error', MB_OK or MB_ICONERROR);
Exit;
end;
// ...
Result := True;
end;
Можно заметить, что тут очень много копипастится код
MessageBox(0, 'Unable to «что не получилось»!', 'Error', MB_OK or MB_ICONERROR);
Exit;
Кроме копипаста этих двух строк, есть и второй копипаст: вместо «что не получилось» нужно везде прописывать название функции, в которой это не получилось. Как это можно бы было улучшить? Вынести в отдельную функцию нельзя, потому что Exit не умеет прыгать сразу на две функции вверх, а чтобы message box отобразил строку, нужно эту строку вбить вручную (не важно где, главное что эта строка должна быть вбита своими руками).
Конечно, в языке есть исключения, но WinApi не бросает никаких исключений специально для Free Pascal в случае штатных ошибок.
Как же быть? А никак — только копипаст. Те же беды с контролем ошибок в WinSock, DirectX и далее по списку.
Как такое обойти на лиспе? Понятно, что в данном случае нужно писать макрос, который принимает последовательность команд и генерирует из них код, который последовательно их выполняет, проверяя возвращаемые значения, и в случае нулевого результата, выдаёт мессадж-бокс с последующим возвращением NIL.
(defmacro block-check-winapi (&body body)
`(block NIL
,@(loop for f in body collect
`(if (eql ,f 0)
(progn
(MessageBox 0
(format NIL "Unable to ~a" ',f)
"Error"
(or MB_OK MB_ICONERROR))
(return NIL))))
T))
Как всегда, для наглядности привожу пример того, во что эта конструкция разворачивается:
(block-check-winapi
(RegisterClass wndClass)
(CreateWindowEx dwExStyle
"OpenGL"
WND_TITLE
dwStyle
0 0
Width
Height
0
0
h_Instance
NIL))
(BLOCK NIL
(IF (EQL (REGISTERCLASS WNDCLASS) 0)
(PROGN
(MESSAGEBOX 0 (FORMAT NIL "Unable to ~a" '(REGISTERCLASS WNDCLASS))
"Error" (OR MB_OK MB_ICONERROR))
(RETURN NIL)))
(IF (EQL
(CREATEWINDOWEX DWEXSTYLE "OpenGL" WND_TITLE DWSTYLE 0 0 WIDTH HEIGHT 0
0 H_INSTANCE NIL)
0)
(PROGN
(MESSAGEBOX 0
(FORMAT NIL "Unable to ~a"
'(CREATEWINDOWEX DWEXSTYLE "OpenGL" WND_TITLE DWSTYLE 0 0 WIDTH
HEIGHT 0 0 H_INSTANCE NIL))
"Error" (OR MB_OK MB_ICONERROR))
(RETURN NIL)))
T)
Добро пожаловать!
На текущий момент в блоге затронуты следующие темы: vim, free pascal, lisp, forth, m4
Занимаюсь разработкой своего языка под названием DEmbro, подбробней: wiki и svn
Для постов, не связанных с программированием, у меня есть отдельное жж.
июля 31, 2010
Обработка ошибок внешних библиотек
июля 28, 2010
Загрузка, ждите
С давних времён я задаюсь вопросом о том как можно бы было сделать рисование полосы загрузки по мере выполнения какого-то большого кода — например, при загрузке операционной системы, при запуске всяких программ и игр.
В случае с установщиком все понятно — установщику нужно скопировать определенный набор файлов, делаем это в цикле, а в самом конце каждой итерации обновляем полосу загрузки.
Но что делать, если в программе нужно выполнить кучу неоднородного кода (который нельзя так просто оформить в виде цикла), заставляющего пользователя ждать? Пример с запуском операционной системы очень хороший.
На Free Pascal я могу назвать три решения.
1) В лоб — после каждой строчки кода загрузки вызываем функцию перерисовки. Это можно сделать так:
for I := 0 to LinesCount - 1 do begin
case I of
0: <Line0>;
1: <Line1>;
2: <Line2>;
...
end;
RedrawLoading(I, LinesCount);
end;
Вполне приемлемое решение, кроме того, что при добавлении новой строки нужно не забыть увеличить LinesCount.
2) Использовать нити (threads, так же известные как «потоки») — в одной нити грузим, в другой обновляем экран. Решение мне не нравится тем, что ИМХО с нитями лучше лишний раз не связываться.
3) Написать класс, предназначенный для выполнения одного шага загрузки, имеющий одну абстрактную виртуальную функцию Execute(). Унаследовать от него разные типы шагов: копирование файла, загрузка библиотеки, загрузки картинки, проверка чего-нибудь и т.д. После этого перед загрузкой занести в нужной последовательности такие классы в массив, и в цикле после каждого Execute вызывать RedrawLoading(I, Length(Steps)). Очень громоздкое решение — городить его ради ерунды как-то не очень хочется.
Наиболее симпатичным является первое решение, за свою простоту. Но вручную это делать не хочется — вот было бы здорово, если бы компилятор это сделал сам!
На лиспе в таких случаях проблем нет — просто пишется макрос, который всю грязную работу выполняет за вас. У меня для данной задачи получилось следующее
(defmacro watch ((watch-func) &body body)
(let ((op body))
`(progn
,@(loop for i upto (- (* (list-length body) 2) 1) collect
(if (evenp i)
`(,watch-func ,(/ i 2) ,(list-length body))
(let ((result `(,@(car op)))) (setf op (cdr op)) result)))
(,watch-func ,(list-length body) ,(list-length body)))))
Функция watch-func как бы наблюдает за ходом выполнения кода body, поэтому макрос я назвал watch. Несмотря на то, что я попытался написать красивый код, получилось спагетти. Но поставленную задачу оно выполняет, вот лог нескольких тестов:
(watch (L) A B C D E F)
(PROGN (L 0 6) A (L 1 6) B (L 2 6) C (L 3 6) D (L 4 6) E (L 5 6) F (L 6 6))
(watch (L) A)
(PROGN (L 0 1) A (L 1 1))
(watch (L))
(PROGN (L 0 0))
По хорошему, нужно еще обезопасить L от многократных вычислений, это можно сделать конструкцией once-only.
В Free Pascal нормальной системы макросов нет. Поэтому аналогичную конструкцию я написал в моей утилитке dpp. Т.к. для того, чтобы достаточно точно интегрировать конструкцию в сам язык, нужно парсить программу на паскале, мне пришлось вводить несколько инородный синтаксис:
#(watch (RenderLoading)
(FGui := TGui.Create(FGraph, FWindow);)
(FFontMan := TFontMan.Create(Graph);)
(FScene := TCucuScene.Create(Graph);)
(FStyle := TStyle.Create;)
(FStyle.SetParam('font.primary', TStyleParamFont.Create(TFont.Create(FFontMan, 'data\font\c256.bit', -6)));)
(FStyle.SetParam('color.primary', TStyleParamColor.Create(Vec4f(0.5, 0.5, 0, 1)));)
(FStyle.SetParam('color.background', TStyleParamColor.Create(Vec4f(0.3, 0.0, 0.3, 1)));)
(FGui.Root.Style := FStyle;)
(FConsole := TGuiConsole.Create(FGui);)
(FStart := GetTimer;)
)
Читать дальше......
июля 24, 2010
vim 7.3 beta
Недавно вышла бета будущего vim 7.3.
Одно из интересных нововведений — вечное UNDO. Теперь вся история редактирования файла хранится не только в рамках текущей сессии, но и при перезапусках. Например, можно будет увидеть состояние файла годичной давности. Конечно, при использовании svn это не так актуально, но не всё ведь хранится в свн. А бэкапы делать лень.
По умолчанию фича отключена, чтобы ею воспользоваться, достаточно добавить в vimrc файл строку:
set undofile
Появилась возможность нумеровать строки не относительно начала файла, а относительно текущего положения курсора. Удобно при выполнении команд, для которых нужно считать строки. Включается командой
set relativenumber
Появилась возможность подсвечивать определенные столбцы в буфере, включается командой
set colorcolumn=…
Где вместо … нужно указать через запятую номера столбцов, которые нужно подсветить. Я у себя выставил «set colorcolumn=81» чтобы видеть какие строки не влезли в 80 символов.
Вот как у меня выглядят последние две фичи:
Читать дальше......
dpp: персморт концепции
Изначально я предполагал, что моя утилитка dpp для препроцессинга сурсов на паскале будет строчно-ориентирована. Т.е. ключевые строки программы (объявление класса, функции, слова «interface» и «implementation») должны были бы занимать одну строку, а dpp может только дописывать в конец некоторым строкам какой-то код.
Объяснял я это тем, что от этого будет проще жить. Однако, напротив, начался адский кошмар и говнокод.
Поэтому я принял решение полностью отказаться от концепции строчно-ориентированности, ввёл новый синтаксис для dpp-макросов и начал с нуля.
А именно, код для dpp должен находится внутри блока #(…). Например, уже реализована конструкция
#(return A)
Которую dpp заменяет на
begin Result := A; Exit end
Хотя только что мне пришла в голову идея реализовать конструкцию с возможностью вставки нескольких операторов, т.е.
#(return A1 A2 … AN)
развернётся в
begin A1; A2; … A(N-1); Result := AN; Exit end
Теперь у меня серьезные проблемы с тем, что некоторые команды должны не только генерировать код прям на месте, но и добавлять какой-то код в другие части проекта. (К примеру, лямбда-функции должны добавлять свой код в implementation-секции модуля.) А это без предположения о строчном разбиении задача не минутная.
Но нет худа без добра, и возможно, что будет возможность писать макросы на лиспе, командой #(lisp …).
Читать дальше......
июля 22, 2010
Собственная линейная алгебра на лиспе 2
Предыдущая версия векторных вычислений замечательно работает с константами. Но что будет, если сложить два вектора, которые получены функцией с побочным эффектом?
Пусть, например, есть такие две функции
(defun get-vector-one () (print "get-vector-one") (vec3f 1.0 0.0 0.0))
(defun get-vector-two () (print "get-vector-two") (vec3f 0.0 2.0 0.0))
Тогда вызов
(add3f (get-vector-one) (get-vector-two))
выдаст:
"get-vector-one"
"get-vector-two"
"get-vector-one"
"get-vector-two"
"get-vector-one"
"get-vector-two"
#(1.0 2.0 0.0)
Получается, что обе функции будут вызваны трижды. Чтобы понять почему это так, можно посмотреть во что развернется макрос add3f в этом вызове:
>(macroexpand '(add3f (get-vector-one) (get-vector-two)))
(MAKE-ARRAY 3 :ELEMENT-TYPE 'SINGLE-FLOAT :INITIAL-CONTENTS
(LIST (+ (VEC3F-X (GET-VECTOR-ONE)) (VEC3F-X (GET-VECTOR-TWO)))
(+ (VEC3F-Y (GET-VECTOR-ONE)) (VEC3F-Y (GET-VECTOR-TWO)))
(+ (VEC3F-Z (GET-VECTOR-ONE)) (VEC3F-Z (GET-VECTOR-TWO)))))
Вот такой вот код получится при компиляции. Данное явление является нежелательным не только потому, что лишние побочные действия могут что-нибудь испортить, но и потому, что это может занять много времени на вычисление.
Решение проблемы заключается в том, чтобы ввести вспомогательные переменные при помощи конструкции (let (...) ...) и вычислить значения параметров один раз в эти вспомогательные переменные. Фактически, хочется чтобы при раскрытии «(add3f (get-vector-one) (get-vector-two))» получалось:
(LET ((TEMP1 (GET-VECTOR-ONE)) (TEMP2 (GET-VECTOR-TWO)))
(VEC3F (+ (VEC3F-X TEMP1) (VEC3F-X TEMP2))
(+ (VEC3F-Y TEMP1) (VEC3F-Y TEMP2))
(+ (VEC3F-Z TEMP1) (VEC3F-Z TEMP2))))
Чтобы не заморачиваться с тем, как называть временные переменные, в Common Lisp есть функция GENSYM, которая возвращает уникальный символ, которым не пользовались и не воспользуются где-то еще. Теперь макрос add3f будет совершать еще два действия: во-первых, создавать на каждый входной параметр уникальный символ функцией GENSYM, и, во-вторых, генерировать конструкцию LET с необходимыми присвоениями:
(defmacro add3f (&rest vecs) "Сложение векторов"
(let ((gensyms (loop for v in vecs collect (gensym))))
`(let (,@(loop for g in gensyms for v in vecs collect `(,g ,v)))
(vec3f (+ ,@(map 'list (lambda(v) `(vec3f-x ,v)) gensyms))
(+ ,@(map 'list (lambda(v) `(vec3f-y ,v)) gensyms))
(+ ,@(map 'list (lambda(v) `(vec3f-z ,v)) gensyms))))))
Абсолютно тоже самое нужно проделать и для разности. Для остальных векторных макросов с фиксированным кол-вом параметров все гораздо проще, можно воспользоваться известным макросом once-only. Изначально в Common Lisp его вроде как нет, но его легко можно написать самому, или взять отсюда:
(defmacro once-only ((&rest names) &body body)
(let ((gensyms (loop for n in names collect (gensym))))
`(let (,@(loop for g in gensyms collect `(,g (gensym))))
`(let (,,@(loop for g in gensyms for n in names collect ``(,,g ,,n)))
,(let (,@(loop for n in names for g in gensyms collect `(,n ,g)))
,@body)))))
Суть его проще всего понять на примере. Вот так вот выглядит исправленная версия mul3f:
(defmacro mul3f (v s) "Покомпонентное умножение вектора v на скаляр s"
(once-only (v s)
`(vec3f (* (vec3f-x ,v) ,s)
(* (vec3f-y ,v) ,s)
(* (vec3f-z ,v) ,s))))
once-only обеспечивает одноразовое вычисление переданных ей параметров.
Наконец, под катом привожу полный код текущей версии векторной математики:
(defmacro once-only ((&rest names) &body body)
(let ((gensyms (loop for n in names collect (gensym))))
`(let (,@(loop for g in gensyms collect `(,g (gensym))))
`(let (,,@(loop for g in gensyms for n in names collect ``(,,g ,,n)))
,(let (,@(loop for n in names for g in gensyms collect `(,n ,g)))
,@body)))))
(defmacro vec3f (x y z) "Создание вектора по координатам"
`(make-array 3 :element-type 'single-float :initial-contents (list ,x ,y ,z)))
;;; Привычное обращение к координатам вектора
(defmacro vec3f-x (v) `(elt ,v 0))
(defmacro vec3f-y (v) `(elt ,v 1))
(defmacro vec3f-z (v) `(elt ,v 2))
(defmacro mul3f (v s) "Покомпонентное умножение вектора v на скаляр s"
(once-only (v s)
`(vec3f (* (vec3f-x ,v) ,s)
(* (vec3f-y ,v) ,s)
(* (vec3f-z ,v) ,s))))
(defmacro div3f (v s) "Покомпонентное деление вектора v на скаляр s"
(once-only (v s)
`(vec3f (/ (vec3f-x ,v) ,s)
(/ (vec3f-y ,v) ,s)
(/ (vec3f-z ,v) ,s))))
(defmacro dot3f (a b) "Скалярное произведение векторов"
(once-only (a b)
`(+ (* (vec3f-x ,a) (vec3f-x ,b))
(* (vec3f-y ,a) (vec3f-y ,b))
(* (vec3f-z ,a) (vec3f-z ,b)))))
(defmacro qlen3f (v) "Квадрат длины вектора" (once-only (v) `(dot3f ,v ,v)))
(defmacro len3f (v) "Длина вектора" `(sqrt (qlen3f ,v)))
(defmacro norm3f (v) "Нормирование вектора"
(once-only (v) `(div3f ,v (len3f ,v))))
(defmacro add3f (&rest vecs) "Сложение векторов"
(let ((gensyms (loop for v in vecs collect (gensym))))
`(let (,@(loop for g in gensyms for v in vecs collect `(,g ,v)))
(vec3f (+ ,@(map 'list (lambda(v) `(vec3f-x ,v)) gensyms))
(+ ,@(map 'list (lambda(v) `(vec3f-y ,v)) gensyms))
(+ ,@(map 'list (lambda(v) `(vec3f-z ,v)) gensyms))))))
(defmacro sub3f (&rest vecs) "Вычитание векторов"
(let ((gensyms (loop for v in vecs collect (gensym))))
`(let (,@(loop for g in gensyms for v in vecs collect `(,g ,v)))
(vec3f (- ,@(map 'list (lambda(v) `(vec3f-x ,v)) gensyms))
(- ,@(map 'list (lambda(v) `(vec3f-y ,v)) gensyms))
(- ,@(map 'list (lambda(v) `(vec3f-z ,v)) gensyms))))))
(defmacro cross3f (a b) "Векторное произведение векторов"
(once-only (a b)
`(vec3f (- (* (vec3f-y ,a) (vec3f-z ,b)) (* (vec3f-z ,a) (vec3f-y ,b)))
(- (* (vec3f-z ,a) (vec3f-x ,b)) (* (vec3f-x ,a) (vec3f-z ,b)))
(- (* (vec3f-x ,a) (vec3f-y ,b)) (* (vec3f-y ,a) (vec3f-x ,b))))))
(defmacro triple3f (a b c) "Смешанное произведение векторов"
`(dot3f (cross3f ,a ,b) ,c))
Читать дальше......
июля 21, 2010
Собственная линейная алгебра на лиспе 1
Под векторами ниже по тексту я буду подразумевать вектора в смысле линейной алгебры, а не в смысле стандартной структуры VECTOR в лиспе.
Векторные операции в больших количествах могут съедать много времени на обработку, особенно при делениях и извлечениях корней. В связи с тем, что вектора не требуют никакой метаинформации о себе, от них ничего не планируется наследовать, и все что от них нужно — умение выполнять несколько простых операций (сложение, умножение, вычисление длины, скалярные и векторные произведения), то я принял решение реализовать их в виде массива фиксированной длины в надежде на то, что он обеспечит высокое быстродействие.
Будем писать векторные операции для трехмерного вектора с SINGLE-FLOAT в качестве типа каждой компоненты. Массивы в лиспе создаются функцией MAKE-ARRAY. Таким образом, чтобы создать новый вектор с координатами (50 60 70) нужно написать
(make-array 3 :element-type 'single-float :initial-contents (list 50.0 60.0 70.0))
Код прост и понятен, но слишком длинный, да и запомнить такое не так просто. Хотелось бы писать что-то вроде (vec3f 50.0 60.0 70.0), и чтоб компилятор вместо этого выражения сам бы подставлял make-array с нужными параметрами. (На самом деле, в лиспе принято функции и макросы создания данных называть начиная с «make-», то есть в моём случае должно было бы быть «(make-vec3f 50.0 60.0 70.0)», но из соображений лаконичности я оставлю vec3f.) Напрашивается такой вот макрос
(defmacro vec3f (x y z) "Создание вектора по координатам"
`(make-array 3 :element-type 'single-float :initial-contents (list ,x ,y ,z)))
Теперь уже можно писать (vec3f 50.0 60.0 70.0). Для работы с вектором крайне полезно уметь узнавать его координаты. В лиспе для доступа к элементам массива используется функция ELT, т.е. получение x-координаты вектора v можно осуществить вызовом (elt v 0), y-координаты — (elt v 1), z-координаты — (elt v 2). Подобные записи не всегда являются наглядными, многие люди (и я в том числе) привыкли к «x», «y» и «z», поэтому напрашивается еще тройка макросов:
;;; Привычное обращение к координатам вектора
(defmacro vec3f-x (v) `(elt ,v 0))
(defmacro vec3f-y (v) `(elt ,v 1))
(defmacro vec3f-z (v) `(elt ,v 2))
База написана, приступаем к содержательной части. Так как я хочу добиться быстродействия и не сильно беспокоюсь за размер скомпилированного кода, — буду пользоваться преимущественно макросами, чтобы компилятор разворачивал выражения в код, использующий только MAKE-ARRAY, ELT и арифметические функции. Возможно, что это ошибочный подход, — поживём-увидим.
При помощи написанного можно сходу написать пачку полезных операторов:
(defmacro mul3f (v s) "Покомпонентное умножение вектора v на скаляр s"
`(vec3f (* (vec3f-x ,v) ,s)
(* (vec3f-y ,v) ,s)
(* (vec3f-z ,v) ,s)))
(defmacro div3f (v s) "Покомпонентное деление вектора v на скаляр s"
`(vec3f (/ (vec3f-x ,v) ,s)
(/ (vec3f-y ,v) ,s)
(/ (vec3f-z ,v) ,s)))
(defmacro dot3f (a b) "Скалярное произведение векторов"
`(+ (* (vec3f-x ,a) (vec3f-x ,b))
(* (vec3f-y ,a) (vec3f-y ,b))
(* (vec3f-z ,a) (vec3f-z ,b))))
(defmacro qlen3f (v) "Квадрат длины вектора" `(dot3f ,v ,v))
(defmacro len3f (v) "Длина вектора" `(sqrt (qlen3f ,v)))
(defmacro norm3f (v) "Нормирование вектора"
`(div3f ,v (len3f ,v)))
Теперь вызов (len3f (vec3f 30.0 40.0 0.0)) выдаст «50.0». Осталось самое сложное в этом посте: сложение и вычитание векторов.
Сложность заключается в том, что мы хотим написать макрос не для сложения двух векторов, а для сложения сколь угодного кол-ва векторов. Например, (add3f a b c d) должно сложить четыре вектора.
Чтобы это провернуть, нужно задействовать сразу несколько возможностей лиспа. Во-первых, — возможность передавать произвольное кол-во параметров в функцию. Это осуществляется ключем &rest в списке параметров, тогда параметры передадутся списком.
Во-вторых, для вычисления x-координаты результата нужно собрать все x-координаты каждого вектора в список и сложить их всех. Например, если нам на вход дали список векторов (a b c d), то для получения x-координаты результата мы должны просуммировать элементы списка ((vec3f-x a) (vec3f-x b) (vec3f-x c) (vec3f-x d)).
Для этих целей удобно воспользоваться встроенной функцией MAP. Она получает на вход функцию и список, и применяет эту функцию к каждому элементу списка. Например, такой вот вызов
(map 'list (lambda(v) (vec3f-x v)) (list (vec3f 1.0 2.0 3.0) (vec3f 4.0 5.0 6.0) (vec3f 7.0 8.0 9.0)))
вернёт список (1.0 4.0 7.0) — будет применена функция под lambda к каждому элементу под list.
Теперь вернёмся к нашим сложениям. В макросе нужно получить список выражений вида «((vec3f-x v1) (vec3f-x v2) ...)» и подставить этот список под знак суммирования в «(+ ...)». Подстановка списка в выражение осуществляется конструкцией «,@». Приходим к такому вот:
(defmacro add3f (&rest vecs) "Сложение векторов"
`(vec3f (+ ,@(map 'list (lambda(v) `(vec3f-x ,v)) vecs))
(+ ,@(map 'list (lambda(v) `(vec3f-y ,v)) vecs))
(+ ,@(map 'list (lambda(v) `(vec3f-z ,v)) vecs))))
Аналогично для разности:
(defmacro sub3f (&rest vecs) "Вычитание векторов"
`(vec3f (- ,@(map 'list (lambda(v) `(vec3f-x ,v)) vecs))
(- ,@(map 'list (lambda(v) `(vec3f-y ,v)) vecs))
(- ,@(map 'list (lambda(v) `(vec3f-z ,v)) vecs))))
Кому-то может показаться, что add3f — очень тяжелая штука с циклом и созданием промежуточного списка, и потому будет тормозить. На самом деле это не так — это макрос, все его вызовы будут раскрыты в код еще при компиляции, и на этапе выполнения никаких циклов уже не будет. Для тестировния в лиспе есть функция macroexpand, которая раскрывает данное ей выражение с макросом в то, во что оно раскроется при компиляции. Например, набрав
(macroexpand '(add3f a b c d e f))
получим:
(MAKE-ARRAY 3 :ELEMENT-TYPE 'SINGLE-FLOAT :INITIAL-CONTENTS
(LIST (+ (VEC3F-X A) (VEC3F-X B) (VEC3F-X C) (VEC3F-X D)
(VEC3F-X E) (VEC3F-X F))
(+ (VEC3F-Y A) (VEC3F-Y B) (VEC3F-Y C) (VEC3F-Y D)
(VEC3F-Y E) (VEC3F-Y F))
(+ (VEC3F-Z A) (VEC3F-Z B) (VEC3F-Z C) (VEC3F-Z D)
(VEC3F-Z E) (VEC3F-Z F))))
Кроме того, VEC3F-X будет раскрыто в вызов ELT, поэтому в действительности во время выполнения будет выполнено ровно столько операций, сколько нужно, и ничего лишнего не будет.
Реализовать функцию cross3f, вычисляющую векторное произведение, оставлю на домашнее задание. На этом пока всё.
Читать дальше......
июля 19, 2010
(print "Hello world!")
Недавно я решил написать собственный простой язык, по синтаксису совпадающий с лисп, но заточенный под мои собственные нужды, — метапрограммирование, выполнение сгенерированного на лету кода и прочее.
Около месяца в свободное время проектировал стандартный набор функций и как это все должно действовать, написал интерпретатор. А сегодня я узнал о Common Lisp и осознал, что всё, что я напридумывал, уже придумали до меня.
Теперь временно приостанавливаю собственные потуги по написанию лиспа, и начинаю изучать Common Lisp :)
Читать дальше......
июля 06, 2010
Печать нестандартных символов
Иногда приходится набирать символы, которых нет на клавиатуре. Это всегда сопровождается некоторыми неудобствами.
Сегодня нашел удобную программу для ввода нестандартных символов — «Type It Easy».
Теперь тире и кавычки набираю комбинациями «Alt -», «Alt <» и «Alt >» соответственно. Причем прямо в редакторе, аське или браузере, т.е. «Type It Easy» действует как «Punto Switcher».
Любой юникод-символ можно таким же образом назначить на любую клавишу. Поэтому печать частоиспользуемых математических символов тоже упростилась.
Очень удобно.
Читать дальше......