LISP Appropriate Way to Return Value From Function
Clash 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))
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.
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