Richard Waters' Series and lispmoo2


This is a quite technical fizzbuzz article about totally avoiding writing iteration with your own hands.

Based on these Mastodon toots by KMP, mdh, jackdaniel and all: https://mastodon.sdf.org/@screwtape/113490818622436062 I read AIM-1082; Optimization of Series Expressions: Part I: User's Manual for the Series Macro Package by Richard C. Waters

which is a bit of a read!

1. Long story short

Instead of the vast majority of iteration, you make these weird statements of intent. Waters' package infers the best way it knows how to do what you asked of it. Er…

2. Fizzbuzz With Series

(defun series-fizzbuzz-of (x)
  (let* ((nos (scan-range))
         (fizz-src (series 'fizz))
         (buzz-src (series 'buzz))
         (by3s (scan-range :from 0 :by 3))
         (by5s (Scan-range :from 0 :by 5))
         (fizzes (mask by3s))
         (buzzes (mask by5s)))
    (list (collect-nth x (#Mand fizzes fizz-src))
          (collect-nth x (#Mand buzzes buzz-src))
          (collect-nth x nos))))

I don't know about you, but this looks crazy to me. I never tried writing this jazz before just now. Basically I just calculate the fizzbuzz information for one number, which can be interpreted. The idea being I can pick a large-enough number, and compare its speed.

3. Me trying to use the loop facility

(defun loopfizz-of (no)
  (let ((res (loop :repeat (1+ no)
                   :for x :from 0 :by 1
                   :for y := '0 :then y
                   :for z := '0 :then z
                   :when (eql x y) 
                     :do (incf y 3)
                   :When (eql x z) 
                     :do (incf z 5)
                   :finally (return
                              (list x
                                    (decf y 3)
                                    (decf z 5))))))
    (list
     (car res)
     (when (= (car res) (cadr res)) 'fizz)
     (when (= (car res) (cadr res)) 'buzz))))

4. Results

Series appears to be about 10% better.

4.1. Series

LISPMOO2/USER> (time (eg 1555432))
Evaluation took:
  0.065 seconds of real time
  0.063839 seconds of total run time (0.063798 user, 0.000041 system)
  98.46% CPU
  71,738,506 processor cycles
  0 bytes consed

(NIL NIL 1555432)

4.2. Loop

LISPMOO2/USER> (time (loopfizz-of 1555432))
Evaluation took:
  0.080 seconds of real time
  0.079086 seconds of total run time (0.078894 user, 0.000192 system)
  98.75% CPU
  87,505,433 processor cycles
  0 bytes consed
  
(1555432 NIL NIL)

5. What's the difference, is Series not a loop?

A bunch of dolists are made out of Series' macros, so it does a loop.

The difference is that you don't write the loop. You make one Series, then another, then another one out of the first two, and so on until the values you want can be addressed in one of your series.

This is a different approach to loop; in the loop facility, you describe how to do the iteration. In Waters' Series macros, you describe Series (in terms of other Series) until you can point a finger at where what you want is. You don't know anything about how your values are actually resolved, though sometimes the result of the macro is a warning that it doesn't think you did a good job in one way or another.

6. Series are appropriate for lispmoo2

because they track how yduJ tells us to program in-MOO in her famous wind-up-duck tutorial. Basically she says to prefer the Series approach and calculating what hypothetically happened lazily when asked, rather than painstakingly simulating the wind-up duck moment to moment to moment. Save those cycles for the players who need them.

Aesthetically, a Series solution is a static graph inferred from your statements about it. This tracks lispmoo2, where I want a lispMOO2 $thing to really be a $thing, not a description of ongoing iteration.

On the other hand, when I want the world's clock to tick along moving us through real time; that still is and is written as iteration.

</div>

Author: screwlisp

Created: 2024-11-17 Sun 16:30

Validate

Get lispmoo2

Comments

Log in with itch.io to leave a comment.

* Not that you were about to say so, but I used counting up instead of just 

(time (list (mod 1555432 3) (mod 1555432 5) 1555432))

in the spirit of fizzbuzz.