LISP Appropriate Way to Return Value From Function

The name of the pictureThe name of the pictureThe name of the pictureClash Royale CLAN TAG#URR8PPP



LISP Appropriate Way to Return Value From Function



So I am working my way through Paul Graham's Common Lisp and one questions asks to create a union function that maintains the order of the elements in the lists being unionized. To that end I have written the following function:


(defun new-union (listA listB)
(setq retset (list (car listA)))
(loop for el in (append (cdr listA) listB)
do (if (not(member el retset))
(push el (cdr (last retset)))))
(return-from new-union retset))



This returns the unique elements of each list while maintaining the order so if I create and run:


(setq listA '(a b c a))
(setq listB '(c d e))
(new-union listA listB)



The return is:


(A B C D E)



So the first things is I get a compiler warning: "undefined variable: RETSET" on this line: (setq retset (list (car listA))). The other thing is that the above method seems to be a more "object-oriented" way of doing things rather than the LISP way with the return-from statement.


"undefined variable: RETSET"


(setq retset (list (car listA)))


return-from



Is it possible to write this code in a more "lisp-appropriate" manner without the compiler error?



*Edit: using the answer from @Sylwester I have rewritten the function as follows and get no errors:


(defun new-union (listA listB)
(let ((retset (list (car listA))))
(loop for el in (append (cdr listA) listB)
do (if (not (member el retset))
(push el (cdr (last retset)))))
retset))





Your function tries to push items to the end of a linked list. That's not a good use of singly-linked lists.
– Rainer Joswig
Aug 13 at 6:01




2 Answers
2



setq is to update an existing binding and your variable retset is not created. How this is handled is not specified in the standard so you cannot depend on code that touches it. You make global variables with defparameter and defvar while you can make local variables with &aux in functions, let and loop can create variables with with. Thus:


setq


retset


defparameter


defvar


&aux


let


loop


with


(defun new-union (list-a list-b)
(let ((retset (list (car list-a))))
...
retset
))



Is the same as this using &aux:


(defun new-union (list-a list-b &aux (retset (list (car list-a))))
...
retset
)



And also the same as loop with clause:


(defun new-union (list-a list-b)
(loop :with retset := (list (car list-a))
...
:finally (return retset)))



About return values. In tail position the value evaluated is the returned value. eg.


(if (< 3 4)
8
10)



Here 8 is returned. That means (return from new-union retset) in your code, which is in tail position, could have been written just retset.


8


(return from new-union retset)


retset



Now if you have code which is not in tail position and you wish to return early you can do what you did in tail position and it will work.



The one I use (return retset) returns from the nearest unnamed (nil) block while return-from returns from a named block. loop has the keyword named which allows you to choose the name of the block it produces.


(return retset)


nil


return-from


loop


named



Asking a lisper to implement such a trivial function you will get lots of answers. With the specifications and tests you had I would have done:


(defun new-union (&rest lists &aux (hash (make-hash-table :test 'equal)))
(loop :for list :in lists
:nconc (loop :for element :in list
:if (gethash element hash t)
:collect element
:do (setf (gethash element hash) nil))))





While I found your final implementation too far removed from my solution, I did change the setq line to a let. You can see my fixed implementation in my edit.
– EliSquared
Aug 12 at 20:34



setq


let





Return returns from a block named nil, not from any block.
– Svante
Aug 12 at 21:01


Return


nil





@Svante updated. Still returns from the nearsest obe, bur so does return-from
– Sylwester
Aug 12 at 21:32


return-from





To be precise, (return …) does the same as (return-from nil …). Every block has a name (it is not optional). Loop uses a default name of nil.
– Svante
Aug 13 at 11:38


(return …)


(return-from nil …)


Loop


nil





Yep, that's a good way to surprise yourself.
– Svante
Aug 13 at 20:29



A slightly better list-based version:



Code


(defun new-union (&rest lists
&aux (retset (list (caar lists)))
(rretset retset))
(dolist (list lists retset)
(dolist (el list)
(unless (member el retset)
(setf (cdr rretset) (list el)
rretset (cdr rretset))))))






By clicking "Post Your Answer", you acknowledge that you have read our updated terms of service, privacy policy and cookie policy, and that your continued use of the website is subject to these policies.

Popular posts from this blog

Firebase Auth - with Email and Password - Check user already registered

Dynamically update html content plain JS

How to determine optimal route across keyboard