screwlisp proposes kittens

cl-series, vector multiplication, assert and lisp interactivity norms

I thought I should furnish our incredible discussion yesterday about common lisp macro assertables with Vassil Nikolov and Kent M. Pitman with my own practical example of assert similar to the hyperspec’s example.

Vassil is working (when he has time) on an his assertables macros for hierarchical, programmatic control of including and excluding assertions in code.

I will write a trivial vector multiplication using cl-series - i.e., a lazy, tight, declarative, generated in-order traversal, withstanding that this is a highschoolish task.

We will write a macro that generates a specific dimension vector multiplier, and assert that arguements’ dimensions are correct. The assertion will turn out to be important.

While writing this, it also occured to me that the interactive expert useage generated by lisp for me in order to help me in the context of my failed assertion is very important iconic classic lisp useage.

emacs eev and setup

#|
 (eepitch-sbcl)
 (eepitch-kill)
 (eepitch-sbcl)
(require :series)
(series::install)
|#

Example of planned vector multiplication

(equal ((generate-vector-multiplier (2))
	'(1 2) '((3)
		 (4)))
       (+ (* 1 3)
	  (* 2 4)))

Common lisp macro to generate a vector multiplier lambda

(defmacro generate-vector-multiplier ((lengths))
  `(lambda (a b)
     (assert (and (equal (length a) ,lengths)
		  (equal (length b) ,lengths))
	     (a b)
	     "Dimension 1 of the first vector, ~d,
should be equal to dimension 2 of the second vector, ~d,
should be equal to the vector-multiplier's length, ~d."
	     (length a) (length b) ,lengths)

     (let* ((series-a (scan a))
	    (series-b (scan b))
	    (series-bᔀ (#Mcar series-b))
	    (series-abᔀ (#M* series-a series-bᔀ)))
       (collect-sum series-abᔀ))))

Example use in the repl

generate-vector-multiplier for lengths 2.

CL-USER> (generate-vector-multiplier (2))
#<FUNCTION (LAMBDA (A B)) {5373E07B}>

Try with correct vector dimensions

CL-USER> (funcall * '(1 2) '((3) (4)))
11

Great. Also remember in lisp interaction, * is ‘the last first return’, ** is ‘the second last first return’, ***, third.

Try with an incorrect vector dimension

Looks good but what about (funcall ** '(1 2) '((3) (4) (5)))? We end up interactively in lisp’s debugger with the message we just wrote:

The assertion fails message/debugger menu

Dimension 1 of the first vector, 2,
should be equal to dimension 2 of the second vector, 3,
should be equal to the vector-multiplier's length, 2.
   [Condition of type SIMPLE-ERROR]

Restarts:
 0: [CONTINUE] Retry assertion with new values for A, B.
 1: [RETRY] Retry SLIME REPL evaluation request.
 2: [*ABORT] Return to SLIME's top level.
 3: [ABORT] abort thread (#<THREAD "repl-thread" RUNNING {1001380003}>)

Backtrace:
  0: (SB-KERNEL:ASSERT-ERROR (AND (EQUAL (LENGTH A) 2) (EQUAL (LENGTH B) 2)) (A B) "Dimension 1 of the first vector, ~d, ..)
  1: ((LAMBDA (A B)) (1 2) ((3) (4) (5)))
  2: (SB-INT:SIMPLE-EVAL-IN-LEXENV (FUNCALL ** (QUOTE (1 2)) (QUOTE (# # #))) #<NULL-LEXENV>)
  3: (EVAL (FUNCALL ** (QUOTE (1 2)) (QUOTE (# # #))))
 --more--

I clicked CONTINUE:

Continuing (prompts me for values to change)

CL-USER> (funcall ** '(1 2) '((3) (4) (5)))
The old value of A is (1 2).
Do you want to supply a new value?  (y or n) n
The old value of B is ((3) (4) (5)).
Do you want to supply a new value?  (y or n) y
Type a form to be evaluated:
#((3) (4))
11

You can also notice that I snuck in a vector (of lists) instead of a list; cl-series operates on lisp sequences, not particularly just vectors or lists.

Alternate, assertionless lambda problem

We find out that our assertion was critically important, because of a desireable property of cl-series. Let’s make a version without the assertion of matching lengths:

(defmacro generate-jagged-vector-multiplier ((lengths))
  `(lambda (a b)

     (let* ((series-a (scan a))
	    (series-b (scan b))
	    (series-bᔀ (#Mcar series-b))
	    (series-abᔀ (#M* series-a series-bᔀ)))
       (collect-sum series-abᔀ))))

Try it for the everything-right case above:

CL-USER> (generate-jagged-vector-multiplier (2))
; caught STYLE-WARNING:
;   The variable LENGTHS is defined but never used.
; 
; compilation unit finished
;   caught 1 STYLE-WARNING condition
GENERATE-JAGGED-VECTOR-MULTIPLIER
CL-USER> (generate-jagged-vector-multiplier (2))
#<FUNCTION (LAMBDA (A B)) {5373E7DB}>
CL-USER> (funcall * '(1 2) '((3) (4)))
11

Well, the warning kind of gives it away.

Try it for the vector mismatch case above:

CL-USER> (funcall ** '(1 2) '((3) (4) (5)))
11

cl-series lazily truncated the second vector without asking us. This is because series handles infinities for example through this laziness, cotruncating computation to the shortest participant (basically to implicitly cut out infinities).

However our intent here was vector multiplication, which is not defined on mismatched vector lengths. Furthermore, as we were warned above, our lengths arguement above is now actually doing nothing:

CL-USER> (funcall *** '(1 2 3) '((3) (4) (5)))
26

In summary

We included an assertion when generating specific-length matrix multiplication lambdas per the hyperspec

(defmacro generate-vector-multiplier ((lengths))
  `(lambda (a b)
     (assert (and (equal (length a) ,lengths)
		  (equal (length b) ,lengths))
	     (a b)
	     "Dimension 1 of the first vector, ~d,
should be equal to dimension 2 of the second vector, ~d,
should be equal to the vector-multiplier's length, ~d."
	     (length a) (length b) ,lengths)

     (let* ((series-a (scan a))
	    (series-b (scan b))
	    (series-bᔀ (#Mcar series-b))
	    (series-abᔀ (#M* series-a series-bᔀ)))
       (collect-sum series-abᔀ))))

this generated a choice for me to interactively resolve some assertion-failing data (note that I didn’t write these prompts):

CL-USER> (funcall ** '(1 2) '((3) (4) (5)))

<I select CONTINUE (with new values) from the interactive condition handler>

The old value of A is (1 2).
Do you want to supply a new value?  (y or n) n
The old value of B is ((3) (4) (5)).
Do you want to supply a new value?  (y or n) y
Type a form to be evaluated:
#((3) (4))
11

without the assertion, cl-series was implicitly cotruncating the incorrect length vector - good for working with infinities, bad for our definition of vector multiplication.

Fin

What do you think about ANSI CL’s assertions, my example, and cl-series’ historic, modern, declarative, lazy evaluation? Also, thinking about it, I think the way common lisp is seen to actively engage with its expert lispuser about the failed assertion with the image still in the context that the assertion failed is an incredibly important useage.

Anyway, talk in the Mastodon thread please (or come on the lispy gopher show live with everyone).

I hope we can share and discuss these futuristic and bleeding edge ANSI common lisp features and concerns with everyone who kinda feels these ideas should have existed (and actually do) and even have been standardized with ANSI, and be actively pursued and expanded with substandards like cl-series and assertables.

Please do share and disseminate this article and yesterday’s interview and everything by whatever your personal channels are if you agree.

screwlisp proposes kittens