2

I wonder if it's possible to write a macro in Racket that would translate every form of shape (c(a|d)+r xs), where c(a|d)+r is a regular expression matching car, cdr, caar, cadr, ... etc, into the corresponding composition of first and rest.

For example, this macro should take (caadr '(1 2 3 4 5)) and transform that to (first (first (rest '(1 2 3 4 5)))).

Something like this in Shen (Mark Tarver's new programming language): https://groups.google.com/group/qilang/browse_thread/thread/131eda1cf60d9094?hl=en

Racket Noob
  • 1,056
  • 1
  • 8
  • 16

4 Answers4

14

It is very possible to do exactly that in Racket, and in a much shorter way than done above. There are two (not-really) tricks involved:

  1. Using Racket's #%top macro makes it possible to create such bindings-out-of-thin-air. This macro is getting used implicitly around any variable reference that is unbound ("top" because these things are references to toplevel variables).

  2. Macros become much simpler if you make them do the necessary minimum, and leave the rest to a function.

Here's the complete code with comments and tests (the actual code is tiny, ~10 lines).

#lang racket

;; we're going to define our own #%top, so make the real one available
(require (only-in racket [#%top real-top]))
;; in case you want to use this thing as a library for other code
(provide #%top)

;; non-trick#1: doing the real work in a function is almost trivial
(define (c...r path)
  (apply compose (map (λ(x) (case x [(#\a) car] [(#\d) cdr])) path)))

;; non-trick#2: define our own #%top, which expands to the above in
;; case of a `c[ad]*r', or to the real `#%top' otherwise.
(define-syntax (#%top stx)
  (syntax-case stx ()
    [(_ . id)
     (let ([m (regexp-match #rx"^c([ad]*)r$"
                            (symbol->string (syntax-e #'id)))])
       (if m
         #`(c...r '#,(string->list (cadr m)))
         #'(real-top . id)))]))

;; Tests, to see that it works:
(caadadr '(1 (2 (3 4)) 5 6))
(let ([f caadadr]) (f '(1 (2 (3 4)) 5 6))) ; works even as a value
(cr 'bleh)
(cadr '(1 2 3))    ; uses the actual `cadr' since it's bound,
;; (cadr '(1))     ; to see this, note this error message
;; (caddddr '(1))  ; versus the error in this case
(let ([cr list]) (cr 'bleh)) ; lexical scope is still respected
Eli Barzilay
  • 29,301
  • 3
  • 67
  • 110
  • Excellent! This solution is what I wanted, thanks! Racket is a truly beautiful and powerful language. – Racket Noob Feb 06 '12 at 15:06
  • @RacketNoob Most of the Racket devs recommend How to Design Programs. It's not a Racket manual, and it probably doesn't cover `#%top`, but it's still a useful book to read. – C. K. Young Feb 06 '12 at 16:50
  • @chris Yeah, it's useful. But, unfortunately, HtDP is the only one book about Racket, and only for beginners. – Racket Noob Feb 06 '12 at 19:50
2

You can certainly write something that takes in a quoted s-expression and outputs the translation as a quoted s-expression.

Start with simply translating well-formed lists like '(#\c #\a #\d #\r) into your first/rest s-expressions.

Now build the solution with symbol?, symbol->string, regexp-match #rx"^c(a|d)+r$", string->list, and map

Traverse the input. If it is a symbol, check the regexp (return as-is if it fails), convert to list, and use your starting translator. Recurse on the nested expressions.

EDIT: here's some badly written code that can translate source-to-source (assuming the purpose is to read the output)

;; translates a list of characters '(#\c #\a #\d #\r)
;; into first and rest equivalents
;; throw first of rst into call
(define (translate-list lst rst)
  (cond [(null? lst) (raise #f)]
        [(eq? #\c (first lst)) (translate-list (rest lst) rst)]
        [(eq? #\r (first lst)) (first rst)]
        [(eq? #\a (first lst)) (cons 'first (cons (translate-list (rest lst) rst) '()))]
        [(eq? #\d (first lst)) (cons 'rest (cons (translate-list (rest lst) rst) '()))]
        [else (raise #f)]))

;; translate the symbol to first/rest if it matches c(a|d)+r
;; pass through otherwise
(define (maybe-translate sym rst)
  (if (regexp-match #rx"^c(a|d)+r$" (symbol->string sym))
      (translate-list (string->list (symbol->string sym)) rst)
      (cons sym rst)))

;; recursively first-restify a quoted s-expression
(define (translate-expression exp)
  (cond [(null? exp) null]
        [(symbol? (first exp)) (maybe-translate (first exp) (translate-expression (rest exp)))]
        [(pair? (first exp)) (cons (translate-expression (first exp)) (translate-expression (rest exp)))]
        [else exp]))

'test-2
(define test-2 '(cadr (1 2 3)))
(maybe-translate (first test-2) (rest test-2))
(translate-expression test-2)
(translate-expression '(car (cdar (list (list 1 2) 3))))
(translate-expression '(translate-list '() '(a b c)))
(translate-expression '(() (1 2)))

As mentioned in the comments, I am curious why you'd want a macro. If the purpose is to translate source into something readable, don't you want to capture the output to replace the original?

ccoakley
  • 3,235
  • 15
  • 12
  • The OP asked for a macro; if it just a function that takes an S-expression and returns it, that still requires an eval to run. :-) – C. K. Young Feb 05 '12 at 20:19
  • If the purpose is to translate source to make it readable, then you don't need a macro. I figured the OP wanted something to translate code into code, not to eval it (despite asking for a macro). – ccoakley Feb 05 '12 at 21:04
  • Yes, that is a reasonable expectation, but see the OP's comments on my post. – C. K. Young Feb 05 '12 at 21:10
  • @ChrisJester-Young: Alas, you are correct. Well, it was a fun puzzle to spend some time on this morning (and it's something I've actually wanted to do before). – ccoakley Feb 05 '12 at 21:14
  • Likewise, it was good macro-writing practice for me, so yay for us both (even if the OP doesn't like any of it). :-D – C. K. Young Feb 05 '12 at 21:22
  • @chris: true, i don't like it because it does not solve the problem, as stated. I want that when someone write in the REPL, for example, (caddaaaaaddaaaar '(.... some really long list...)) that stuff works. I don't want that i must write (blub (caddaaaaaddaaaar '(.... some really long list...))), where 'blub' is (constant) name for your macro! – Racket Noob Feb 05 '12 at 21:38
  • @RacketNoob - It sounds to me like you want to override the reader, not just use a (single) macro. Unfortunately, I've not even looked into racket's reader (except just now to make sure that it could be done). – ccoakley Feb 06 '12 at 01:17
  • 1
    @chris Ok, I've tried to make your macro work the way the OP requested in https://github.com/dyoo/fallthrough-racket. So you can make Racket behave this way rather easily. But it suddenly makes it harder to understand code in this "fallthrough" language and know what is going on. The fallthrough macros end up pervading everything, which makes the register-fallthrough macro programmer-unfriendly. It's cute and all, but I'd never use it in production code: it's just too hostile to human beings. – dyoo Feb 06 '12 at 05:17
1

Here's my implementation (now fixed to use call-site's car and cdr, so you can redefine them and they will work correctly):

(define-syntax (biteme stx)
  (define (id->string id)
    (symbol->string (syntax->datum id)))
  (define (decomp id)
    (define match (regexp-match #rx"^c([ad])(.*)r$" (id->string id)))
    (define func (case (string-ref (cadr match) 0)
                  ((#\a) 'car)
                  ((#\d) 'cdr)))
    (datum->syntax id (list func (string->symbol (format "c~ar" (caddr match))))))
  (syntax-case stx ()
    ((_ (c*r x)) (regexp-match #rx"^c[ad]+r$" (id->string #'c*r))
     (with-syntax (((a d) (decomp #'c*r)))
       (syntax-case #'d (cr)
         (cr #'(a x))
         (_ #'(a (biteme (d x)))))))))

Examples:

(biteme (car '(1 2 3 4 5 6 7)))        ; => 1
(biteme (cadr '(1 2 3 4 5 6 7)))       ; => 2
(biteme (cddddr '(1 2 3 4 5 6 7)))     ; => (5 6 7)
(biteme (caddddddr '(1 2 3 4 5 6 7)))  ; => 7
(let ((car cdr)
      (cdr car))
  (biteme (cdaaaaar '(1 2 3 4 5 6 7)))) ; => 6
C. K. Young
  • 219,335
  • 46
  • 382
  • 435
  • Thanks Chris, but can you do that without biteme at operator position? – Racket Noob Feb 05 '12 at 20:35
  • 1
    You're asking, then, for some macro to activate when something unrecognized is in application position, right? One way to do this is to override #%app in Racket. It's such a pervasive change to apply to a language, though! See https://github.com/dyoo/infix-syntax-example for an example that overrides #%app for a dubious purpose. It should be straightforward to hook Chris Jester-Young's solution similarly. – dyoo Feb 05 '12 at 20:57
  • @RacketNoob Yes, it does work, if you update the macro (make sure it's the version with the `datum->syntax`), and remember to write in the `biteme` (my bad for forgetting the latter). :-) – C. K. Young Feb 05 '12 at 21:00
  • @chris: I'm still getting "expand: unbound identifier in module in: cdaaaaar" error on your last example, – Racket Noob Feb 05 '12 at 21:05
  • @RacketNoob Do not forget to write the `biteme`. (See the edited last example.) – C. K. Young Feb 05 '12 at 21:07
  • @RacketNoob dyoo is referring to the infix syntax as a dubious purpose to be overriding `#%app` for, however, I do believe that doing that for this macro is even more dubious. – C. K. Young Feb 05 '12 at 21:14
  • 1
    @RacketNoob: see https://github.com/dyoo/fallthrough-racket/blob/master/test-fallthrough-racket.rkt. – dyoo Feb 06 '12 at 05:11
  • @dyoo: Wow, I'm impressed. +1 Like you say, not really something to use in production code, but still, that's of quite some hack value. :-) – C. K. Young Feb 06 '12 at 06:12
1

Let Over Lambda is a book which uses Common Lisp but it has a chapter in which it defines a macro with-all-cxrs that does what you want.

Daimrod
  • 4,902
  • 23
  • 25
  • Alas, CL macros are very different from Scheme macros! – C. K. Young Feb 05 '12 at 21:03
  • 1
    @Daimrod: solution described in Let Over Lambda is not as elegant as solution in Shen, described at this link (because it require us to use with-all-cxrs form whenever we want to use any of cxr function): https://groups.google.com/group/qilang/browse_thread/thread/131eda1cf60d9094?hl=en – Racket Noob Feb 05 '12 at 21:19
  • @RacketNoob: Wooa, I've heard about Shen but I never thought it was so great. This kind of macros seems really amazing, I need to learn a lot more about Shen, thanks for the link. :) But as said in the link you gave, I don't think it's possible in Scheme or CL. Or maybe in CL with a reader macro... But that's not clean as in Shen. – Daimrod Feb 06 '12 at 07:05