Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can this racket code for an anaphoric -> or ->> macro be improved?

I want to create a Racket macro which includes the functionality of one of Clojure's threading macros, but in addition requires an anaphoric argument (for example it), permitting me to specifically indicate where each previous value should be inserted into the next function call.

I’ll start with the ->> macro (ported to scheme or racket) where each new function call implicitly inserts the value of the previous function as its last argument:

(define-syntax ->>  
  (syntax-rules ()  
    [(_ x) x]  
    [(_ x (y ...) rest ...)  
     (->> (y ... x) rest ...)]))  

(->> (range 10)  
     (map add1)  
     (apply +)  
     (printf "the sum is: ~a\n"))  

; "the sum is: 55"  

I now define a macro named >> using syntax-case: Of course, I'll only need this new macro when it is not necessarily the last argument (nor always the first argument when using Clojure's -> macro).

(define-syntax (>> stx)  
  (syntax-case stx ()  
    [(_ x) #'x]  
    [(_ x (y ...) rest ...)  
     (with-syntax ([it (datum->syntax stx 'it)])  
       #'(let ([it x])  
           (>> (y ...) rest ...)))]))  

(>> (range 10)  
    (map add1 it)  
    (take it 5)  ; 'it' is not the last argument  
    (apply + it))  

Unfortunately, when evaluated the macro returns 45 rather than 15.

DrRacket's macro stepper shows the expansion to be:

(let ([it (range 10)])  
  (let ([it (map add1 it)])  
    (let ([it (take it 5)]) (apply + it))))  

The stepper also indicates that the final it is bound to the value returned by (range 10). Given this information, the return value of 45 is explained. But why is this the case? Can anyone help me to correct this lexical scoping problem?

By the way, the macro written slightly differently with syntax-parameterize works fine:

(require racket/stxparam)  
(define-syntax-parameter it  
  (lambda (stx)  
    (raise-syntax-error #f "can only be used inside '>>'" stx)))  

(define-syntax >>  
  (syntax-rules ()  
    [(_ x) x]  
    [(_ x (y ...) rest ...)  
      (let ([val x])  
        (syntax-parameterize ([it (make-rename-transformer #'val)])  
          (>> (y ...) rest ...)))]))  
like image 495
macro_curious Avatar asked Feb 11 '23 08:02

macro_curious


1 Answers

Here is one way to do it. First define a version of >> that uses an explicit it (below it is called >>>). The definition of >> simply generates an identifier and hands it to >>>.

#lang racket
(define-syntax (>>> stx)  
  (syntax-case stx ()  
    [(_ it x) #'x]  
    [(_ it x (y ...) rest ...)
     #'(let ([it x])  
         (>>> it (y ...) rest ...))]))

(>>> it
    (range 10)  
    (map add1 it) 
    (take it 5)  
    (apply + it))

(define-syntax (>> stx)
  (with-syntax ([it (datum->syntax stx 'it)])
    (syntax-case stx ()
      [(_ . more)
       #'(>>> it . more)])))

(>> (range 10)  
    (map add1 it) 
    (take it 5)  
    (apply + it))

The output is:

15
15

Note: I prefer your syntax-parameterize version.

like image 166
soegaard Avatar answered Apr 30 '23 08:04

soegaard