Ваш вопрос довольно широкий, давайте рассмотрим простой пример для 2D-фигур.
Код ниже определяется тем, что мы можем написать это:
(isomorphism (alexandria:compose (scale 10)
(lift #'round)
(rotate 90))
(triangle (point 0 0)
(point 1 0)
(point 0 1)))
=> (TRIANGLE (POINT 0 0) (POINT 0 10) (POINT -10 0))
Это вычисляет простую функцию преобразования, известную как изоморфизм (который сохраняет форму), который является сначала вращением, затем округлением вычисленных точек, затем операцией масштабирования. Результатом является список, описывающий полученную форму. Копирование фигуры - это просто изоморфизм с функцией #'identity
(но это немного бесполезно, если вы идете чисто функциональным путем).
Примечание: округление здесь, например. вернуться к нулю, когда cos / sin дают очень маленькие значения; использование поплавков нарушает сохранение формы и округление, но здесь, когда они объединены, результирующая форма является фактическим изоморфизмом. Этот бит правильности / точности может или не может быть важным в зависимости от цели ваших потребностей. Вы также можете описать, какие преобразования применяются и только «растеризовать» их для отображения.
Функции преобразования работают со списком координат и возвращают список координат:
(defun translate (dx &optional (dy dx))
(lambda (xy) (mapcar #'+ xy (list dx dy))))
(defun scale (sx &optional (sy sx))
(lambda (xy) (mapcar #'* xy (list sx sy))))
(defun rotate (degrees)
(let* ((radians (* degrees pi 1/180))
(cos (cos radians))
(sin (sin radians)))
(lambda (xy)
(destructuring-bind (x y) xy
(list (- (* x cos) (* y sin))
(+ (* y cos) (* x sin)))))))
(defun lift (fn)
(lambda (things)
(mapcar fn things)))
Функция isomorphism
определяется следующим образом и рекурсивно разрушает фигуры в тег типа (который удваивается как конструктор) и компоненты, а в случае точек применяет функцию преобразования:
(defun isomorphism (transform shape)
(flet ((isomorphism (s) (isomorphism transform s)))
(with-shape (constructor components) shape
(apply constructor
(if (eq constructor 'point)
(funcall transform components)
(mapcar #'isomorphism components))))))
Я определил shape
и with-shape
следующим образом, чтобы немного абстрагироваться от того, как они представлены:
(defun shape (constructor components)
(list* constructor components))
(defmacro with-shape ((constructor components) shape &body body)
`(destructuring-bind (,constructor &rest ,components) ,shape
,@body))
И я могу определять формы с помощью простых функций, которые могут выполнять или не выполнять некоторую проверку и нормализацию:
(defun point (&rest coords)
(shape 'point coords))
(defun triangle (a b c)
(shape 'triangle (list a b c)))
(defun rectangle (x0 y0 x1 y1)
(shape 'rectangle
(list (min x0 x1)
(min y0 y1)
(max x0 x1)
(max y0 y1))))
Обратите внимание, что конструктор всегда совпадает с символом имени функции. Это может быть реализовано с помощью макроса, где вам нужно только вернуть список компонентов:
(defconstructor point (x y)
(list x y))
Вы также можете создавать производные конструкторы из приведенного выше:
(defun rectangle-xywh (x y width height)
(rectangle x y (+ x width) (+ y height)))
Здесь приведенные выше формы определены в виде точек, но вы можете представить, что фигуры собираются из более мелких фигур:
(defun group (&rest shapes)
(shape 'group shapes))
Это небольшой игрушечный пример, но он может быть полезен в качестве отправной точки.
Тогда, если вы хотите принять форму и сделать разные копии, повернутые на 90 °, вы можете сделать:
(loop
for angle from 0 below 360 by 90
collect
(isomorphism (compose (lift #'round)
(rotate angle)
(scale 2)
(translate 10 0))
(group (triangle (point 0 0)
(point 1 0)
(point 0 1)))))