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))
- Perform the function named
concatenate - with a resulting type named
liston -
- the function named
subseqon the value named*fruits*from 0 below 3
- the function named
-
- the function named
subseqon the value named*fruits*from3
- the function named
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 .