Unit testing of destructive functions in lisp - unit-testing

The background here is that I have a non-destructive version of a function that I want to make destructive for performance reasons. However, writing unit tests gets challenging because lisp considers all quoted lists in the source code to be constants and changing those leads to undefined behaviour.
I'm using parachute as the unit testing framework
e.g.,
(define-test suite-1
(let ((start '((1 2) (3 4) (5 6)))
(end '((7 1 2) (3 4) (5 6))))
(is #'equal end (push 7 (car start))))
(let ((start '((1 2) (3 4) (5 6)))
(end '((8 1 2) (3 4) (5 6))))
(is #'equal end (push 8 (car start)))))
This kinda fails because we end up pushing 8 onto the constant list (1 2) which eventually causes (push 8 (car start)) to result in (8 7 1 2) instead of the expected (8 1 2)
This isn't a problem with testing non-destructive functions because they don't modify constants. This is also not a problem outside of unit tests because I know that the original structure will no longer be needed.
I could replace the above with this ugly thing :-
(let ((start (list (list 1 2) (list 3 4) (list 5 6))) ...
which then creates a proper non-constant list but it sure does make the code unreadable...
Any suggestions on how other people approach this?

Use COPY-TREE to make a deep copy of the quoted list structure.
(let ((start (copy-tree '((1 2) (3 4) (5 6)))))
...
)

Your example has several mistakes.
(define-test suite-1
(let ((start '((1 2) (3 4) (5 6)))
(end '((7 1 2) (3 4) (5 6))))
(is #'equal end (push 7 (car start))))
(let ((start '((1 2) (3 4) (5 6)))
(end '((8 1 2) (3 4) (5 6))))
(is #'equal end (push 8 (car start)))))
These are two independent let expressions. Meaning,
the start in the first expression - no matter what destructive function you apply on it - can't affect the start in the second expression.
Since inbetween, it got newly created using the same quoted list expression.
Second, (push 7 (car start)) doesn't return the entire start content, thus just it returns only the modified car of start.
Therefore, you can't compare it with end but you should compare it to (car end) for equality.
If you do that, your code should run through.
I tested it by:
(defun test ()
(let ((start '((1 2) (3 4) (5 6)))
(end '((7 1 2) (3 4) (5 6))))
(assert (equal (car end) (push 7 (car start)))))
(let ((start '((1 2) (3 4) (5 6)))
(end '((8 1 2) (3 4) (5 6))))
(assert (equal (car end) (push 8 (car start)))))
(print "successfully run through."))
;; and then run:
(test)
;;
;; "successfully run through."
'' "successfully run through."
So the asserts were fulfilled.
SBCL warns about applying destructive function on constant data. But the constant data applies only wihin the let expression you are using. But outside of it, in a new let expression, a '((1 2) (3 4) (5 6)) would produce the same list always.
I couldn't believe when I saw your example that - if you would use (car end) to test for equality, that it would give in the second expression anything else than T.
The start in the first let's scope has nothing to do with the start in the second let's scope
You can easily prove it by:
(eq '((1 2) (3 4)) '((1 2) (3 4)))
;; => NIL
So with every redefinition of a literal object, you create a new entity in a new place. Thus mutations of the first start can't affect the second start which is freshly defined in a new scope (let expression).
This is why I criticize your example - which would only make sense if the same literal refers to the same place.
So I was wrong
#adabsurdum in the comments gave even the link to a discussion about this behavior that some implementations even collapse literal objects into one - according to CLtL. Thank you for being patient and explaining this all to me!!

Related

How to remove outer collection from a collection of collections?

How can I transform '((1 2 3) (4 5 6)) into '(1 2 3) '(4 5 6)?
Concretely, if I have the following collection:
(def coll '((1 2 3) (4 5 6))
And I want to pass both '(1 2 3) and '(4 5 6) as arguments to a method, how can coll be transformed into two lists?
For example, in
(map list ?? coll)
what implementation of ?? would lead to
'((1 4) (2 5) (3 6))
Which is the result of (map list '(1 2 3) '(4 5 6)).
What I tried is:
(flatten coll) does not work since it leads to one list (1 2 3 4 5 6).
Unquote splicing as in ~#coll leads to
Attempting to call unbound fn: #'clojure.core/unquote-splicing.
And mapping with quote as in (map #(quote %) coll) leads to something like (p1__10109# p1__10109#).
The operation is often called transpose. In clojure, you can do it thus:
(apply map list coll) ;((1 4) (2 5) (3 6))

Common Lisp: Removing elements with the same cdr

(Assignment help, go easy on me) (I have to do this without using destructive functions (setf))
With common lisp, as part of some code I need to be able to:
Take a list of lists,
compare the cdr of 2 elements,
If equal disregard the first element,
If not equal then try and compare the first element with the next unchecked element in the list.
Some examples to clarify:
((1 1 1) (2 1 1) (3 1 1)) -> ((3 1 1))
((2 2 0) (4 1 1) (1 2 0) (3 0 1) (8 1 1)) -> ((1 2 0) (3 0 1) (8 1 1))
(defun simplify2 (vari)
;If last term: stop
(if (equal (cdr vari) nil) vari
;If cdr of first and second term are equal...
(if (equal (cdar vari) (cdr (cadr vari)))
;Ignore the first term and continue with the rest of the list
(simplify2 (cdr vari))
;Otherwise (this is the line which isn't working)
(cons (car vari) (simplify2 (cdr vari))))))
At the moment the code will only work properly when all 'like' terms are placed next to each other in the list.
Le Petit Prince's suggestion in the comments to use remove-duplicates is probably what you want here. remove-duplicates is non-destructive (cf. delete-duplicates which can be destructive), and it's specified to a return a new list in which all but the last instance of an element are omitted (emphasis added):
remove-duplicates
remove-duplicates returns a modified copy of sequence from which any
element that matches another element occurring in sequence has been
removed. … The elements of sequence are compared pairwise, and
if any two match, then the one occurring earlier in sequence is
discarded, unless from-end is true, in which case the one later in
sequence is discarded.
You'll need to specify the a key argument to indicate that what should actually be compared is the cdr of the elements, and a test argument to indicate that they should be compared with equal. Thus:
(remove-duplicates '((1 1 1) (2 1 1) (3 1 1))
:test 'equal
:key 'cdr)
;=> ((3 1 1))
(remove-duplicates '((2 2 0) (4 1 1) (1 2 0) (3 0 1) (8 1 1))
:test 'equal
:key 'cdr)
;=> ((1 2 0) (3 0 1) (8 1 1))

Expression inside a list in Racket

If I have a list like this (define lst '((,(car '(1 2)) (1 2)) (,(car '(3 4)) (3 4))) ) and I try to 'evaluate' the expression (i.e. take as result '((1 (1 2)) (3 (3 4)))) I obtain the same list that I have.
I know that if I use (quoasiquote ((,(car '(1 2)) (1 2)) (,(car '(3 4)) (3 4))) I obtain what I'm looking for, but the problem is in an execution with an iterative method, where (I think) I'm not capable of take only the values, without being a list. (i.e. take only the second part of the quoasiquote expression).
For example, if I use for/list and I do (list-ref lst 0), I obtain '(,(car '(1 2)) (1 2)), when I want (,(car '(1 2)) (1 2)) for use it in quoasiquote function.
How can I obtain the expression inside a list and evaluate it?
Thank you.
So when you quote data it's like single quotes in some popular Algol dialects like perl. eg print '$_'; actually prints $_ and not the value the variable $_ represents. If you would have used double quotes the variables in the string is expanded to it's value.
In Scheme we have (quote x) which is 'x. Nothing in it will ever going to become evaluated since it's quoted with '. If it were `x, which is the same as (quasiquote x) then Scheme looks for ,expression (same as (unquote expression) ) and ,#expression (same as (unquote-splicing expression) ). These quote forms are NOT procedures but macros. It transforms your static code to different static code.
So (begin (define test 10) `((1 2) (3 ,(+ test 4)))) ; ==> ((1 2) (3 14))
What Scheme really does is transform it into (list '(1 2) (list 3 (+ test 4))), while if it was `((3 ,(+ test 4)) (1 2)) it turns into (cons (list 3 (+ test 4)) '((1 2))) since the tail can be a constant but the head cannot if it needs to have a evaluated tail.
I get the impression from you question that you think you can store the text and expand them later like (let ((x '((1 2) (3 ,(+ test 4))))) `x) but that will not work since quasiquote is a macro and thus it will evaluate to x. and (let ((x '((1 2) (3 ,(+ test 4))))) `,x) would evaluate to ((1 2) (3 ,(+ test 4))).
I'm pretty confident that we have an XY-problem here because what you describe is pretty unusual. Also, the only solution I imagine here is using eval, which again shows that you're probably heading into the wrong direction.
But here's a little attempt at what I believe you want:
(define lst '((list (car '(1 2)) '(1 2))
(list (car '(3 4)) '(3 4))))
(define-namespace-anchor nsa)
(define ns (namespace-anchor->namespace nsa))
(for/list ((i lst))
(define e (eval i ns))
(printf "evaluating ~a to ~a\n" i e)
e)
which will print
evaluating (list (car '(1 2)) '(1 2)) to (1 (1 2))
evaluating (list (car '(3 4)) '(3 4)) to (3 (3 4))
and evaluate to
'((1 (1 2)) (3 (3 4)))

Strange behavior invoking destructive Common LISP function receiving as argument a list created with quote

I've been getting a strange behavior when invoking a destructive definition receiving as argument a local variable whose type is a list created with a quote.
Destructive function:
(defun insert-at-pos (pos list elem)
(if (= pos 0)
(cons elem list)
(let ((aux-list (nthcdr (1- pos) list)))
(setf (rest aux-list) (cons elem (rest aux-list)))
list)))
WRONG: Local variable is a list created with the special operator quote.
(defun test ()
(let ((l '(1 2 3)))
(print l)
(insert-at-pos 2 l 4)
(print l)))
> (test)
(1 2 3)
(1 2 4 3)
(1 2 4 3)
> (test)
(1 2 4 3)
(1 2 4 4 3)
(1 2 4 4 3)
> (test)
(1 2 4 4 3)
(1 2 4 4 4 3)
(1 2 4 4 4 3)
CORRECT: Local variable is a list created with function list.
(defun test2 ()
(let ((l (list 1 2 3)))
(print l)
(insert-at-pos 2 l 4)
(print l)))
or
(defun test2 ()
(let ((l '(1 2 3)))
(print l)
(setf l (cons (first l) (cons (second l) (cons 4 (nthcdr 2 l)))))
(print l)))
> (test2)
(1 2 3)
(1 2 4 3)
(1 2 4 3)
> (test2)
(1 2 3)
(1 2 4 3)
(1 2 4 3)
> (test2)
(1 2 3)
(1 2 4 3)
(1 2 4 3)
Does someone know the reason of this strange behaviour?
If you quote data in a function, then it is literal data. The effects of destructively modifying such literal data are undefined in the Common Lisp standard. In your example all function invocations share the same literal data and the implementation does not warn you that you are changing it. That's what most implementations do. But it would also possible to imagine an implementation which puts all code (and its literal data) into a read-only part of the memory.
You can get funky effects with this.
If you want to destructively modify a list without running into potential problems, then you need to create a fresh copy at runtime. For example by calling LIST or COPY-LIST. LIST will return a fresh consed list.
There are similar pitfalls. For example imagine a file with these definitions:
(defvar *foo* '(1 2 3 4 5 6 ... 10000))
(defvar *foo* '(0 1 2 3 4 5 6 ... 10000))
If you compile such a file with the file compiler, the compiler is allowed to create a compiled file, where the two variables share literal data - saving space. If you would change an element in either list, both might be changed.

lisp filter out results from list not matching predicate

I am trying to learn lisp, using emacs dialect and I have a question.
let us say list has some members, for which predicate evaluates to false. how do I create a new list without those members? something like { A in L: p(A) is true }. in python there is filter function, is there something equivalent in lisp? if not, how do I do it?
Thanks
These functions are in the CL package, you will need to (require 'cl) to use them:
(remove-if-not #'evenp '(1 2 3 4 5))
This will return a new list with all even numbers from the argument.
Also look up delete-if-not, which does the same, but modifies its argument list.
If you manipulate lists heavily in your code, please use dash.el modern functional programming library, instead of writing boilerplate code and reinventing the wheel. It has every function to work with lists, trees, function application and flow control you can ever imagine. To keep all elements that match a predicate and remove others you need -filter:
(-filter (lambda (x) (> x 2)) '(1 2 3 4 5)) ; (3 4 5)
Other functions of interest include -remove, -take-while, -drop-while:
(-remove (lambda (x) (> x 2)) '(1 2 3 4 5)) ; (1 2)
(-take-while (lambda (x) (< x 3)) '(1 2 3 2 1)) ; (1 2)
(-drop-while (lambda (x) (< x 3)) '(1 2 3 2 1)) ; (3 2 1)
What is great about dash.el is that it supports anaphoric macros. Anaphoric macros behave like functions, but they allow special syntax to make code more concise. Instead of providing an anonymous function as an argument, just write an s-expression and use it instead of a local variable, like x in the previous examples. Corresponding anaphoric macros start with 2 dashes instead of one:
(--filter (> it 2) '(1 2 3 4 5)) ; (3 4 5)
(--remove (> it 2) '(1 2 3 4 5)) ; (1 2)
(--take-while (< it 3) '(1 2 3 2 1)) ; (1 2)
(--drop-while (< it 3) '(1 2 3 2 1)) ; (3 2 1)
I was looking for the very same last night and came across the Elisp Cookbook on EmacsWiki. The section on Lists/Sequences contains filtering teqniques and show how this can be done with mapcar and delq. I had to mod the code to use it for my own purposes but here is the original:
;; Emacs Lisp doesn’t come with a ‘filter’ function to keep elements that satisfy
;; a conditional and excise the elements that do not satisfy it. One can use ‘mapcar’
;; to iterate over a list with a conditional, and then use ‘delq’ to remove the ‘nil’
;; values.
(defun my-filter (condp lst)
(delq nil
(mapcar (lambda (x) (and (funcall condp x) x)) lst)))
;; Therefore
(my-filter 'identity my-list)
;; is equivalent to
(delq nil my-list)
;; For example:
(let ((num-list '(1 'a 2 "nil" 3 nil 4)))
(my-filter 'numberp num-list)) ==> (1 2 3 4)
;; Actually the package cl-seq contains the functions remove-if and remove-if-not.
;; The latter can be used instead of my-filter.
Emacs now comes with the library seq.el, use seq-remove.
seq-remove (pred sequence)
"Return a list of all the elements for which (PRED element) is nil in SEQUENCE."
With common lisp, you can implement the function as follows:
(defun my-filter (f args)
(cond ((null args) nil)
((if (funcall f (car args))
(cons (car args) (my-filter f (cdr args)))
(my-filter f (cdr args))))))
(print
(my-filter #'evenp '(1 2 3 4 5)))
There are a ton of ways to filter or select stuff from a list using built-ins which are much faster than loops. The built-in remove-if can be used this way. For example, suppose I want to drop the elements 3 through 10 in list MyList. Execute the following code as an example:
(let ((MyList (number-sequence 0 9))
(Index -1)
)
(remove-if #'(lambda (Elt)
(setq Index (1+ Index))
(and (>= Index 3) (<= Index 5))
)
MyList
)
)
You will get '(0 1 2 6 7 8 9).
Suppose you want to keep only elements between 3 and 5. You basically flip the condition I wrote above in the predicate.
(let ((MyList (number-sequence 0 9))
(Index -1)
)
(remove-if #'(lambda (Elt)
(setq Index (1+ Index))
(or (< Index 3) (> Index 5))
)
MyList
)
)
You will get '(3 4 5)
You can use whatever you need for the predicate that you must supply to remove-if. The only limit is your imagination about what to use. You can use the sequence filtering functions, but you don't need them.
Alternatively, you could also use mapcar or mapcar* to loop over a list using some function that turns specific entries to nil and the use (remove-if nil ...) to drop nils.
It's surprising there's no builtin version of filter without cl or (or seq which is very new).
The implementation of filter mentioned here (which you see in the Elisp Cookbook and elsewhere) is incorrect. It uses nil as a marker for items to be removed, which means if you have nils in your list to start with, they're going to be removed even if they satisfy the predicate.
To correct this implementation, the nil markers need to be replaced with an uninterred symbol (ie. gensym).
(defun my-filter (pred list)
(let ((DELMARKER (make-symbol "DEL")))
(delq
DELMARKER
(mapcar (lambda (x) (if (funcall pred x) x DELMARKER))
list))))