0

I want to implement cond (using lisp macros in guile) with if, this my attempt:

(define-macro (cond . clauses)
  (if (pair? clauses)
      (let ((first (car clauses)) (rest (cdr clauses)))
         `(if ,(car first)
              (begin
                 ,@(cdr first))
              ,(if (equal? (caar rest) 'else)
                  ',(cadr rest)
                   `(cond ,rest))))))

but it don't work, when I call it with this code:

(cond ((= 1 0) (display "hello"))
      ((= 1 1) (display "world"))
      (else
        (display "foo")))

I got this error:

ERROR: In procedure car: Wrong type argument in position 1 (expecting pair): ()

why I got this error and how to fix it? I prefer solution with lisp macros.

jcubic
  • 61,973
  • 54
  • 229
  • 402
  • 1
    scheme and common-lisp are two different lisp languages and lisp is often understood as a synonym for CL which is the direct descendant of lisp1.5. guile is a r5rs scheme and has nothing to do with common-lisp. Are you after answers in other languages than guile? – Sylwester Jul 18 '16 at 19:21
  • @Sylwester I wanted to have answer in lisp macros ether CL or guile scheme since I can rewrite the macro in scheme when it's written in CL. I've mark the question common lisp because I wanted to have more answers. – jcubic Jul 19 '16 at 07:27

2 Answers2

5

Most Scheme programmers, including myself, do not like to use define-macro, because it's totally unhygienic. I have no idea why you prefer to use them. With this in mind (that I would not write any define-macro macros myself), I poked around Femtolisp (a Scheme-like implementation that also does not use hygienic macros) for its implementation of cond:

(define-macro (cond . clauses)
  (define (cond-clauses->if lst)
    (if (atom? lst)
        #f
        (let ((clause (car lst)))
          (if (or (eq? (car clause) 'else)
                  (eq? (car clause) #t))
              (if (null? (cdr clause))
                  (car clause)
                  (cons 'begin (cdr clause)))
              (if (null? (cdr clause))
                  ; test by itself
                  (list 'or
                        (car clause)
                        (cond-clauses->if (cdr lst)))
                  ; test => expression
                  (if (eq? (cadr clause) '=>)
                      (if (1arg-lambda? (caddr clause))
                          ; test => (lambda (x) ...)
                          (let ((var (caadr (caddr clause))))
                            `(let ((,var ,(car clause)))
                               (if ,var ,(cons 'begin (cddr (caddr clause)))
                                   ,(cond-clauses->if (cdr lst)))))
                          ; test => proc
                          (let ((b (gensym)))
                            `(let ((,b ,(car clause)))
                               (if ,b
                                   (,(caddr clause) ,b)
                                   ,(cond-clauses->if (cdr lst))))))
                      (list 'if
                            (car clause)
                            (cons 'begin (cdr clause))
                            (cond-clauses->if (cdr lst)))))))))
  (cond-clauses->if clauses))

Hope it works for you!


If what you prefer isn't old-style unhygienic macros, but simply a macro system that lets you play with the incoming form in the raw, many Scheme implementations provide an explicit renaming (ER) macro system, which allows you to manipulate the forms directly and still allow you to maintain hygiene by (as the name implies) explicitly renaming any identifiers that should be protected from shadowing by the macro call site. Here's Chibi Scheme's implementation of cond:

(define-syntax cond
  (er-macro-transformer
   (lambda (expr rename compare)
     (if (null? (cdr expr))
         (if #f #f)
         ((lambda (cl)
            (if (compare (rename 'else) (car cl))
                (if (pair? (cddr expr))
                    (error "non-final else in cond" expr)
                    (cons (rename 'begin) (cdr cl)))
                (if (if (null? (cdr cl)) #t (compare (rename '=>) (cadr cl)))
                    (list (list (rename 'lambda) (list (rename 'tmp))
                                (list (rename 'if) (rename 'tmp)
                                      (if (null? (cdr cl))
                                          (rename 'tmp)
                                          (list (car (cddr cl)) (rename 'tmp)))
                                      (cons (rename 'cond) (cddr expr))))
                          (car cl))
                    (list (rename 'if)
                          (car cl)
                          (cons (rename 'begin) (cdr cl))
                          (cons (rename 'cond) (cddr expr))))))
          (cadr expr))))))

Major Scheme implementations are generally split into two camps in terms of what they use for low-level macros: syntax-case and explicit renaming. Racket, Chez Scheme, Guile, etc. use syntax-case. CHICKEN, MIT Scheme, Chibi Scheme, etc. use explicit renaming. So you won't be able to use the explicit renaming version above in Guile, because it's in the syntax-case camp.

C. K. Young
  • 219,335
  • 46
  • 382
  • 435
2

This is a working Lisp version:

(defmacro mycond (&rest clauses)
  (if (consp clauses)
      (destructuring-bind ((pred . forms) . rest-clauses) clauses
        `(if ,pred
             (progn ,@forms)
           ,(if (and (consp rest-clauses)
                     (eq (caar rest-clauses) 't))
                `(progn ,@(cdar rest-clauses))
              `(mycond ,@rest-clauses))))
    nil))

You can see that there are four errors fixed:

  • comma not inside a backquote
  • the rest-clauses need to be spliced in
  • the default T clause needs to insert the code
  • don't overwrite the built-in COND

Expanding the example:

CL-USER 67 > (walker:walk-form '(mycond ((= 1 0) (write "hello"))
                                        ((= 1 1) (write "world"))
                                        (t       (write "foo"))))

(IF (= 1 0)
    (PROGN (WRITE "hello"))
  (IF (= 1 1)
      (PROGN (WRITE "world"))
    (PROGN (WRITE "foo"))))

A slightly simpler version:

(defmacro mycond (&rest clauses)
  (if (consp clauses)
      (destructuring-bind ((pred . forms) . rest-clauses) clauses
        (if (eq pred t)
            `(progn ,@forms)
          `(if ,pred
               (progn ,@forms)
             (mycond ,@rest-clauses))))
    nil))
Rainer Joswig
  • 136,269
  • 10
  • 221
  • 346