I am a beginner in Lisp. I have two functions, a defparameter, and a defstruct. Every time a book is passed to inputBook I would like the title of the book (the string) to become the name of the defparameter. Is this possible?
I have tried to hard code a string in like below where it says "MobyDick" but I get an error. So is this even possible?
I tried to simply use the parameter passed title but if you try to pass another book into the function, they are both assigned to title but the last one passed will print, not the first and not both. So how can I do it so that I have as many "books" without a list or a hash table?
If the latter is not possible, how could I alter the code so that a defparameter would be created (unique) to any number of books and accessible through the getAuthor function? Does that make sense? (Please see the functions below.)
(defstruct book()
(title) ;;title of the book, type string
(author) ;;author of the book, type string
(date)) ;; date of the book, type string or int
(defun inputBook(title author month year)
(defparameter "MobyDick" ;;I want this to use the parameter title
(make-book :title title
:author author
:date '(month year))))
(defun getAuthor (book)
(write (book-author book)))
Many many thanks in advance! Also, I am a beginner beginner. I have been learning through googling and I am stumped here.
You probably want something which looks like this, rather than some madness of top-level variables.
(defvar *books* (make-hash-table))
(defun bookp (title)
(nth-value 1 (gethash title *books*)))
(defun remove-book (title)
(remhash title *books*))
(defun book (title)
(nth-value 0 (gethash title *books*)))
(defun (setf book) (new title)
(setf (gethash title *books*) new))
Then, for instance:
> (setf (book 'moby) (make-book ...))
> (book 'moby)
> (bookp 'moby)
> (remove-book 'moby)
Using symbols with arbitrary names has the typical drawback: you can overwrite the values of existing symbols. Thus it would be useful to have a separate package for it, which does not import symbols from other packages.
Better would be to have a hash table, which would map from a string to the book object.
Sketch of code with symbols:
(defstruct book
(title) ;; title of the book, type string
(author) ;; author of the book, type string
(date)) ;; date of the book, type list
(defun input-book (title author month year)
(setf (symbol-value (intern title))
(make-book :title title
:author author
:date (list month year))))
Example:
CL-USER 52 > (input-book "Lisp Style & Design"
"Molly M. Miller, Eric Benson"
1 1990)
#S(BOOK :TITLE "Lisp Style & Design"
:AUTHOR "Molly M. Miller, Eric Benson"
:DATE (1 1990))
CL-USER 53 > (book-author (symbol-value '|Lisp Style & Design|))
"Molly M. Miller, Eric Benson"
CL-USER 54 > (book-author |Lisp Style & Design|)
"Molly M. Miller, Eric Benson"
Related
In short: (gethash 'PARIS pandemic-hash-table) is returning nil, despite 'PARIS being a key in the table; this seems to be related to quoting/evaluation of symbols during the hash-table creation in some way but I can't figure it out.
I'm playing with a graph search (testing for the shortest route between cities in the board game Pandemic; just for fun - trying to find optimal research lab placement in a way more sophisticated than 'has the most edges'). I'm using a hash table to hold the route data (nodes and edges), and, needed to input the data as a preliminary:
(defvar *nodes* '('San-Francisco 'Chicago 'Atlanta 'Washington 'Montreal 'New-York 'Madrid 'Paris 'London 'Essen 'Milan 'St-Petersburg))
(defvar *edges* '(('Chicago 'St-Petersburg)
('San-Francisco 'Atlanta 'Montreal)
('Chicago 'Washington)
('Atlanta 'Montreal 'New-York)
('Chicago 'Washington 'New-York)
('Montreal 'Washington 'Madrid 'London)
('New-York 'London 'Paris)
('Madrid 'Essen 'London 'Milan)
('Madrid 'Essen 'London 'New-York)
('London 'Paris 'Milan 'St-Petersburg)
('Paris 'Essen)
('Essen 'Chicago)))
(defvar *pandemic-node-hash* (make-hash-table))
(loop for node in *nodes*
for edges in *edges*
do (setf (gethash node *pandemic-node-hash*) edges))
If I look at the resulting hash table:
CL-USER> (loop for key being the hash-keys of *pandemic-node-hash*
do (print key))
'SAN-FRANCISCO
... ;other keys removed for brevity
'PARIS
NIL
So it's making the table (and the edges show up similarly), but, (gethash 'PARIS *pandemic-node-hash*) returns nil. If I then add another 'PARIS node directly (setf (gethash 'paris *pandemic-node-hash*) 'somevalue), and check the keys, I get:
(loop for key being the hash-keys of *pandemic-node-hash*
do (print key))
'other keys
'PARIS
PARIS
NIL
So, the problem has something to do with the evaluation of the symbols ('PARIS and friends) in the initial hash table creation loop, but I can't quite figure out what's going on or how to do that correctly. I'm guessing node evaluates to the un-evaluated symbol, passing that to gethash ... but what's the right way? Surely not (eval node)? Backtick the list, with commas in front of the symbols? (ugh).
Remember: 'foo is the short form for (quote foo). It's a list with two elements: the symbol CL:QUOTE and the symbol FOO.
(defun show-it (arg)
(print (list arg (type-of arg)))
(values))
Above is a smaller helper function for this answer.
CL-USER 37 > (show-it 'hamburg)
(HAMBURG SYMBOL)
Above shows that the function sees the symbol hamburg and not the value of a variable hamburg.
Not
CL-USER 38 > (show-it '('hamburg))
(((QUOTE HAMBURG)) CONS)
Above: the function sees a nested list, with a list, which has quote as a symbol.
Note: ((quote hamburg)) can be written shorter as('hamburg).
CL-USER 39 > (show-it (first '('hamburg)))
((QUOTE HAMBURG) CONS)
Above: if we get the first element, we get the list with the quote symbol.
Better
CL-USER 40 > (show-it '(hamburg))
((HAMBURG) CONS)
Above provides a list with one symbol, the symbol hamburg.
CL-USER 41 > (show-it (first '(hamburg)))
(HAMBURG SYMBOL)
Above gets the first element, which is the symbol hamburg.
Evaluation
Make sure that you understand evaluation in Lisp. quote blocks evaluation for the whole quoted expression and on all levels of those. Thus it makes no sense to quote contents inside a quoted list.
When passing arguments in a function call, then quoting is used to prevent lists and symbols to be evaluated. Thus quote creates literal data: literal symbols, literal lists, literal vectors, etc.
Thus quoting is a mechanism of code, not of data.
Code:
(first '(hamburg)) vs. (first (hamburg))
getting the first element of a literal list vs.
getting the first element of the result
of calling the function `hamburg`.
Data:
(paris hamburg berlin rome) vs. ('paris 'hamburg 'berlin 'rome)
A list of city names vs. a list of city names,
each nested in a list (quote ...)
Thus:
('paris 'hamburg 'berlin 'rome) makes no sense.
Why not
(dolist (node *nodes*)
(dolist (edges *edges*)
(setf (gethash node *pandemic-node-hash*) edges)))
But also you've double quoted your symbols (as someone else commented)
Why:
'(('Chicago 'St-Petersburg) ...)
when it should probably be just this (don't quote the list and each symbol)
'((Chicago St-Petersburg) ...)
You would see this if you evaluated: *edges*
I need some help understanding completion-at-point.
I have this minimal example, where I want to:
activate when I type "#"
search/complete on candidates car ...
... but return cdr, so result at point is, for example "#doe" (though I may need to extend this later to drop the "#" in some cases, like with LaTeX).
The actual use case is to insert a citation key in a document, but search on author, title, etc. The intention is for this to be used with solutions like corfu and company-capf.
In that code, which is a front-end to bibtex-completion like helm-bibtex and ivy-bibtex, I have a core bibtex-actions-read function based on completing-read-multiple for minibuffer completion.
With this capf, I want to use the same cached data to complete against for at-point completion.
With this test example, I get 1 and 2, which is what I want on the UI end.
(defun test-capf ()
"My capf."
(when (looking-back "#[a-zA-Z]*")
(list
(save-excursion
(backward-word)
(point))
(point)
(lambda (str pred action)
(let ((candidates '(("a title doe" . "doe")
("different title jones" . "jones")
("nothing smith" . "smith"))))
(complete-with-action action candidates str pred))))))
But how do I adapt it to this to add 3? That is, if I type "#not", corfu or company should display "nothing smith", and if I select that item, it should return "#smith" at-point.
Note: my package pretty much depends on completion-styles like orderless, so order is of course not significant.
Do I need to use an :exit-function here?
For completeness, here's the current actual function, which now says "no matches" when I try to use it.
(defun bibtex-actions-complete-key-at-point ()
"Complete citation key at point.
When inserting '#' in a buffer the capf UI will present user with
a list of entries, from which they can narrow against a string
which includes title, author, etc., and then select one. This
function will then return the key 'key', resulting in '#key' at
point."
;; FIX current function only returns "no match"
;; TODO this regex needs to adapt for mode/citation syntax
(when (looking-back "#[a-zA-Z]+" 5)
(let* ((candidates (bibtex-actions--get-candidates))
(begin (save-excursion (backward-word) (point)))
(end (point)))
(list begin end candidates :exclusive 'no
;; I believe I need an exit-function so I can insert the key instead
;; of the candidate string.
:exit-function
(lambda (chosen status)
(when (eq status 'finished)
(cdr (assoc chosen candidates))))))))
Any other tips or suggestions?
This Q&A is related, but I can't figure out how to adapt it.
Why not just keep the completion candidates in your completion table, not conses?
There are some useful wrappers in minibuffer.el around completion tables. In this case you could use completion-table-dynamic, as a wrapper to use a function as the COLLECTION argument to complete-with-action.
I think the more efficient way would just collect the cdrs of your current candidates and allow the C implementations of all-completions to find matches
(complete-with-action action (mapcar #'cdr candidates) str pred)
Or, calling a function to return current candidates
(completion-table-dynamic
(lambda (_str)
(mapcar #'cdr (my-current-candidates))))
Or, filtering in elisp
(let ((candidates '((...)))
(beg '...)
(end '...))
;; ...
(list beg end
(completion-table-dynamic
(lambda (str)
(cl-loop for (a . b) in candidates
if (string-prefix-p str a)
collect b)))))
The solution was an exit-function, with body like this:
(delete-char (- (length str)))
(insert (cdr (assoc str candidates)))))
At the beginning, I create a class and accessor based on a macro as followed (this code his largely inspire from the book of Peter Seibel http://www.gigamonkeys.com/book/ )
(defun slot->defclass-slot (spec)
`(,spec :initarg ,(as-keyword spec) :accessor ,spec :initform 0))
(defmacro define-class (class-name class-slot)
`(defclass ,class-name ()
,(mapcar #'slot->defclass-slot class-slot)))
When I use the macro to generate the class, i have no fixed slots. The slots could be "name" and "id", or it could be "name", "id" and "description".
After, I had generate a class definition with the macro, i want to make several instance of that class. And again, I try to build a function as generic as possible, because the number of the slots in the class is variable.
For example, if I create a class with two slots "name" and "id". I might be interested to use this command:
(defun myfunction (slots)
`(make-instance 'myclass :name ,(first slots) :id ,(second slots)))
But, if my class has three slots "name","id" and "description", I might be interested to use this command:
(defun myfunction (slots)
`(make-instance 'myclass :name ,(first slots) :id ,(second slots) :description (third slots)))
Somehow, i succeed to define a function that creates a list with the command make-instance and the right number of slots. Here is how I proceed.
First I add in the macro definition a properties "slots" to the class that contains the list of slots.
(defmacro define-class (class-name class-slot id-slot)
`(progn
(eval-when (:compile-toplevel :load-toplevel :execute)
(setf (get ',class-name 'slots) ',class-slot))
(defclass ,class-name ()
,(mapcar #'slot->defclass-slot class-slot))
Then I could generate the command with the following text:
(defun make (slot-title slot-value)
`(,(as-keyword slot-title) ,slot-value))
(defun list->db (slots-instance)
`(make-instance 'model
,#(mapcan #'make (get 'model 'slots) slots-instance))))
(defmacro make-model (myslots)
(list->db myslots))
And when i execute list->db the following input ("a" "b"), i get the following result for example:
(make-instance 'myclass :name "a" :id "b")
But now, I wonder how i can make a function that take a list of list (e.g, (("k" "u") ("t" "j"))), and for each element of the list execute the command and create a class?
When you use backquote, you are creating a list, not executing it as an expression. It is often used to create macros. Macros transform code to other code. Code is represented as lists in Lisp.
What you have written is equivalent to:
(defun myfunction (slots)
(list 'make-instance ''myclass :name (first slots) :id (second slots)))
What you seem to intend is to just do it:
(defun myfunction (slots)
(make-instance 'myclass :name (first slots) :id (second slots)))
This mistake seems to stem from rote application of things you have seen elsewhere without understanding it at all. Don't do that. You might benefit a lot from reading a good book, e. g. Practical Common Lisp.
I was toying around with macros and clos, where I created an "object" macro to create instances
(defmacro object (class &rest args)
`(make-instance ',class ,#args))
Now doing this, I also ended up kind of wanting to do something similar for accessor functions created by clos. Example:
(defclass person () ((name :accessor person-name :initarg :name)))
then creating the instance
(setf p1 (object person :name "tom"))
now to get the name from the object obviously I would call person-name, however just as with the object macro, I wanted to create a "gets" macro to do this. So ideally:
(gets person name p1) which then would return the name.
The problem then is the binding of person and name (person-name) and how to do that. Is there anyway to get those two arguments bound together in the macro? sort of like:
(defmacro gets (class var object)
`(,class-,var ,object))
I think I may have misunderstood the original intent. At first I thought you were asking how to generate the accessor names for the class definition, which third part of the answer addresses. After reading through a second time, it actually sounds like you want to generate a new symbol and call it with some argument. That's easy enough too, and is given in the second part of this answer. Both the second and third parts depend on being able to create a symbol with a name that's built from the names of other symbols, and that's what we start with.
"Concatenating" symbols
Each symbol has a name (a string) that you can obtain with symbol-name. You can use concatenate to create a new string from some old strings, and then use intern to get a symbol with the new name.
(intern (concatenate 'string
(symbol-name 'person)
"-"
(symbol-name 'name)))
;=> PERSON-NAME
Reconstructing an accessor name
(defmacro gets (class-name slot-name object)
(let ((accessor-name
(intern (concatenate 'string
(symbol-name class-name)
"-"
(symbol-name slot-name))
(symbol-package class-name))))
`(,accessor-name ,object)))
(macroexpand-1 '(gets person name some-person))
;=> (PERSON-NAME SOME-PERSON)
For a number of reasons, though, this isn't very robust. (i) You don't know whether or not the slot has an accessor of the form <class-name>-<slot-name>. (ii) Even if the slot does have an accessor of the form <class-name>-<slot-name>, you don't know what package it's in. In the code above, I made the reasonable assumption that it's the same as the package of the class name, but that's not at all required. You could have, for instance:
(defclass a:person ()
((b:name :accessor c:person-name)))
and then this approach wouldn't work at all. (iii) This doesn't work with inheritance very well. If you subclass person, say with north-american-person, then you can still call person-name with a north-american-person, but you can't call north-american-person-name with anything. (iv) This seems to be reïnventing slot-value. You can already access the value of a slot using the name of the slot alone with (slot-value object slot-name), and I don't see any reason that your gets macro shouldn't just expand to that. There you wouldn't have to worry about the particular name of the accessor (if it even has one), or the package of the class name, but just the actual name of the slot.
Generating accessor names
You just need to extract the names of the symbols and to generate a new symbol with the desired name.
If you want to automatically generate accessors with defstruct style names, you can do it like this:
(defmacro define-class (name direct-superclasses slots &rest options)
(flet ((%slot (slot)
(destructuring-bind (slot-name &rest options)
(if (listp slot) slot (list slot))
`(,slot-name ,#options :accessor ,(intern (concatenate 'string
(symbol-name name)
"-"
(symbol-name slot-name)))))))
`(defclass ,name ,direct-superclasses
,(mapcar #'%slot slots)
,#options)))
You can check that this produces the kind of code that you'd expect by looking at the macroexpansion:
(pprint (macroexpand-1 '(define-class person ()
((name :type string :initarg :name)
(age :type integer :initarg :age)
home))))
(DEFCLASS PERSON NIL
((NAME :TYPE STRING :INITARG :NAME :ACCESSOR PERSON-NAME)
(AGE :TYPE INTEGER :INITARG :AGE :ACCESSOR PERSON-AGE)
(HOME :ACCESSOR PERSON-HOME)))
And we can see that it works as expected:
(define-class person ()
((name :type string :initarg :name)
(age :type integer :initarg :age)
home))
(person-name (make-instance 'person :name "John"))
;=> "John"
Other comments on your code
(defmacro object (class &rest args)
`(make-instance ',class ,#args))
As Rainer pointed out this isn't very useful. For most cases, it's the same as
(defun object (class &rest args)
(apply 'make-instance class args))
except that you can (funcall #'object …) and (apply #'object …) with the function, but you can't with the macro.
Your gets macro isn't really any more useful than slot-value, which takes an object and the name of a slot. It doesn't require the name of the class, and it will work even if the class doesn't have a reader or accessor.
Don't (naïvely) create symbol names with format
I've been creating symbol names with concatenate and symbol-name. Sometimes you'll see people use format to construct the names, e.g., (format nil "~A-~A" 'person 'name), but that's prone to issues with capitalization settings that can be changed. For instance, in the following, we define a function foo-bar, and note that the format based approach fails, but the concatenate based approach works.
CL-USER> (defun foo-bar ()
(print 'hello))
FOO-BAR
CL-USER> (foo-bar)
HELLO
HELLO
CL-USER> (setf *print-case* :capitalize)
:Capitalize
CL-USER> (funcall (intern (concatenate 'string (symbol-name 'foo) "-" (symbol-name 'bar))))
Hello
Hello
CL-USER> (format nil "~a-~a" 'foo 'bar)
"Foo-Bar"
CL-USER> (intern (format nil "~a-~a" 'foo 'bar))
|Foo-Bar|
Nil
CL-USER> (funcall (intern (format nil "~a-~a" 'foo 'bar)))
; Evaluation aborted on #<Undefined-Function Foo-Bar {1002BF8AF1}>.
The issue here is that we're not preserving the case of the symbol names of the arguments. To preserve the case, we need to explicitly extract the symbol names, rather than letting the print functions map the symbol name to some other string. To illustrate the problem, consider:
CL-USER> (setf (readtable-case *readtable*) :preserve)
PRESERVE
;; The symbol-names of foo and bar are "foo" and "bar", but
;; you're upcasing them, so you end up with the name "FOO-BAR".
CL-USER> (FORMAT NIL "~{~A~^-~}" (MAPCAR 'STRING-UPCASE '(foo bar)))
"FOO-BAR"
;; If you just concatenate their symbol-names, though, you
;; end up with "foo-bar".
CL-USER> (CONCATENATE 'STRING (SYMBOL-NAME 'foo) "-" (SYMBOL-NAME 'bar))
"foo-bar"
;; You can map symbol-name instead of string-upcase, though, and
;; then you'll get the desired result, "foo-bar"
CL-USER> (FORMAT NIL "~{~A~^-~}" (MAPCAR 'SYMBOL-NAME '(foo bar)))
"foo-bar"
This function creates symbols from string designators:
(defun symb (&rest args)
(intern (format nil "~{~a~^-~}" (mapcar #'string args))))
The function uses format, yet passes Joshua's test:
CL-USER> (symb 'foo :bar "BAZ")
FOO-BAR-BAZ
NIL
CL-USER> (defun foo-bar ()
(print 'hello))
FOO-BAR
CL-USER> (foo-bar)
HELLO
HELLO
CL-USER> (setf *print-case* :capitalize)
:Capitalize
CL-USER> (funcall (symb 'foo 'bar))
Hello
Hello
If you want your gets to use accessor methods:
(defmacro gets (class var object)
`(,(intern (format nil "~a-~a" (symbol-name class) (symbol-name var))) ,object))
In general, what you're trying to accomplish is not really useful. make-instance is a well known symbol, easily greppable, part of the standard and optimized by some implementations when the class name is constant. So with your object macro, you're just saving a few characters and a single-quote. Usually, one hides make-instance in specific cases where you don't want to provide a direct way to initialize instances, or more likely, when you want to provide layers of initialization (e.g. phases of initialization, Lisp slots and foreign objects).
PS: I remember vaguely that someone prominent in the standardization of Common Lisp argued in favor of always wrapping/hiding make-instance in a function (e.g. make-<class-name>), but I can't find either a reference or the reasoning.
PPS: Here's a rather old discussion (2004) about it in comp.lang.lisp (and another one from 2002). The main reasons people cite in favor of constructor functions are:
Required arguments; achievable at runtime instead of at compile-time with :initform (error ...) in a slot that requires a provided initial value
Generally, hide implementation details: class instance, structure instance, cons, something else
2.1. Not wanting to export the actual class name
2.2. Being able to return an instance of some other class, usually a subclass
Convenient shorthand for a specific class
I striked always, because it seems proponents to constructor functions for CLOS objects don't necessarily want to hide the protocol that make-instance follows (allocate-instance, initialize-instance → shared-initialize) to implementers or extenders of the API or framework, although they might want to hide it to the consumers of the API or framework.
For something faster, you might want to access slots directly, but that doesn't use accessor methods, and hence doesn't support side-effects, e.g. :before and :after methods:
(defmacro gets (class var object)
(let ((object-var (gensym)))
`(let ((,object-var ,object))
(declare (optimize (speed 3) (safety 0) (debug 0))
(type ,class ,object-var))
(slot-value ,object-var ',var))))
This might be a direct slot access on some implementations.
Finally, you also have with-slots and with-accessors in the standard.
Try playing with something like this:
(let ((a 'a)
(dash '-)
(b 'b))
`(,a,dash,b))
The other possibilities is to use intern, or more user friendly, alexandria's symbolicate.
I'm beginning to work through Practical Common LISP and the first exercise is to write a simple database. I'm using GNU CLISP 2.48 (2009-07-28) on cygwin.
This code, which I've compared against the book several times, doesn't produce output the way the book says it should
(defun make-cd (title artist rating ripped)
(list :title title :artist artist :rating rating :ripped))
(defvar *db* nil)
(defun add-record (cd) (push cd *db*))
(add-record (make-cd "Roses" "Kathy Mattea" 7 t))
(add-record (make-cd "Fly" "Dixie Chicks" 8 t))
(add-record (make-cd "Home" "Dixie Chicks" 9 t))
(defun dump-db ()
(dolist (cd *db*)
(format t "~{~a:~10t~a~%~}~%" cd)))
(dump-db)
I get
TITLE: Home
ARTIST: Dixie Chicks
RATING: 9
RIPPED:
*** - There are not enough arguments left for this format directive.
Current point in control string:
"~{~a:~10t~a~%~}~%"
|
I don't understand format or LISP well enough to being to troubleshoot. The book says I should be getting a list of all the records in the database. What has gone wrong?
First, let's look at the return from (make-cd):
[12]> (make-cd "Home" "Dixie Chicks" 9 t)
(:TITLE "Home" :ARTIST "Dixie Chicks" :RATING 9 :RIPPED)
You aren't including a value for :ripped! Change (make-cd) to:
(defun make-cd (title artist rating ripped)
(list :title title :artist artist :rating rating :ripped ripped))
Note the ripped after :ripped.
If you use the compiler in CLISP, it tells you what is wrong:
[1]> (defun make-cd (title artist rating ripped)
(list :title title :artist artist :rating rating :ripped))
MAKE-CD
[2]> (compile 'make-cd)
WARNING: in MAKE-CD : variable RIPPED is not used.
Misspelled or missing IGNORE declaration?
MAKE-CD ;
1 ;
NIL
The variable RIPPED is not used.
The format directive ~{...~} is an iterative construct, and its corresponding argument is expected to be a list. Furthermore in this case, because of the two occurrences of ~a, each iteration will consume two items, so the total number of items in the list is expected to be even. Yet you provided it with an odd number of items.