Search code examples
lambdamacroslispsbcldefmacro

SBCL Type Inference problem in a somewhat weird macro


I'm trying to create a shorthand for lambda using underbar (_), per:

(defmacro _ (&rest body)                                                                                                                                        
  `(lambda (&rest _) ,@(expand_s body)))                                                                                                                        
                                                                                                                                                                
(defun expand_s (s)                                                                                                                                             
  (cond ((null s) nil)                                                                                                                                          
        ((atom s)                                                                                                                                               
         (if (eq '_ s) '(nth 0 _)                                                                                                                               
             (let ((s_string (format nil "~a" s)))                                                                                                              
               (if (char-equal #\_ (aref s_string 0))                                                                                                           
                   `(nth ,(1- (parse-integer (subseq s_string 1))) _)                                                                                           
                   s))))                                                                                                                                        
        (t (cons (expand_s (car s)) (expand_s (cdr s))))))                                                                                                      
                                                                                                                                                                
(print (macroexpand '(_ (+ _1 _2))))                                                                                                                            
(print (mapcar (_ (+ (* _1 _2) (expt _2 _1))) '(1 2 3) '(10 20 30)))   

Ugly as it is, it works fine compiled in SBCL:

* (load "shlambda.fasl")                                                                                                                                        
                                                                                                                                                                
#'(LAMBDA (&REST _) (+ (NTH 0 _) (NTH 1 _)))                                                                                                                    
(20 440 27090)                                                                                                                                                                                                                                                                                                                

But the SBCL compiler really doesn't like it:

; compiling (PRINT (MAPCAR # ...))                                                                                                                              
; file: shlambda.lisp                                                                                                            
; in:                                                                                                                                                           
;      PRINT (MAPCAR (_ (+ (* |_1| |_2|) (EXPT |_2| |_1|))) '(1 2 3) '(10 20 30))                                                                               
;     (_ (+ (* |_1| |_2|) (EXPT |_2| |_1|)))                                                                                                                    
; --> FUNCTION + * NTH SB-C::%REST-REF AND IF                                                                                                                   
; ==>                                                                                                                                                           
;   NIL                                                                                                                                                         
;                                                                                                                                                               
; caught STYLE-WARNING:                                                                                                                                         
;   This is not a NUMBER:                                                                                                                                       
;    NIL                                                                                                                                                        
;   See also:                                                                                                                                                   
;     The SBCL Manual, Node "Handling of Types"                                                                                                                 
;                                                                                                                                                               
; caught STYLE-WARNING:                                                                                                                                         
;   This is not a NUMBER:                                                                                                                                       
;    NIL                                                                                                                                                        
;   See also:                                                                                                                                                   
;     The SBCL Manual, Node "Handling of Types"                                                                                                                 
                                                                                                                                                                
; --> FUNCTION + EXPT NTH SB-C::%REST-REF AND IF                                                                                                                
; ==>                                                                                                                                                           
;   NIL                                                                                                                                                         
;                                                                                                                                                               
; caught STYLE-WARNING:                                                                                                                                         
;   This is not a NUMBER:                                                                                                                                       
;    NIL                                                                                                                                                        
;   See also:                                                                                                                                                   
;     The SBCL Manual, Node "Handling of Types"                                                                                                                 
;                                                                                                                                                               
; caught STYLE-WARNING:                                                                                                                                         
;   This is not a NUMBER:                                                                                                                                       
;    NIL                                                                                                                                                        
;   See also:                                                                                                                                                   
;     The SBCL Manual, Node "Handling of Types"                                                                                                                 
;                                                                                                                                                               
; compilation unit finished                                                                                                                                     
;   caught 4 STYLE-WARNING conditions       

I guess type inference can't figure out the types of an &rest in a lambda (which, I admit, I'm amazed that it even accepts an &rest in a lambda!) But you can pretty much never figure out the types in an &rest, so ... ???

Thanks in advance for your guidance.


Solution

  • The following compiles entirely silently for me, in a cold SBCL 2.2.7:

    (defmacro _ (&rest body)                ;should be &body
      `(lambda (&rest _) ,@(expand_s body)))
    
    (eval-when (:load-toplevel :compile-toplevel :execute)
      ;; needed on voyage
      (defun expand_s (s)
        (cond ((null s) nil)
              ((atom s)
               (if (eq '_ s) '(nth 0 _)
                 (let ((s_string (format nil "~a" s)))
                   (if (char-equal #\_ (aref s_string 0))
                       `(nth ,(1- (parse-integer (subseq s_string 1))) _)
                     s))))
              (t (cons (expand_s (car s)) (expand_s (cdr s)))))))
    
    (print (macroexpand '(_ (+ _1 _2))))
    (print (mapcar (_ (+ (* _1 _2) (expt _2 _1))) '(1 2 3) '(10 20 30)))
    

    And I can't see why it should not. Barmar is right that the &rest in the macro should probably be &body but that's stylistic.

    My guess is that you might not be defining expand_s early enough (see my eval-when), but actually I have no idea.