screwlisp proposes kittens

ANSI cl like python-slicing lisp read macro

| link | Last. 20260116T040451406920Z |

Writing something easy to ease myself back into the swing of things. On the mastodon I saw a professional python tutorial which I have reproduced in lisp off the cuff with a few notes.

(defvar *fruits*
  '(watermelon apple lime
    kiwi pear lemon orange))
(elt *fruits* 3)
(subseq *fruits* 0 3)
(defparameter *first3* *)
(subseq *fruits* 1 3)
(concatenate 'list
	     (subseq *fruits* 0 3)
	     (subseq *fruits* 3))
(concatenate 'list
	     (last *fruits*)
	     (butlast *fruits*))

in case you aren’t playing along, sending this line by line to the repl in emacs eev results in:

CL-USER> (defvar *fruits*
	     '(watermelon apple lime
	           kiwi pear lemon orange))
*FRUITS*
CL-USER> (elt *fruits* 3)
KIWI
CL-USER> (subseq *fruits* 0 3)
(WATERMELON APPLE LIME)
CL-USER> (defparameter *first3* *)
*FIRST3*
CL-USER> (subseq *fruits* 1 3)
(APPLE LIME)
CL-USER> (concatenate 'list
		(subseq *fruits* 0 3)
		(subseq *fruits* 3))
(WATERMELON APPLE LIME KIWI PEAR LEMON ORANGE)
CL-USER> (concatenate 'list
		(last *fruits*)
		(butlast *fruits*))
(ORANGE WATERMELON APPLE LIME KIWI PEAR LEMON)
CL-USER> 

You can mostly guess what the python is if you want to. Python’s syntaxful fruits[3:] in lisp is the symbolic expression (subseq *fruits* 3)

The *earmuffs* in lisp are conventional because it is useful to easily know if a name refers to a special i.e. non-lexical variable.

displaced arrays instead of lists lisp

| link | Last. 20260116T040651412916Z |

(defvar *vfruits* (coerce *fruits* 'vector))
(elt *vfruits* 3)
(subseq *vfruits* 0 3)
(defparameter *vfirst3* *)
(subseq *vfruits* 1 3)
(length *vfruits*)
(- * 3)
(adjust-array #() *
	      :displaced-to *vfruits*
	      :displaced-index-offset 3)
(adjust-array *vfruits* 3)
(concatenate 'vector ** *)

Note that I often use ANSI lisp’s repl special variables like * ā€œthe last first returnā€, ** ā€œthe second-last first returnā€.

If you did not just mash F8 in emacs eev to pitch that line by line into the repl like I did, here is a vectorful version of what we did with lists

CL-USER> (defvar *vfruits* (coerce *fruits* 'vector))
*VFRUITS*
CL-USER> (elt *vfruits* 3)
KIWI
CL-USER> (subseq *vfruits* 0 3)
#(WATERMELON APPLE LIME)
CL-USER> (defparameter *vfirst3* *)
*VFIRST3*
CL-USER> (subseq *vfruits* 1 3)
#(APPLE LIME)
CL-USER> (length *vfruits*)
7
CL-USER> (- * 3)
4
CL-USER> (adjust-array #() *
		:displaced-to *vfruits*
		:displaced-index-offset 3)
#(KIWI PEAR LEMON ORANGE)
CL-USER> (adjust-array *vfruits* 3)
#(WATERMELON APPLE LIME)
CL-USER> (concatenate 'vector ** *)
#(KIWI PEAR LEMON ORANGE WATERMELON APPLE LIME)
CL-USER> 

Seemingly unlike python, and unlike using subseq on a sequence resulting in a copied region list, using adjust-array to displace an empty lisp vector to be a region of another vector or array does not copy and results in a displaced-array (hence, :displaced-to).

lisp strings too and loop stepping iteration

| link | Last. 20260116T040833714914Z |

"hello!"
(adjust-array "" 3
	      :displaced-to *
	      :displaced-index-offset 3)

"hello!"
(1- (length *))
(With-output-to-string (*standard-output*)
  (loop :for x :downfrom *
	  :to 0 :by 2
	:do
	   (princ (elt ** x))))

Lisp strings are basically simple-vectors of characters, so they are just like the vectors above. Repl for those not playing along:

CL-USER> "hello!"
"hello!"
CL-USER> (adjust-array "" 3
		:displaced-to *
		:displaced-index-offset 3)
"lo!"
CL-USER> 
; No value
CL-USER> "hello!"
"hello!"
CL-USER> (1- (length *))
5
CL-USER> (With-output-to-string (*standard-output*)
	     (loop :for x :downfrom *
	   	  :to 0 :by 2
	   	:do
	   	   (princ (elt ** x))))
"!le"
CL-USER> 

while indexing and subsequencing a string are just the vector above again, indexing with a custom, maybe negative (ā€œfrom endā€) step in python like >>> "hello!"[6:0:-2] ⮕ '!le' are written explicitly in ansi common lisp’s loop facility domain specific language (or do).

Except for the explicit loop above, in ansi cl no iteration per se is being used, so it does not really make sense to supply an iterative step. Then, outputting to a string, or vector, or into an existing array, or letting loop collect and split lists like it wants to all seem too different to put in one bag together.

Read macro making lisp like python

| link | Last. 20260116T040155739926Z |

I think the idiom is to use symbolic expressions with clear meaning, but lisp does famously provide a lisp-powered macro system, so we could make an iterative ā€œslicerā€.

(defun iteratively-slice (s c n)
  "#step[(&optional start stop) seq]⮕ slice, inclusive limits"
  (declare (ignore c))
  (destructuring-bind
      (start stop)
      (read s)
    (let* ((input (read s))
	   (step (or n 1))
	   (start (or start 0))
	   (stop (or stop (length input))))
      (peek-char #\] s nil nil t)
      (read-char s nil nil t)
      `(loop :with new-vector
	       := (make-array 0 :adjustable t
				:fill-pointer 0)
	     :with input-sequence := ,input
	     :for idx :from ,start
	     ,(if (> stop start) :to :downto) ,stop
	     :by ,step
	     :do (vector-push-extend (elt input-sequence idx)
				     new-vector)
	     :finally (return new-vector)))))
(set-dispatch-macro-character #\# #\[ #'iteratively-slice)
#2[(6 2) "hello there" ]

Elliding the defun,

CL-USER> (set-dispatch-macro-character #\# #\[ #'iteratively-slice)
T
CL-USER> #2[(6 2) "hello there" ]
#(#\t #\o #\l)
CL-USER> 

where I guess my lisp #2[(6 2) "hello there" ] would be "hello there"[6:2:-2] in python.

We need to tell lisp to coerce it to a string: (coerce #(#\t #\o #\l) 'string) ⮕ "tol".

Drawing some conclusions

| link | Last. 20260116T041510325901Z |

Writing this really thrust upon me how different lisp and python are. fruits[:3]+fruits[3:] cute syntax in lisp is the symbolic expression

(concatenate 'list
	(subseq *fruits* 0 3)
	(subseq *fruits* 3))
  1. Perform the function named concatenate
  2. with a resulting type named list on
    • the function named subseq on the value named *fruits* from 0 below 3
    • the function named subseq on the value named *fruits* from 3

We saw it is possible and in specialized contexts convenient to modify lisp’s *readtable* with a python-like slicing syntax based on lisp’s loop facility.

CL-USER> #2[(10 1) '#.(loop :for x :below 11 :collect x)]
#(10 8 6 4 2)

however even here showing my python-style slicing read macro you see I wrote a symbolic description of the numbers I wanted. Sandewall resisted his university’s adoption of python because lisp’s symbolic normalised string data structure and conventions were not easily available in python.

I guess it would by myopic for me not to mention cl-series and cl-slice, but my examples have been pure ansi common lisp.

The python tutorial I saw on the Mastodon was https://www.pythonmorsels.com/slicing .

See you on the Mastodon thread!
screwlisp proposes kittens