How to assign multiple constants within one macro call - macros

I want to assign multiple constants within one macro call. But the code below only assigns the last constant, the constants which where defined before are not available.
; notes.lisp
(defconstant N_oct0 0)
(defmacro N_defheight(_oct _note _offset)
`(defconstant ,(read-from-string (concatenate 'string _note _oct))
,(+ (eval (read-from-string (concatenate 'string "N_oct" _oct)))
_offset)))
(defmacro N_octave(_octave)
`(N_defheight ,_octave "c" 0)
`(N_defheight ,_octave "c#" 1)
`(N_defheight ,_octave "des" 1)
`(N_defheight ,_octave "d" 2)
`(N_defheight ,_octave "d#" 3)
`(N_defheight ,_octave "es" 3)
`(N_defheight ,_octave "e" 4)
`(N_defheight ,_octave "f" 5)
`(N_defheight ,_octave "f#" 6)
`(N_defheight ,_octave "ges" 6)
`(N_defheight ,_octave "g" 7)
`(N_defheight ,_octave "g#" 8)
`(N_defheight ,_octave "as" 8)
`(N_defheight ,_octave "a" 9)
`(N_defheight ,_octave "a#" 10)
`(N_defheight ,_octave "b" 10)
`(N_defheight ,_octave "h" 11))
(N_octave "0")
After loading the file in sbcl, I have only the h0 constant, but none of the c0..b0 constants.
$ sbcl
This is SBCL 1.0.40.0.debian, an implementation of ANSI Common Lisp.
More information about SBCL is available at <http://www.sbcl.org/>.
SBCL is free software, provided as is, with absolutely no warranty.
It is mostly in the public domain; some portions are provided under
BSD-style licenses. See the CREDITS and COPYING files in the
distribution for more information.
* (load "notes")
T
* h0
11
* c0
debugger invoked on a UNBOUND-VARIABLE in thread #<THREAD
"initial thread" RUNNING
{1002C34141}>:
The variable C0 is unbound.
Type HELP for debugger help, or (SB-EXT:QUIT) to exit from SBCL.
restarts (invokable by number or by possibly-abbreviated name):
0: [ABORT] Exit debugger, returning to top level.
(SB-INT:SIMPLE-EVAL-IN-LEXENV C0 #<NULL-LEXENV>)
0]
So how can I change the macro to execute all defconstant calls, not only the last one?

Other answers already pointed out the correct solution: to use PROGN.
Here some remarks about 'style':
(defmacro N_defheight(_oct _note _offset)
`(defconstant ,(read-from-string (concatenate 'string _note _oct))
,(+ (eval (read-from-string (concatenate 'string "N_oct" _oct)))
_offset)))
Variables with leading underscore have what purpose? This not common Lisp practice in Common Lisp
READ-FROM-STRING might be replaced by INTERN and STRING-UPCASE.
You might want to control the package for the symbol generated by INTERN (or READ-FOR-STRING).
EVAL can be replaced by SYMBOL-VALUE
CONCATENATE could be replaced by FORMAT: (format nil "N-OCT~a" oct)
for a defining macro, DEF should be the beginning of the name. That's just a convention.
example:
(defmacro N_octave(_octave)
`(progn
(N_defheight ,_octave "c" 0)
...
(N_defheight ,_octave "h" 11))
Above could be simplified with a simple iteration:
`(progn
,#(loop for (note offset) in '(("c" 0) ("c#" 1) ... ("h" 11))
collect (list 'defheight octave note offset)))
or using MAPCAR
`(progn
,#(mapcar (lambda (desc)
(destructuring-bind (note offset) desc
(list 'defheight octave note offset)))
'(("c" 0) ("c#" 1) ... ("h" 11))))
The effect is less typing and the important symbols are written only once.
One has to decide what is better: many similar looking statements or a small program transforming a data description.
But there is another problem: the data is coded into the macro.
This is wrong. A macro should do the code transformation and not contain data. Again, you can do everything, but good Lisp requires to have some feeling for programming style. I would put the notes and offsets as a list into a variable and use that in the macro, or provide it as a parameter:
(defvar *notes-and-offsets*
'(("c" 0) ("c#" 1) ... ("h" 11)))
(defoctave (octave notes-and-offsets)
`(progn
,#(mapcar (lambda (desc)
(destructuring-bind (note offset) desc
(list 'defheight octave note offset)))
(eval notes-and-offsets))))
(defoctave "0" *notes-and-offsets*)
Now there is another problem. We define constants with names like C0. A constant in Lisp always refers to the global constant value. Rebinding is not allowed. That means that C0 is no longer a valid local variable name in your program. If you know that you will never use C0 as a variable name, that's fine - but this problem may not be known later during maintenance. For this reason, it is good style to put plus signs around names of constants like this: +C0+. Again, just a convention. You can also use your own specialized naming convention, which should not clash with your names for variables. like NOTE-C0.
If your intention is to always use an identifier like c0 as a global name for a constant note value, then you don't have a problem - you just need to understand that then with DEFCONSTANT, you can't use c0 no longer as a variable. It might be a good idea to have your own package, then.
Next: when you want to use variables when computing a macro expansion, then you need to make sure that the variables have values. Either load a file before or use EVAL-WHEN.
This leads to this code:
(eval-when (:compile-toplevel :load-toplevel :execute)
(defvar *n-oct0* 0)
(defvar *notes-and-offsets*
'((c 0) (c# 1) (des 1) (d 2)
(d# 3) (es 3) (e 4) (f 5)
(f# 6) (ges 6) (g 7) (g# 8)
(as 8) (a 9) (a# 10) (b 10)
(h 11)))
) ; end of EVAL-WHEN
(defmacro defheight (oct note offset)
`(defconstant ,(intern (format nil "~a~a" note oct))
(+ ,(intern (format nil "*N-OCT~a*" oct))
,offset)))
(defmacro defoctave (octave notes-and-offsets)
`(progn
,#(mapcar (lambda (note offset)
(list 'defheight octave note offset))
(mapcar #'first (eval notes-and-offsets))
(mapcar #'second (eval notes-and-offsets)))))
(defoctave 0 *notes-and-offsets*)

You need to expand to a progn form
(defmacro N_octave(_octave)
`(progn
(N_defheight ,_octave "c" 0)
(N_defheight ,_octave "c#" 1)
(N_defheight ,_octave "des" 1)
(N_defheight ,_octave "d" 2)
(N_defheight ,_octave "d#" 3)
(N_defheight ,_octave "es" 3)
(N_defheight ,_octave "e" 4)
(N_defheight ,_octave "f" 5)
(N_defheight ,_octave "f#" 6)
(N_defheight ,_octave "ges" 6)
(N_defheight ,_octave "g" 7)
(N_defheight ,_octave "g#" 8)
(N_defheight ,_octave "as" 8)
(N_defheight ,_octave "a" 9)
(N_defheight ,_octave "a#" 10)
(N_defheight ,_octave "b" 10)
(N_defheight ,_octave "h" 11)))
Your macro code is instead computing all expansions and throwing them away except the last one (as always happens for all forms except last one in a function body).
Note that probably this is one of the case in which eval-when comes into play but I cannot really suggest anything about it because I've yet to truly understand all its intricacies (and I'm not even sure I want to :-) )

Several statements are not concatenated this way in Lisp. Try to use the progn construct:
(defmacro N_octave(_octave)
`(progn (N_defheight ,_octave "c" 0)
(N_defheight ,_octave "c#" 1)
... ))

Related

Common Lisp macro let-curry - not working

I found myself calling lots of methods whose first argument is a complex object from a given class.
Whilst with-slots and with-accessors are useful, generic methods cannot be bound in this way. So I thought: if we could locally curry any functions, slots + accessors + generic functions + functions could all be addressed with the same construct.
Example of code I want to clean up:
(defun clox-string (scanner)
"Parse string into a token and add it to tokens"
(loop while (and (char/= #\" (peek scanner))
(not (at-end-p scanner)))
do
(if (char= #\Newline (peek scanner)) (incf (line scanner))
(advance scanner)))
(when (at-end-p scanner)
(clox.error::clox-error (line scanner) "Unterminated string.")
(return-from clox-string nil))
(advance scanner) ;; consume closing "
(add-token scanner 'STRING (subseq (source scanner)
(1+ (start scanner))
(1- (current scanner)))))
This would be cleaner (I'm imitating this in CL https://craftinginterpreters.com/scanning.html#reserved-words-and-identifiers but I often end up with more verbose and less readable code than in Java - specially when using this classes a lot). As in CL methods don't belong to classes you end up declaring such arguments over and over. This would be a bit better:
(defun clox-string (scanner)
"Parse string into a token and add it to tokens"
(let-curry scanner (peek at-end-p line source start current advance add-token)
(loop while (and (char/= #\" (peek))
(not (at-end-p)))
do
(if (char= #\Newline (peek)) (incf (line))
(advance)))
(when (at-end-p)
(clox.error::clox-error (line) "Unterminated string.")
(return-from clox-string nil))
(advance) ;; consume closing "
(add-token 'STRING (subseq (source)
(1+ (start))
(1- (current)))))
sketch of macro (not working):
;; Clearly not as I don't understand macros very well :) non-working code:
(defmacro let-curry (obj functions &body body)
"Locally curry all functions"
(let ((fn (gensym)))
`(flet (loop
for ,fn in ,functions
collect (list ,fn (&rest args)
(funcall ,fn ,obj args)))
,#body)))
EDIT (ADD): Notice that scanner is a class; start, source, line, etc., accessors to the slots with the same name; add-token a generic function of more than one argument, advance a generic method of one argument:
(defclass scanner ()
((source
:initarg :source
:accessor source)
...
(...)))
(defmethod advance ((scanner scanner)) ...)
(defmethod add-token ((scanner scanner) token-type) ...)
Simpler Example with error:
;; With
(defun add (x y) (+ x y))
(defun mul (x y) (* x y))
;; I want to have this:
(let-curry 1000 (add mul)
(print (add 3))
(print (mul 3)))
;; expanding to:
(flet ((add (y) (add 1000 y))
(mul (y) (mul 1000 y)))
(print (add 3))
(print (mul 3)))
;; but instead I'm getting:
Execution of a form compiled with errors.
Form:
(FLET (LOOP
FOR
#1=#:G777
IN
(ADD MUL
)
COLLECT
(LIST #1#
(&REST ARGS)
(FUNCALL #1# 1000 ARGS)))
(PRINT (ADD 3))
(PRINT (MUL 3)))
Compile-time error:
The FLET definition spec LOOP is malformed.
[Condition of type SB-INT:COMPILED-PROGRAM-ERROR]
Thanks! The basic question is: is it possible to make such macro work?
Your version didn't expand to what you wanted but:
(flet (loop for #:g8307 in (add mul) collect (list #:g8307 (&rest args) (funcall #:g8307 1000 args)))
(print (add 3)) (print (mul 3)))
Now the loop needs to be done at macro expansion time.
Here is a working version:
(defmacro let-curry (obj (&rest functions) &body body)
"Locally curry all functions"
`(flet ,(loop for fn in functions
collect `(,fn (&rest args)
(apply #',fn ,obj args)))
,#body))
;; test it using add and mul from OP
(macroexpand-1 '(let-curry 10 (add mul) (list (add 5) (mul 5))))
;; ==>
(flet ((add (&rest args) (apply #'add 10 args))
(mul (&rest args) (apply #'mul 10 args)))
(list (add 5) (mul 5)))
(let-curry 10 (add mul) (list (add 5) (mul 5)))
;; ==> (15 50)
Using gensym is only needed if you are in danger of shadowing/colliding something or to ensure evaluation order is least surprising, but in your case you actually want to shadow the original names with the curried version so it makes sense to just use the original name.
If you want to have more than one argument you should use apply
since you know the function is in the function namespace you need to call #'symbol instead of symbol.
I've done (&rest functions) instead of functions in the prototype that with bad usage (not a list) you get a compile time error and it is more preciese.

Is the variable defined by a let mutable in Common Lisp?

In, for example this let in Common Lisp
(let ((a 5)) (print a))
Is a mutable as with defparameter, or is a constant as is the case with defvar?
You can change what a is bound to, i.e. make a refer to something else:
(let ((a 5)) (setf a 10))
If the value referenced by a is mutable, you can mutate it:
(let ((a (list 5))) (setf (first a) 10))
Is a mutable as with defparameter, or is a constant as is the case with defvar?
No, DEFVAR does not define constants.
(defvar *var* :value)
(setf *var* 5)
Then:
*var*
=> 5
What happens is that when you evaluate a DEFVAR form, it first checks whether the symbol is already bound. If this is the case, then the existing value is kept in place. On the other hand, DEFPARAMETER always reintializes the variable.
Here's some examples that might clarify this. You can try them at the repl. Try to think about whether they are more like defvar or defparameter
(loop repeat 2 do
(let ((a 1)) (print a) (setf a 5) (print a)))
(loop repeat 2 do
(let ((a (list 1 2)))
(print (first a))
(setf (first a) 5)
(print (first a))))
(loop repeat 2 do
(let ((a '(1 2)))
(print (first a))
(setf (first a) (+ (first a) 5))
(print (first a))))
Hopefully these examples should help you get a better idea of what let does. What happens when you put the third example into the repl is actually implementation dependant and has little to do with let and much more to do with quote.

Syntax error in Lisp code with loop and conditions

What is the syntax error in following code?
(defun getchoice3 ()
(let ( (choice 1) )
(format t "~%Enter a number (1-5): ")
(loop for choice = (or (parse-integer (prompt-read "Choice: ") :junk-allowed t) 0) do
while (and (> choice 0) (< choice 6))
(cond
((= choice 1) (print "1 chosen"))
((= choice 2) (print "2 chosen"))
((= choice 3) (print "3 chosen"))
((= choice 4) (print "4 chosen"))
((= choice 5) (print "5 chosen"))
(t (print "invalid entry, exiting."))))
choice))
The error being reported is very general:
*** - LOOP: illegal syntax near
(COND ((= CHOICE 1) (PRINT "1 chosen")) ((= CHOICE 2) (PRINT "2 chosen")) ((= CHOICE 3) (PRINT "3 chosen"))
((= CHOICE 4) (PRINT "4 chosen")) ((= CHOICE 5) (PRINT "5 chosen")) (T (PRINT "0 chosen, exiting.")))
in
(LOOP FOR CHOICE = (OR (PARSE-INTEGER (PROMPT-READ "Choice: ") :JUNK-ALLOWED T) 0) WHILE (AND (> CHOICE 0) (< CHOICE 6))
(COND ((= CHOICE 1) (PRINT "1 chosen")) ((= CHOICE 2) (PRINT "2 chosen")) ((= CHOICE 3) (PRINT "3 chosen"))
((= CHOICE 4) (PRINT "4 chosen")) ((= CHOICE 5) (PRINT "5 chosen")) (T (PRINT "0 chosen, exiting."))))
Though 'do' is there in code, it is not being reported in Error message.
The syntax error is gone. You should actually try your code.
I fail to understand why you are not indenting the code properly. Without proper indentation you will not be able to write any working code, and especially not working Lisp code.
Your code:
(defun getchoice3 ()
(let ( (choice 1) )
(format t "~%Enter a number (1-5): ")
(loop for choice = (or (parse-integer (prompt-read "Choice: ") :junk-allowed t) 0)
while (and (> choice 0) (< choice 6)) do
(cond
((= choice 1) (print "1 chosen"))
((= choice 2) (print "2 chosen"))
((= choice 3) (print "3 chosen"))
((= choice 4) (print "4 chosen"))
((= choice 5) (print "5 chosen"))
(t (print "invalid entry, exiting."))))
choice)) ; <- WHY THIS INDENTATION?
The code properly formatted looks like more like this (there is not a single one way to format it, but indentation is always the same way):
(defun getchoice3 ()
(let ((choice 1))
(format t "~%Enter a number (1-5): ")
(loop for choice = (or (parse-integer (prompt-read "Choice: ")
:junk-allowed t)
0)
while (and (> choice 0)
(< choice 6))
do (cond
((= choice 1) (print "1 chosen"))
((= choice 2) (print "2 chosen"))
((= choice 3) (print "3 chosen"))
((= choice 4) (print "4 chosen"))
((= choice 5) (print "5 chosen"))
(t (print "invalid entry, exiting."))))
choice))
You see the difference for example in the last line? My version is correctly indented.
Why is that important? It may help you to understand that your function will always return 1. Independent of any input, the function will return always 1. Indentation helps you to understand what belongs to what, given some scope.
This is not correctly indented.
(let ((a 1))
(loop for a from 1 to 10)
a) ; <- where does this a belong to???
; this indentation indicates that A belongs to the LOOP
; which it doesn't
The correct indentation is:
(let ((a 1))
(loop for a from 1 to 10)
a) ; here it's clear to see that A was introduced by the LET construct
So, don't indent the code such a way how you dream it makes sense.
Use an editor command to do it correctly. Then you can spot the problems in your code much better.
Lisp code can be formatted and indented in arbitrary ways, because it uses an indentation independent data structure: the s-expression.
Lisp does not care:
(+ a b c)
or
(+
a b
c)
or
(+
a
b
c)
It's all the same for Lisp.
But not for humans. Only one version of those above is useful for humans to read.
If you don't put any effort into indenting and formatting your code, why should anyone put in the effort to answer your questions, which are so far all caused by trivial syntax errors.

Why does this Lisp macro as a whole work, even though each piece doesn't work?

I'm reading/working through Practical Common Lisp. I'm on the chapter about building a test framework in Lisp.
I have the function "test-+" implemented as below, and it works:
(defun test-+ ()
(check
(= (+ 1 2) 3)
(= (+ 5 6) 11)
(= (+ -1 -6) -7)))
Remember, I said, it works, which is why what follows is so baffling....
Here is some code that "test-+" refers to:
(defmacro check (&body forms)
`(combine-results
,#(loop for f in forms collect `(report-result ,f ',f))))
(defmacro combine-results (&body forms)
(with-gensyms (result)
`(let ((,result t))
,#(loop for f in forms collect `(unless ,f (setf ,result nil)))
,result)))
(defmacro with-gensyms ((&rest names) &body body)
`(let ,(loop for n in names collect `(,n (gensym)))
,#body))
(defun report-result (value form)
(format t "~:[FAIL~;pass~] ... ~a~%" value form)
value)
Now, what I've been doing is using Slime to macro-expand these, step by step (using ctrl-c RET, which is mapped to macroexpand-1).
So, the "check" call of "test-+" expands to this:
(COMBINE-RESULTS
(REPORT-RESULT (= (+ 1 2) 3) '(= (+ 1 2) 3))
(REPORT-RESULT (= (+ 5 6) 11) '(= (+ 5 6) 11))
(REPORT-RESULT (= (+ -1 -6) -7) '(= (+ -1 -6) -7)))
And then that macro-expands to this:
(LET ((#:G2867 T))
(UNLESS (REPORT-RESULT (= (+ 1 2) 3) '(= (+ 1 2) 3)) (SETF #:G2867 NIL))
(UNLESS (REPORT-RESULT (= (+ 5 6) 11) '(= (+ 5 6) 11)) (SETF #:G2867 NIL))
(UNLESS (REPORT-RESULT (= (+ -1 -6) -7) '(= (+ -1 -6) -7))
(SETF #:G2867 NIL))
#:G2867)
And it is THAT code, directly above this sentence, which doesn't work. If I paste that into the REPL, I get the following error (I'm using Clozure Common Lisp):
Unbound variable: #:G2867 [Condition of type UNBOUND-VARIABLE]
Now, if I take that same code, replace the gensym with a variable name such as "x", it works just fine.
So, how can we explain the following surprises:
The "test-+" macro, which calls all of this, works fine.
The macro-expansion of the "combine-results" macro does not run.
If I remove the gensym from the macro-expansion of "combine-results", it
does work.
The only thing I can speculate is that you cannot use code the contains literal usages of gensyms. If so, why not, and how does one work around that? And if that is not the explanation, what is?
Thanks.
GENSYM creates uninterned symbols. When the macro runs normally, this isn't a problem, because the same uninterned symbol is being substituted throughout the expression.
But when you copy and paste the expression into the REPL, this doesn't happen. #: tells the reader to return an uninterned symbol. As a result, each occurrence of #:G2867 is a different symbol, and you get the unbound variable warning.
If you do (setq *print-circle* t) before doing the MACROEXPAND it will use #n= and #n# notation to link the identical symbols together.
The code, after being printed and read back, is no longer the same code. In particular, the two instances of #:G2867 in the printed representation would be read back as two separated symbols (albeit sharing the same name), while they should be the same in the original internal representation.
Try setting *PRINT-CIRCLE* to T to preserve the identity in the printed representation of the macro-expanded code.

Lisp match keywords and find values

Say I have a list with keywords:
'(("element1" :keyword1 "a" :keyword2 "b")
("element2" :keyword3 "c" :keyword4 "d")
("element3" :keyword2 "e" :keyword4 "f"))
Which functions can I use to find which list elements contain :keyword2 and find its value in each list? I'm trying to do this in Emacs Lisp but I think with the cl package I could possibly adapt a Common Lisp solution? I've tried to use the find function as illustrated here but to no avail (of course, after changing a few syntax elements to adapt the examples to Emacs Lisp).
(require 'cl)
(defvar *data* '(("element1" :keyword1 "a" :keyword2 "b")
("element2" :keyword3 "c" :keyword4 "d")
("element3" :keyword2 "e" :keyword4 "f")))
(find :keyword2 *data* :test #'find)
;;=> ("element1" :keyword1 "a" :keyword2 "b")
(getf (cdr (find :keyword2 *data* :test #'find)) :keyword2)
;;=> "b"
;; Above only finds the first match; to find all matches,
;; use REMOVE* to remove elements that do not contain the keyword:
(remove* :keyword2 *data* :test-not #'find)
;;=> (("element1" :keyword1 "a" :keyword2 "b")
;; ("element3" :keyword2 "e" :keyword4 "f"))
(mapcar (lambda (x) (getf (cdr x) :keyword2))
(remove* :keyword2 *data* :test-not #'find))
;;=> ("b" "e")
In Common Lisp, you would typically extract the values with destructuring-bind in this case, something like
(destructuring-bind (string &key keyword2 &allow-other-keys)
'("element1" :keyword1 "a" :keyword2 "b")
(list string keyword2)) ; or do anything with string and keyword2
should result in
("element1" "b")
In lisp, you would typically use a so-called associative list (or alist for short). It has the following form:
((key1 . value1) (key2 . value2) (key3 . value3))
There are a number of functions designed to work with alists, including assq and assoc, which return the dotted-pair, or nil.
Install dash list manipulation library (the GitHub link contains instructions). It contains multitude of helpful functions to achieve any goal. Suppose, your list above is named data, then you could:
(--find-indices (-elem-indices :keyword2 it) data) ; => (0 2)
(--map (cadr (--drop-while (not (eq :keyword2 it)) it)) data) ; => ("b" nil "e")
(--map-indexed (cons it-index ; => ((0 . "b") (1) (2 . "e"))
(cadr (--drop-while (not (eq :keyword2 it)) it))) data)