1

How can I decompose this process in a lisp-like languages?

  • Start with some geometrical figure.
  • We multiply it and get several copies.
  • Then we set this copies on a border of other figure with, e.q. equal distance between copies.
  • Then we set rotation of each object on the border with dependence from its position on the border.

What ways exist to decompose in functions this process as a whole: making copies of an object, arranging its copies on a border of other object, setting rotation of its copies?

Last step is specially interesting and also ways how to compose it with previous ones.

Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346
Mikail
  • 11
  • 2
  • 2
    Hi! Could you add an example of what you are talking about? this is a bit unclear to me. How would you do the task in pseudo-code or in another language of your choice? Is there something that you don't see how to translate in Lisp? or is your question maybe about how to design such a system at all, irrespective of a particular language? I have difficulties imaging what kind of answer you would like to have. – coredump Mar 30 '19 at 21:16
  • Thanks, @coredump! I'm asking about examples, tips, articles on system design: 1. without classes (without OOP), 2. working with any figures, 3. using functions as functions parameters (first class citizens), if suitable, 4. easy to add new operations on figures, 5. convenient syntax. – Mikail Mar 31 '19 at 04:38

1 Answers1

8

Your question is quite broad, let's have a simple example for 2D shapes.

The code below is defined to that we can write this:

(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))

This compute a simple transform function known as an isomorphism (which preserves shape), which is first a rotation, then a rounding of the computed points, then a scale operation. The result is a list describing the resulting shape. Copying a shape is simply the isomorphism with function #'identity (but it is a bit useless if you go with a pure functional way).

NB: the rounding is here e.g. to fall back to zero when cos/sin give very small floats ; the use of floats breaks shape preservation and so does rounding, but here when they are combined the resulting shape is an actual isomorphism. This bit of correctness/accuracy might or might not be important depending of the purpose of your needs. You could also describe what transformations are applied and only "rasterize" them for display.

The transform functions works over a list of coordinates, and return list of coordinates:

(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)))

The isomorphism function is defined as follows, and recursively destructures shapes into a type-tag (which doubles as a constructor) and components, and in case of points, apply the transform function:

(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))))))

I defined shape and with-shape as follows, to have a little bit of abstraction over how they are represented:

(defun shape (constructor components)
  (list* constructor components))

(defmacro with-shape ((constructor components) shape &body body)
  `(destructuring-bind (,constructor &rest ,components) ,shape
     ,@body))

And I can define shapes with simple functions, that may or may not perform some checking and normalization:

(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))))

Notice how the constructor always is the same symbol as the function's name. This could be enforced with a macro, where you only need return the list of components:

(defconstructor point (x y)
  (list x y))

You can also build derived constructors from the one above:

(defun rectangle-xywh (x y width height)
  (rectangle x y (+ x width) (+ y height)))

Here above shapes are defined in terms of points, but you can imagine having shapes being assembled from smaller shapes:

(defun group (&rest shapes)
  (shape 'group shapes))

This is a bit of a toy example, but that might be useful as a starting point.

Then, if you want to take a shape and make different copies rotated by increments of 90°, you could do:

(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)))))
coredump
  • 37,664
  • 5
  • 43
  • 77