recursioncommon-lisplocal-functions

flet and closures?


I defined a local recursive function using flet. The recursive function calculates factorials:

 (flet ((my-fac (n)
           (cond ((= n 1) 1)
                 (t (* n (my-fac (1- n)))))))

Then I used mapcar to apply my-fac to each element of a list to produce a list of factorials:

(defun my-mapping (lst x)
  (flet ((my-fac (n)
           (cond ((= n 1) 1)
                 (t (* n (my-fac (1- n)))))))
    (mapcar #'my-fac lst)))

Note the unused second parameter variable in the my-mapping definition. Here are the results:

CL-USER> (my-mapping '(1 2 3 4) 1)
(1 2 6 24)

Next, I changed the my-mapping function to use the second parameter variable:

(defun my-mapping (lst x)
  (flet ((my-fac (n)
           (cond ((= n 1) x) ; <==========CHANGE HERE
                 (t (* n (my-fac (1- n)))))))
    (mapcar #'my-fac lst)))

As a result of the change, I expected when I called (my-mapping '(1 2 3 4) 2), the results would be factorials that were multiplied by 2. Therefore, instead of getting the previous result:

  (1 2 6 24)

I expected to get:

  (2 4 12 48)

Here are my actual results

CL-USER> (my-mapping '(1 2 3 4) 2) 
(2 2 6 24)

It looks to me like my-fac picked up the value for x when calculating the factorial for 1 (the first element in the list), but for the rest of the elements in the list it looks like x had a value of 1 somehow.


Solution

  • There are two ways to define local functions: FLET and LABELS.

    See the CL Hyperspec for the specification of FLET and LABELS.

    It says about FLET:

    The scope of the name binding encompasses only the body. Within the body of flet, function-names matching those defined by flet refer to the locally defined functions rather than to the global function definitions of the same name.

    It says about LABELS:

    labels is equivalent to flet except that the scope of the defined function names for labels encompasses the function definitions themselves as well as the body.

    Means:

    (flet ((my-local-function ()
             ...))  ; <- my-local-function CAN'T be used here
    
      ; my-local-function can be used in the body:
      (my-local-function))
    
    (labels ((my-local-function ()
             ...))  ; <- my-local-function can be used here
    
      ; my-local-function can be used in the body :
      (my-local-function))
    

    LABELS is used for recursive functions.

    Here you are using FLET, which won't work.

    (defun my-mapping (lst x)
      (flet ((my-fac (n)
               (cond ((= n 1) x) 
                     (t (* n (my-fac (1- n)))))))  ; <- problem
        (mapcar #'my-fac lst)))                    ; <- no problem
    

    In above function the recursive call to MY-FAC is not possible. FLET does not allow self-recursive calls. You are calling a global function called MY-FAC. You would need to have such a function, to make above code work. I would suspect that this is the case, defined in prior experiments.

    To actually call the local recursive function replace FLET with LABELS:

    CL-USER 16 > (defun my-mapping (lst x)
                   (labels ((my-fac (n)
                              (cond ((= n 1) x)
                                    (t (* n (my-fac (1- n)))))))
                     (mapcar #'my-fac lst)))
    MY-MAPPING
    
    CL-USER 17 > (my-mapping '(1 2 3 4) 2)
    (2 4 12 48)
    

    Above shows that one gets the expected result.

    The problematic code and the Lisp compiler

    This is for example what the LispWorks compiler says:

    CL-USER 18 > (defun my-mapping (lst x)
                   (flet ((my-fac (n)
                            (cond ((= n 1) 1)
                                  (t (* n (my-fac (1- n)))))))
                     (mapcar #'my-fac lst)))
    MY-MAPPING
    
    CL-USER 19 > (compile *)
    ;;;*** Warning in MY-MAPPING: X is bound but not referenced
    
    The following function is undefined:
    MY-FAC which is referenced by MY-MAPPING
    

    SBCL says:

    ; in: DEFUN MY-MAPPING
    ;     (SB-INT:NAMED-LAMBDA MY-MAPPING
    ;         (LST X)
    ;       (BLOCK MY-MAPPING
    ;         (FLET ((MY-FAC #
    ;                  #))
    ;           (MAPCAR #'MY-FAC LST))))
    ; 
    ; caught STYLE-WARNING:
    ;   The variable X is defined but never used.
    ; in: DEFUN MY-MAPPING
    ;     (MY-FAC (1- N))
    ; 
    ; caught STYLE-WARNING:
    ;   undefined function: COMMON-LISP-USER::MY-FAC
    ; 
    ; compilation unit finished
    ;   Undefined function:
    ;     MY-FAC
    ;   caught 2 STYLE-WARNING conditions