Добро пожаловать!

Я Дож, программирование — моё хобби. По мере того, как я осваиваю что-то новое, стараюсь об этом написать пост.
На текущий момент в блоге затронуты следующие темы: vim, free pascal, lisp, forth, m4
Занимаюсь разработкой своего языка под названием DEmbro, подбробней: wiki и svn
Для постов, не связанных с программированием, у меня есть отдельное жж.

июля 31, 2010

Обработка ошибок внешних библиотек

Возможно, что вам приходилось когда-нибудь писать код создания окна на чистом 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)


Читать дальше......

июля 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 (0 6) A (1 6) B (2 6) C (3 6) D (4 6) E (5 6) F (6 6))


(watch (L) A)

(PROGN (0 1) A (1 1))


(watch (L))

(PROGN (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».

Любой юникод-символ можно таким же образом назначить на любую клавишу. Поэтому печать частоиспользуемых математических символов тоже упростилась.

Очень удобно.


Читать дальше......

Постоянные читатели

Обо мне

Моя фотография
Мой e-mail: vitek_03(at)mail(dot)ru