Fork of https://github.com/google/lisp-koans so that I could go through them. THIS CONTAINS ANSWERS.

macros.lsp 5.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. ;; Copyright 2013 Google Inc.
  2. ;;
  3. ;; Licensed under the Apache License, Version 2.0 (the "License");
  4. ;; you may not use this file except in compliance with the License.
  5. ;; You may obtain a copy of the License at
  6. ;;
  7. ;; http://www.apache.org/licenses/LICENSE-2.0
  8. ;;
  9. ;; Unless required by applicable law or agreed to in writing, software
  10. ;; distributed under the License is distributed on an "AS IS" BASIS,
  11. ;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. ;; See the License for the specific language governing permissions and
  13. ;; limitations under the License.
  14. ;; A lisp macro is like a function which takes an input lisp form
  15. ;; and produces a new output lisp form. Calling the macro
  16. ;; first produces new form, and then evaluates it in the context
  17. ;; of the macro call. The first phase, the creation of the new
  18. ;; macro form, is called 'macro expansion'.
  19. (defmacro repeat-2 (f) (list 'progn f f))
  20. (define-test test-macro-expands
  21. "assert-expands checks the expanded macro form against expectation."
  22. (assert-expands
  23. '(progn (do-something arg1 arg2) (do-something arg1 arg2))
  24. (repeat-2 (do-something arg1 arg2)))
  25. (assert-expands
  26. ____
  27. (repeat-2 (setf x (+ 1 x)))))
  28. ;; ----
  29. (define-test test-backtick-form
  30. "backtick (`) form is much like single-quote (') form, except that subforms
  31. preceded by a comma (,) are evaluated, rather then left as literals"
  32. (let ((num 5)
  33. (word 'dolphin))
  34. (true-or-false? ___ (equal '(1 3 5) `(1 3 5)))
  35. (true-or-false? ___ (equal '(1 3 5) `(1 3 num)))
  36. (assert-equal ____ `(1 3 ,num))
  37. (assert-equal ____ `(word ,word ,word word))))
  38. (define-test test-at-form
  39. "The at form, (@) in the backtick context splices a list variables into
  40. the form."
  41. (let ((axis '(x y z)))
  42. (assert-equal '(x y z) axis)
  43. (assert-equal '(the axis are (x y z)) `(the axis are ,axis))
  44. (assert-equal '(the axis are x y z) `(the axis are ,@axis)))
  45. (let ((coordinates '((43.15 77.6) (42.36 71.06))))
  46. (assert-equal ____
  47. `(the coordinates are ,coordinates))
  48. (assert-equal ____
  49. `(the coordinates are ,@coordinates))))
  50. ;; ---- On Gensym: based on ideas from common lisp cookbook
  51. ;; sets sym1 and sym2 to val
  52. (defmacro double-setf-BAD (sym1 sym2 val)
  53. `(progn (setf ,sym1 ,val) (setf ,sym2 ,val)))
  54. (define-test test-no-gensym
  55. "macro expansions may introduce difficult to see
  56. interactions"
  57. (let ((x 0)
  58. (y 0))
  59. (double-setf-BAD x y 10)
  60. (assert-equal x 10)
  61. (assert-equal y 10))
  62. (let ((x 0)
  63. (y 0))
  64. (double-setf-BAD x y (+ x 100))
  65. (assert-equal x ____)
  66. (assert-equal y ____)))
  67. ;; sets sym1 and sym2 to val
  68. (defmacro double-setf-SAFER (sym1 sym2 val)
  69. (let ((new-fresh-symbol (gensym)))
  70. `(let ((,new-fresh-symbol ,val))
  71. (progn (setf ,sym1 ,new-fresh-symbol) (setf ,sym2 ,new-fresh-symbol)))))
  72. (define-test test-with-gensym
  73. "gensym creates a new symbol."
  74. (let ((x 0)
  75. (y 0))
  76. (double-setf-SAFER x y 10)
  77. (assert-equal x 10)
  78. (assert-equal y 10))
  79. (let ((x 0)
  80. (y 0))
  81. (double-setf-SAFER x y (+ x 100))
  82. (assert-equal x ____)
  83. (assert-equal y ____)))
  84. ;; ----
  85. (defvar *log* nil)
  86. (defmacro log-form (&body body)
  87. "records the body form to the list *log* and then evalues the body normally"
  88. `(let ((retval ,@body))
  89. (push ',@body *log*)
  90. retval))
  91. (define-test test-basic-log-form
  92. "illustrates how the basic log-form macro above works"
  93. (assert-equal 1978 (* 2 23 43))
  94. (assert-equal nil *log*)
  95. "log-form does not interfere with the usual return value"
  96. (assert-equal 1978 (log-form (* 2 23 43)))
  97. "log-form records the code which it has been passed"
  98. (assert-equal ___ (length *log*))
  99. (assert-equal ___ (first *log*))
  100. "macros evaluating to more macros is ok, if confusing"
  101. (assert-equal 35 (log-form (log-form (- 2013 1978))))
  102. (assert-equal 3 (length *log*))
  103. (assert-equal '(log-form (- 2013 1978)) (first *log*))
  104. (assert-equal '(- 2013 1978) (second *log*)))
  105. ;; Now you must write a more advanced log-form, that also records the value
  106. ;; returned by the form
  107. (defvar *log-with-value* nil)
  108. ;; you must write this macro
  109. (defmacro log-form-with-value (&body body)
  110. "records the body form, and the form's return value
  111. to the list *log-with-value* and then evalues the body normally"
  112. `(let ((logform nil)
  113. (retval ,@body))
  114. ;; YOUR MACRO COMPLETION CODE GOES HERE.
  115. retval))
  116. (define-test test-log-form-and-value
  117. "log should start out empty"
  118. (assert-equal nil *log-with-value*)
  119. "log-form-with-value does not interfere with the usual return value"
  120. (assert-equal 1978 (log-form-with-value (* 2 23 43)))
  121. "log-form records the code which it has been passed"
  122. (assert-equal 1 (length *log-with-value*))
  123. (assert-equal '(:form (* 2 23 43) :value 1978) (first *log-with-value*))
  124. "macros evaluating to more macros is ok, if confusing"
  125. (assert-equal 35 (log-form-with-value (log-form-with-value (- 2013 1978))))
  126. (assert-equal 3 (length *log-with-value*))
  127. (assert-equal '(:form (log-form-with-value (- 2013 1978)) :value 35) (first *log-with-value*))
  128. (assert-equal '(:form (- 2013 1978) :value 35) (second *log-with-value*)))