unhygienic.scm 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. ;; The following macro was suggested by Chris Vine on the Guile Scheme user
  2. ;; mailing list.
  3. ;; Comments added by me.
  4. ;; Warning: Apparently the following transcript posted by Mark H. Weaver on the
  5. ;; Guile Scheme user mailing list shows, that this macro is not hygienic:
  6. ;; ==========
  7. ;; TRANSCRIPT
  8. ;; ==========
  9. #|
  10. --8<---------------cut here---------------start------------->8---
  11. scheme@(guile-user)> (define-syntax ->
  12. (lambda (x)
  13. (syntax-case x ()
  14. [(k exp0 . exps)
  15. (let* ([reversed (reverse (cons (syntax->datum #'exp0)
  16. (syntax->datum #'exps)))]
  17. [out (let loop ([first (car reversed)]
  18. [rest (cdr reversed)])
  19. (if (null? rest)
  20. first
  21. (let ([func (car first)]
  22. [args (cdr first)])
  23. (append `(,func ,@args)
  24. (list (loop (car rest) (cdr rest)))))))])
  25. (datum->syntax #'k out))])))
  26. scheme@(guile-user)> (define t 'global-t)
  27. scheme@(guile-user)> (define-syntax-rule (foo x)
  28. (-> x (format #t "[t=~A] ~A\n" t)))
  29. scheme@(guile-user)> (let ((t 'inner-t)) (foo t))
  30. [t=global-t] global-t
  31. $1 = #t
  32. scheme@(guile-user)>
  33. --8<---------------cut here---------------end--------------->8---
  34. I recommend reformulating the -> macro using 'syntax-rules' as follows:
  35. --8<---------------cut here---------------start------------->8---
  36. scheme@(guile-user)> (define-syntax ->
  37. (syntax-rules ()
  38. ((-> exp)
  39. exp)
  40. ((-> exp ... (op args ...))
  41. (op args ... (-> exp ...)))))
  42. scheme@(guile-user)> (let ((t 'inner-t)) (foo t))
  43. [t=global-t] inner-t
  44. $8 = #t
  45. scheme@(guile-user)>
  46. --8<---------------cut here---------------end--------------->8---
  47. |#
  48. (define-syntax ~>
  49. ;; When using `syntax-case`, we need to wrap it in a lambda.
  50. (lambda (x)
  51. ;; Look at the input `x` and define no literals.
  52. (syntax-case x ()
  53. ;; There is only one case, which differentiates between the thing going
  54. ;; through the pipe represented by `k`, the first expression `exp0`, and
  55. ;; remaining expressions as `exps`.
  56. [(k exp0 . exps)
  57. (let*
  58. ([reversed
  59. ;; We are reversing the given expressions, because the first one
  60. ;; given is the inner-most in the result of the macro. The
  61. ;; applications of expressions of the reversed list can be nested
  62. ;; one after another until none remains and everything is applied
  63. ;; to `k`.
  64. (reverse
  65. ;; Make a normal list out of the pattern matched parts, so that it
  66. ;; can be reversed using the reverse function.
  67. (cons (syntax->datum #'exp0)
  68. (syntax->datum #'exps)))]
  69. ;; The output of the macro is computed by nesting the operations
  70. ;; contained within the expressions from the reversed list of
  71. ;; expressions.
  72. [out
  73. ;; We use a named let as loop.
  74. (let loop
  75. ;; For an initial value we extract the first expression from
  76. ;; the reversed list of expressions. It will be the outermost
  77. ;; wrapping call in the result of the macro.
  78. ;; In further iterations the loop will one by one move inwards
  79. ;; towards the first applied expression in the result of the
  80. ;; macro.
  81. ([first (car reversed)]
  82. ;; The next iteration will work with the rest of the
  83. ;; expressions. Typical list eater behavior.
  84. [rest (cdr reversed)])
  85. ;; We check whether there are more expressions in the rest of the
  86. ;; expressions, to know when to not recur by calling `loop`
  87. ;; again.
  88. (if (null? rest)
  89. ;; In case there are no remaining expressions in the rest of
  90. ;; expressions to be applied to the macro input, we only need to
  91. ;; apply the current `first` expression.
  92. first
  93. ;; In case there are still expressions, which need to be
  94. ;; applied, we split up the procedure and the already given
  95. ;; arguments to the procedure, so that we can insert the `k`
  96. ;; into the arguments, changing what the procedure is applied
  97. ;; to.
  98. (let ([func (car first)]
  99. [args (cdr first)])
  100. ;; To the list that is the procedure and its arguments we
  101. ;; append whatever is produced by the recursive calls of
  102. ;; the `loop`, thereby wrapping the result of further
  103. ;; iterations with the procedure call.
  104. (append `(,func ,@args)
  105. ;; `append` requires a list, so we need to build a
  106. ;; list. Since this happens at read-time, the usage
  107. ;; of append will not affect the performance at
  108. ;; runtime.
  109. (list
  110. ;; Here we recur and use the rest of the list.
  111. (loop
  112. ;; The next iteration's first expression is the
  113. ;; first of the rest of expressions
  114. (car rest)
  115. ;; The tail of the rest of expressions will make
  116. ;; the next iteration's rest of expressions.
  117. (cdr rest)))))))])
  118. ;; Wrap `out`, within the lexical context corresponding to the
  119. ;; identifier `k`. I think this is needed to give out the same scope
  120. ;; and as the original input to the macro had. It will mean, that in
  121. ;; that scope the same things are visible and invisible and `out`
  122. ;; cannot access anything not accessible from where the macro was
  123. ;; called.
  124. (datum->syntax #'k out))])))