Search code examples
lispcommon-lisp

Evaluate function inside lambda on Lisp


I have the following structure on LISP

(setf *books* '(
        (  
            (:tit 'Titulo1)
            (:aut (quote Autor1) )
            (:gen 'Genero1)
            (:score 99)
        ) 
        (  
            (:tit 'Titulo2)
            (:aut (quote Autor2) )
            (:gen 'Genero2)
            (:score 40)
        ) 
))

Now, I want to reset all scores to zero, using mapcar and lambda. I try

(
    defun reset(list)
        (mapcar 
            (lambda(libro) 
               '(
                    (:tit (assoc :puntuacion libro))
                    (:aut (assoc :aut libro) )
                    (:gen (assoc :gen libro))
                    (:score 0)
                )
            ) 
            list
        )
)

The point is that assoc is not being evaluated inside the lambda function. How can I access the initial object?


Solution

  • You are quoting your code, so the following:

    '((:tit (assoc :puntuacion libro))
      (:aut (assoc :aut libro) )
      (:gen (assoc :gen libro))
      (:score 0))
    

    ... is not evaluated. It is a literal list that contains various symbols and numbers, including assoc. There are two options here:

    1. Use the backquote/comma mechanism of Lisp: the backquote is like quote except that when the quoted data contains a comma the expression after the comma is evaluated. You can imagine a template where some parts are fixed and other variables:

      `((:tit ,(cdr (assoc :punctuacion libro))))
        ...)
      

      Here above, the whole tree is quoted, except for the part that follows the comma. Note that you need to use cdr on the result of assoc to obtain the value, because assoc returns the entire entry, in other words the (key . value) couple.

    2. Only shadow the part of the value that changes:

       (acons :score 0 libro)
      

      This is a purely functional approach where the :score of zero is appended in front of the association list. The resulting structure is for example:

       ((:score . 0)
        (:tit . 'Titulo1)
        (:aut . 'Autor1 )
        (:gen . 'Genero1)
        (:score . 99))
      

      The next time you ask for the :score with assoc, the first occurrence is obtained. If you are worried about having too many updates, you can also call remove:

       (acons :score 0 (remove :score libro :key #'car))
      

      Note that remove is purely functional too, a new list is built with some element removed (a destructive version would use delete but in your case that would be wrong since your code, by quoting a list, is using literal values which are not allowed to be modified by the standard). I would advise against using destructive operations at first.

      The above approach that call acons allows you to add keys to your data structure without worrying about breaking existing code elsewhere. For example, later you can add an :iban key without having to rewrite the code that updates :score.

    Remarks

    • Please use readable names, like :title or :author

    • Please format your code in a more conventional Lisp way

    • Be careful about how your data is organized:

        (:score 40)
      

      The above is an association list from :score to the singleton list (40). If you write instead (acons :score 40 nil) to build a proper association list, you will notice that this is printed as follows:

        ((:score . 40))
      

      This is a list having a single entry, which is:

        (:score . 40)
      

      The above is how a cons-cell whose cdr is not a list is printed. Generally speaking you want to call (cdr (assoc ...)) to obtain the value, but if you don't like having this dot in your data, then you have to take into account that your value, (cdr (assoc ...)), is a list: so if you have a convention that this value is always a list of a single element, then you have to call (car (cdr (assoc ...))) instead, also known as (cadr (assoc ...)).