├── .gitignore ├── LICENSE ├── README.md ├── doc ├── exercises │ ├── 01_generating_code.md │ ├── 02_fancy_quoting.md │ ├── 03_symbol_capture.md │ ├── 04_danger_zone.md │ └── 99_bonus_round.md └── macro_workshop │ └── demo │ ├── 00_what_are_macros.clj │ ├── 01_generating_code.clj │ ├── 02_fancy_quoting.clj │ ├── 03_symbol_capture.clj │ └── 04_danger_zone.clj ├── project.clj ├── spec └── macro_workshop │ ├── code_generation_2_spec.clj │ ├── code_generation_spec.clj │ ├── fancy_quoting_spec.clj │ ├── if_nonempty_let_spec.clj │ ├── secret_magic_spec.clj │ └── user_friendliness_spec.clj └── src └── macro_workshop ├── aot_example.clj ├── code_generation.clj ├── code_generation_2.clj ├── exercise_macros.clj ├── fancy_quoting.clj └── secret_magic.clj /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | /classes 3 | /checkouts 4 | pom.xml 5 | pom.xml.asc 6 | *.jar 7 | *.class 8 | /.lein-* 9 | /.nrepl-port 10 | .hgignore 11 | .hg/ 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC 2 | LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM 3 | CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 4 | 5 | 1. DEFINITIONS 6 | 7 | "Contribution" means: 8 | 9 | a) in the case of the initial Contributor, the initial code and 10 | documentation distributed under this Agreement, and 11 | 12 | b) in the case of each subsequent Contributor: 13 | 14 | i) changes to the Program, and 15 | 16 | ii) additions to the Program; 17 | 18 | where such changes and/or additions to the Program originate from and are 19 | distributed by that particular Contributor. A Contribution 'originates' from 20 | a Contributor if it was added to the Program by such Contributor itself or 21 | anyone acting on such Contributor's behalf. Contributions do not include 22 | additions to the Program which: (i) are separate modules of software 23 | distributed in conjunction with the Program under their own license 24 | agreement, and (ii) are not derivative works of the Program. 25 | 26 | "Contributor" means any person or entity that distributes the Program. 27 | 28 | "Licensed Patents" mean patent claims licensable by a Contributor which are 29 | necessarily infringed by the use or sale of its Contribution alone or when 30 | combined with the Program. 31 | 32 | "Program" means the Contributions distributed in accordance with this 33 | Agreement. 34 | 35 | "Recipient" means anyone who receives the Program under this Agreement, 36 | including all Contributors. 37 | 38 | 2. GRANT OF RIGHTS 39 | 40 | a) Subject to the terms of this Agreement, each Contributor hereby grants 41 | Recipient a non-exclusive, worldwide, royalty-free copyright license to 42 | reproduce, prepare derivative works of, publicly display, publicly perform, 43 | distribute and sublicense the Contribution of such Contributor, if any, and 44 | such derivative works, in source code and object code form. 45 | 46 | b) Subject to the terms of this Agreement, each Contributor hereby grants 47 | Recipient a non-exclusive, worldwide, royalty-free patent license under 48 | Licensed Patents to make, use, sell, offer to sell, import and otherwise 49 | transfer the Contribution of such Contributor, if any, in source code and 50 | object code form. This patent license shall apply to the combination of the 51 | Contribution and the Program if, at the time the Contribution is added by the 52 | Contributor, such addition of the Contribution causes such combination to be 53 | covered by the Licensed Patents. The patent license shall not apply to any 54 | other combinations which include the Contribution. No hardware per se is 55 | licensed hereunder. 56 | 57 | c) Recipient understands that although each Contributor grants the licenses 58 | to its Contributions set forth herein, no assurances are provided by any 59 | Contributor that the Program does not infringe the patent or other 60 | intellectual property rights of any other entity. Each Contributor disclaims 61 | any liability to Recipient for claims brought by any other entity based on 62 | infringement of intellectual property rights or otherwise. As a condition to 63 | exercising the rights and licenses granted hereunder, each Recipient hereby 64 | assumes sole responsibility to secure any other intellectual property rights 65 | needed, if any. For example, if a third party patent license is required to 66 | allow Recipient to distribute the Program, it is Recipient's responsibility 67 | to acquire that license before distributing the Program. 68 | 69 | d) Each Contributor represents that to its knowledge it has sufficient 70 | copyright rights in its Contribution, if any, to grant the copyright license 71 | set forth in this Agreement. 72 | 73 | 3. REQUIREMENTS 74 | 75 | A Contributor may choose to distribute the Program in object code form under 76 | its own license agreement, provided that: 77 | 78 | a) it complies with the terms and conditions of this Agreement; and 79 | 80 | b) its license agreement: 81 | 82 | i) effectively disclaims on behalf of all Contributors all warranties and 83 | conditions, express and implied, including warranties or conditions of title 84 | and non-infringement, and implied warranties or conditions of merchantability 85 | and fitness for a particular purpose; 86 | 87 | ii) effectively excludes on behalf of all Contributors all liability for 88 | damages, including direct, indirect, special, incidental and consequential 89 | damages, such as lost profits; 90 | 91 | iii) states that any provisions which differ from this Agreement are offered 92 | by that Contributor alone and not by any other party; and 93 | 94 | iv) states that source code for the Program is available from such 95 | Contributor, and informs licensees how to obtain it in a reasonable manner on 96 | or through a medium customarily used for software exchange. 97 | 98 | When the Program is made available in source code form: 99 | 100 | a) it must be made available under this Agreement; and 101 | 102 | b) a copy of this Agreement must be included with each copy of the Program. 103 | 104 | Contributors may not remove or alter any copyright notices contained within 105 | the Program. 106 | 107 | Each Contributor must identify itself as the originator of its Contribution, 108 | if any, in a manner that reasonably allows subsequent Recipients to identify 109 | the originator of the Contribution. 110 | 111 | 4. COMMERCIAL DISTRIBUTION 112 | 113 | Commercial distributors of software may accept certain responsibilities with 114 | respect to end users, business partners and the like. While this license is 115 | intended to facilitate the commercial use of the Program, the Contributor who 116 | includes the Program in a commercial product offering should do so in a 117 | manner which does not create potential liability for other Contributors. 118 | Therefore, if a Contributor includes the Program in a commercial product 119 | offering, such Contributor ("Commercial Contributor") hereby agrees to defend 120 | and indemnify every other Contributor ("Indemnified Contributor") against any 121 | losses, damages and costs (collectively "Losses") arising from claims, 122 | lawsuits and other legal actions brought by a third party against the 123 | Indemnified Contributor to the extent caused by the acts or omissions of such 124 | Commercial Contributor in connection with its distribution of the Program in 125 | a commercial product offering. The obligations in this section do not apply 126 | to any claims or Losses relating to any actual or alleged intellectual 127 | property infringement. In order to qualify, an Indemnified Contributor must: 128 | a) promptly notify the Commercial Contributor in writing of such claim, and 129 | b) allow the Commercial Contributor tocontrol, and cooperate with the 130 | Commercial Contributor in, the defense and any related settlement 131 | negotiations. The Indemnified Contributor may participate in any such claim 132 | at its own expense. 133 | 134 | For example, a Contributor might include the Program in a commercial product 135 | offering, Product X. That Contributor is then a Commercial Contributor. If 136 | that Commercial Contributor then makes performance claims, or offers 137 | warranties related to Product X, those performance claims and warranties are 138 | such Commercial Contributor's responsibility alone. Under this section, the 139 | Commercial Contributor would have to defend claims against the other 140 | Contributors related to those performance claims and warranties, and if a 141 | court requires any other Contributor to pay any damages as a result, the 142 | Commercial Contributor must pay those damages. 143 | 144 | 5. NO WARRANTY 145 | 146 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON 147 | AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER 148 | EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR 149 | CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A 150 | PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the 151 | appropriateness of using and distributing the Program and assumes all risks 152 | associated with its exercise of rights under this Agreement , including but 153 | not limited to the risks and costs of program errors, compliance with 154 | applicable laws, damage to or loss of data, programs or equipment, and 155 | unavailability or interruption of operations. 156 | 157 | 6. DISCLAIMER OF LIABILITY 158 | 159 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY 160 | CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, 161 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION 162 | LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 163 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 164 | ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE 165 | EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY 166 | OF SUCH DAMAGES. 167 | 168 | 7. GENERAL 169 | 170 | If any provision of this Agreement is invalid or unenforceable under 171 | applicable law, it shall not affect the validity or enforceability of the 172 | remainder of the terms of this Agreement, and without further action by the 173 | parties hereto, such provision shall be reformed to the minimum extent 174 | necessary to make such provision valid and enforceable. 175 | 176 | If Recipient institutes patent litigation against any entity (including a 177 | cross-claim or counterclaim in a lawsuit) alleging that the Program itself 178 | (excluding combinations of the Program with other software or hardware) 179 | infringes such Recipient's patent(s), then such Recipient's rights granted 180 | under Section 2(b) shall terminate as of the date such litigation is filed. 181 | 182 | All Recipient's rights under this Agreement shall terminate if it fails to 183 | comply with any of the material terms or conditions of this Agreement and 184 | does not cure such failure in a reasonable period of time after becoming 185 | aware of such noncompliance. If all Recipient's rights under this Agreement 186 | terminate, Recipient agrees to cease use and distribution of the Program as 187 | soon as reasonably practicable. However, Recipient's obligations under this 188 | Agreement and any licenses granted by Recipient relating to the Program shall 189 | continue and survive. 190 | 191 | Everyone is permitted to copy and distribute copies of this Agreement, but in 192 | order to avoid inconsistency the Agreement is copyrighted and may only be 193 | modified in the following manner. The Agreement Steward reserves the right to 194 | publish new versions (including revisions) of this Agreement from time to 195 | time. No one other than the Agreement Steward has the right to modify this 196 | Agreement. The Eclipse Foundation is the initial Agreement Steward. The 197 | Eclipse Foundation may assign the responsibility to serve as the Agreement 198 | Steward to a suitable separate entity. Each new version of the Agreement will 199 | be given a distinguishing version number. The Program (including 200 | Contributions) may always be distributed subject to the version of the 201 | Agreement under which it was received. In addition, after a new version of 202 | the Agreement is published, Contributor may elect to distribute the Program 203 | (including its Contributions) under the new version. Except as expressly 204 | stated in Sections 2(a) and 2(b) above, Recipient receives no rights or 205 | licenses to the intellectual property of any Contributor under this 206 | Agreement, whether expressly, by implication, estoppel or otherwise. All 207 | rights in the Program not expressly granted under this Agreement are 208 | reserved. 209 | 210 | This Agreement is governed by the laws of the State of New York and the 211 | intellectual property laws of the United States of America. No party to this 212 | Agreement will bring a legal action under this Agreement more than one year 213 | after the cause of action arose. Each party waives its rights to a jury trial 214 | in any resulting litigation. 215 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # macro-workshop 2 | 3 | This workshop was originally presented at Clojure/West 2015, and was 4 | concurrently open-sourced to help out folks who weren't able to attend the 5 | workshop at a conference, but who still want to learn about macros. 6 | 7 | Please feel free to open an issue if you have any questions! It doesn't have to 8 | be a mistake in the materials, though I'd like to hear about any of those you 9 | find as well! 10 | 11 | Get in touch with me ([@trptcolin](https://github.com/trptcolin)) if you have 12 | any questions or would like me to facilitate this workshop at your conference 13 | or user group. 14 | 15 | 16 | ## Usage 17 | 18 | The general approach here is to help attendees really grok the fundamentals. 19 | Macros are mostly difficult when we lose track of what mode of interpretation 20 | (code vs. data) we're using at any given time. So the demos and exercises 21 | concentrate primarily on a rock-solid understanding on how to generate code, 22 | and how to avoid some problem areas that tend to trip up budding macro writers. 23 | 24 | 25 | ## Suggested self-study ordering 26 | 27 | These materials are designed such that you work in cycles, starting with the 28 | first entry in the demo material, under `doc/macro-workshop/demo` (ideally with 29 | an experienced macro Clojurist and macro writer available for questions), and 30 | proceed to complete the exercises as described in the file under 31 | `doc/exercises` corresponding to the session you're on. 32 | 33 | 34 | ## Resources 35 | 36 | - [Mastering Clojure Macros](https://pragprog.com/book/cjclojure/mastering-clojure-macros) 37 | - [On Lisp](http://www.paulgraham.com/onlisp.html) 38 | - [Let Over Lambda](http://letoverlambda.com/) 39 | 40 | ## License 41 | 42 | Copyright © 2015 Colin Jones 43 | 44 | Distributed under the Eclipse Public License either version 1.0 or (at your 45 | option) any later version. 46 | -------------------------------------------------------------------------------- /doc/exercises/01_generating_code.md: -------------------------------------------------------------------------------- 1 | # Generating code 2 | 3 | ## Generating expressions with plain old functions 4 | 5 | 1. In the REPL, write an expression that will *return* the expression `(println 6 | "hi")`. To be clear, the code that you *type* shouldn't print "hi" to 7 | STDOUT, but if you copy the return value and type *that* into the REPL too, 8 | that *should* print "hi" to STDOUT. 9 | 10 | (hint: what does `quote` do?) 11 | 12 | What is the type of the return value? 13 | 14 | 15 | 2. Congratulations! You've just generated some code! Now let's do something 16 | more fun. In `spec/macro_workshop/code_generation_spec.clj`, there are some 17 | failing tests. Start up the test auto-runner with: 18 | 19 | ```bash 20 | lein spec -a spec/macro_workshop/code_generation_spec.clj 21 | ``` 22 | 23 | 24 | Now make each test pass, by first removing the `(pending)` marker, and then 25 | fixing the code in `src/macro_workshop/code_generation.clj`. The auto-runner 26 | will re-run the tests every time you save one of the two relevant files. I 27 | like to keep one window open with the auto-runner, and one with my editor, 28 | so that I can see both at once. 29 | 30 | Whether things are passing or failing, feel very free to write additional 31 | tests and/or use the REPL to make sure you understand why! 32 | 33 | 34 | 3. Read the source code for `clojure.core/when`, and explain it [out loud!] to 35 | your pair or the person next to you: 36 | 37 | ```clojure 38 | (defmacro when 39 | "Evaluates test. If logical true, evaluates body in an implicit do." 40 | {:added "1.0"} 41 | [test & body] 42 | (list 'if test (cons 'do body))) 43 | ``` 44 | 45 | 46 | 4. Now macroexpand some input given to `when` at the REPL, and see if your 47 | explanation holds up: 48 | 49 | ```bash 50 | user=> (macroexpand '(when (= 1 2) (println "Hi!") (println "Bye!"))) 51 | ``` 52 | 53 | 5. There are some failing tests in 54 | `spec/macro_workshop/code_generation_2_spec.clj`. Make them pass! You 55 | already have the code you need for each example (from the previous exercise) 56 | - now you should be able to literally copy/paste that code! 57 | 58 | The rules here are: you have to use macros for every example in this 59 | exercise. You totally shouldn't use macros for these examples in real 60 | life... we'll see why soon enough. 61 | 62 | ```bash 63 | lein spec -a spec/macro_workshop/code_generation_2_spec.clj 64 | ``` 65 | -------------------------------------------------------------------------------- /doc/exercises/02_fancy_quoting.md: -------------------------------------------------------------------------------- 1 | # Fancy quoting 2 | 3 | Sure, I know the real name is syntax-quote. But isn't "fancy-quote" more fun? 4 | 5 | 1. Have another look at your solutions to the second code generation exercise, 6 | the one where you wrote macros. Do they look too verbose now? 7 | 8 | Using what you know about syntax quoting, update those examples to make the 9 | macro code look more like the code it's generating. 10 | 11 | 2. There are some failing tests in `spec/macro_workshop/fancy_quoting_spec.clj` 12 | — make the first `describe` block pass! Just implement `math-operations-set` 13 | for now. Use your new syntax-quoting knowledge to generate code in a concise 14 | way. 15 | 16 | ```bash 17 | lein spec -a spec/macro_workshop/fancy_quoting_spec.clj 18 | ``` 19 | 20 | Just the first `describe` block for now! (If you go too far that's OK too.) 21 | 22 | 3. Take a look at the source code for the `if-not` macro and convince yourself 23 | that you would have written it, were it not (a) already written, and (b) 24 | confusing for many programmers to read & maintain (see 25 | [https://signalvnoise.com/posts/2699-making-sense-with-rubys-unless]() for 26 | an example from the Ruby world). 27 | 28 | ```clojure 29 | (defmacro if-not 30 | "Evaluates test. If logical false, evaluates and returns then expr, 31 | otherwise else expr, if supplied, else nil." 32 | {:added "1.0"} 33 | ([test then] `(if-not ~test ~then nil)) 34 | ([test then else] 35 | `(if (not ~test) ~then ~else))) 36 | ``` 37 | 38 | 4. One interesting thing about `if-not` is that there are 2 arities: a 39 | 2-argument version and a 3-argument version. Implement a multiple-arity 40 | macro to pass the remainder of the tests in 41 | `src/macro_workshop/fancy_quoting_spec.clj`. 42 | 43 | Again, you don't need a macro here, but the practice is worthwhile. 44 | 45 | -------------------------------------------------------------------------------- /doc/exercises/03_symbol_capture.md: -------------------------------------------------------------------------------- 1 | # Symbol Capture 2 | 3 | 1. Look back at each of your solutions so far. Where is there potential for 4 | symbol capture? Do you ever emit symbols that are neither 5 | namespace-qualified nor gensyms? Take a close look and fix any problems you 6 | find. 7 | 8 | Feel free to write some additional regression tests that expose the 9 | problems, before you fix them. 10 | 11 | 2. Clojure itself has several macros defined using non-namespace symbols. 12 | Search for `defmacro` on 13 | https://github.com/clojure/clojure/blob/master/src/clj/clojure/core.clj (or 14 | with your editor/IDE if you can navigate to clojure.core's source code) to 15 | find some macros that use plain old quoted symbols. Are these problematic in 16 | terms of symbol capture? Why or why not? 17 | 18 | 3. You've already learned all the skills you need in order to build macros. Now 19 | let's put it together. There are some failing tests in 20 | `spec/macro_workshop/if_nonempty_let_spec.clj`. Make them pass. 21 | 22 | ```bash 23 | lein spec -a spec/macro_workshop/if_nonempty_let_spec.clj 24 | ``` 25 | 26 | 4. Now that you have the happy path working for the macro in #3, let's provide 27 | some nice error messages for our users. There are some failing tests for you 28 | in `macro-workshop.user-friendliness-spec`. 29 | 30 | Once you get these passing, be sure to try the `NOTE` at the top of that 31 | file, and answer the question there. 32 | 33 | 34 | -------------------------------------------------------------------------------- /doc/exercises/04_danger_zone.md: -------------------------------------------------------------------------------- 1 | # Danger Zone 2 | 3 | ## Multiple evaluation 4 | 5 | 1. Multiple evaluation isn't always a bug. In fact, in many cases it's the 6 | fundamental reason for a macro existing. Give a couple of example macros in 7 | clojure.core that may evaluate one or more of their input expressions 8 | multiple times. 9 | 10 | 2. Look back at your solutions to the exercises so far, and see whether you've 11 | got any multiple evaluation problems. Looking for an unquote in front of the 12 | same symbol in multiple places is usually a good clue. 13 | 14 | 3. Why is it that we "Can't take value of a macro"? Is it a Clojure compiler limitation? 15 | Or something more fundamental? 16 | 17 | 4. Isn't there some trick we can do to avoid using a macro to implement 18 | `log-row`? (assuming you still need to build on top of `log`) 19 | 20 | ```clojure 21 | (defmacro log [& args] 22 | `(println (str "[INFO] " (string/join " | " ~(vec args))))) 23 | 24 | (log-row ["column one" "column two" "column three"]) 25 | ``` 26 | 27 | How? (or, alternatively, why isn't it possible?) Hint: could the fact that 28 | we have `eval` help? 29 | 30 | 31 | -------------------------------------------------------------------------------- /doc/exercises/99_bonus_round.md: -------------------------------------------------------------------------------- 1 | # Bonus Round 2 | 3 | 1. In what situations is a quoted expression equal to that same 4 | textual expression without the quote? A first example: 5 | 6 | ```clojure 7 | (= (quote 123) 123) 8 | ``` 9 | 10 | What compound data structures can go in the `123` spot there, and which can't? 11 | 12 | 2. Why doesn't the `(println never-happens)` line below blow up with an "Unable 13 | to resolve symbol" exception? 14 | 15 | ```clojure 16 | (if false 17 | (def never-happens 1) 18 | (def always-happens 1)) 19 | 20 | (println always-happens) 21 | (println never-happens) 22 | ``` 23 | 24 | See [http://www.gfredericks.com/speaking/2015-02-25-vars.pdf]() for a deep 25 | dive into vars. 26 | 27 | 3. With an AOT-compiled namespace, when are macros expanded? During AOT 28 | compilation? When the namespace is loaded? Make an initial guess at the 29 | answer (out loud or typed/written out!); then open 30 | `src/macro_workshop/aot_example.clj` and run 31 | `lein run -m macro-workshop.aot-example` a couple of times, and make sure 32 | you understand why you see what you see. 33 | 34 | Try `lein run -m macro-workshop.aot-example --expand` as well! 35 | 36 | 4. What are the implications of the previous answer for AOT-compiled projects? 37 | That is, what characteristics should macros have in order to avoid bugs in 38 | AOT-compiled code? 39 | 40 | 5. It's normally useful to think of macros as being completely gone at runtime. 41 | Does this mean your running program can't access macros? (hint: no) Name 3 42 | ways it can access/use macros. No judgment here: be as evil as you like! 43 | 44 | 6. We know that we can write expressions in the verb position that evaluate to 45 | functions. What happens if we put a macro expression in the verb position 46 | and try to make it expand to a special form symbol? 47 | 48 | ```clojure 49 | (defmacro make-symbol [x] (symbol x)) 50 | ((make-symbol "if") false (println "the if check didn't work right")) 51 | ``` 52 | 53 | What if we made it expand to a macro symbol instead of a special form? 54 | 55 | 7. There are two rarely-used available bits of syntax in macros: `&form` and 56 | `&env`. Take a look at how they work: 57 | 58 | ```clojure 59 | (defmacro show-special-stuff [& args] 60 | (println "macroexpand-time, &env: " (pr-str &env)) 61 | (println "macroexpand-time, &form: " (pr-str &form))) 62 | 63 | (show-special-stuff 1 2 3) 64 | 65 | (let [x 1 y 2] 66 | (show-special-stuff 1 2 3)) 67 | ``` 68 | 69 | So `&form` gives us the full expression that is being expanded (useful for 70 | things like testing libraries that want to be able to show users where they 71 | went wrong). And `&env` gives access to local variable bindings. Using lists 72 | and quoting (no need to look into the compiler source code for this!), make 73 | the tests pass in `spec/macro_workshop/secret_magic_spec.clj`. 74 | 75 | 76 | ```bash 77 | lein spec -a spec/macro_workshop/secret_magic_spec.clj 78 | ``` 79 | -------------------------------------------------------------------------------- /doc/macro_workshop/demo/00_what_are_macros.clj: -------------------------------------------------------------------------------- 1 | (ns macro-workshop.demo.00-what-are-macros) 2 | 3 | (comment 4 | 5 | ;; what is a macro? 6 | 7 | 8 | ;; a macro is [just] a function that: 9 | ;; - runs at compile time 10 | ;; - takes code as input 11 | ;; - produces code as output 12 | ;; - and that code is evaluated at runtime 13 | 14 | 15 | ;; there are lots of reasons to use macros! a few examples: 16 | 17 | ;; - to evaluate code in a different context (bindings! guaranteed cleanup!) 18 | ;; - for performance (the fastest code at runtime is no code!) 19 | ;; - to eliminate nonessential code (easy-to-read APIs!) 20 | ;; - to build new control flow structures (loops! delimited continuations!) 21 | ;; - to add new language features (pattern matching! goroutines!) 22 | 23 | 24 | ;; BUT: this workshop is more tactical than strategic 25 | 26 | ;; so we'll dig deep into: 27 | ;; - how to write functions that generate code 28 | ;; - how to do ^^^ concisely and clearly 29 | ;; - tricky issues to watch out for & how to avoid them 30 | 31 | 32 | ;; interesting topics we *won't* have time to dig into: 33 | ;; - details of big macros like core.async / core.match 34 | ;; - all the use cases for macros 35 | 36 | 37 | ;; remember: a macro is a function that: 38 | ;; - runs at compile time 39 | ;; - takes code as input 40 | ;; - produces code as output 41 | ;; - and that code is evaluated at runtime 42 | 43 | 44 | ;; initial approximation of the difference between macros & 45 | ;; functions: evaluation order 46 | 47 | (when (= 1 2) 48 | (println "math doesn't work")) 49 | 50 | ;; let's try a simple version, skipping the possibility of varargs 51 | (defn when-fn [test then-expr] 52 | (if test then-expr)) 53 | 54 | (when-fn (= 1 2) 55 | (println "math doesn't work")) 56 | 57 | (defn when-fn' [test then-expr] 58 | (println "top of when-fn") 59 | (if test then-expr) 60 | (println "bottom of when-fn")) 61 | 62 | (when-fn' (= 1 2) 63 | (println "math doesn't work")) 64 | 65 | (when (= 1 2) 66 | (println "math doesn't work")) 67 | 68 | ;; (special forms have this power too!) 69 | (if (= 1 2) 70 | (println "math doesn't work") 71 | (println "math works")) 72 | 73 | 74 | ;; why do macros have this super-power? 75 | ;; why can they control evaluation order? 76 | ) 77 | -------------------------------------------------------------------------------- /doc/macro_workshop/demo/01_generating_code.clj: -------------------------------------------------------------------------------- 1 | (ns macro-workshop.demo.01-generating-code) 2 | 3 | (comment 4 | 5 | ;; a macro is [just] a function that: 6 | ;; - runs at compile time 7 | ;; - takes code as input 8 | ;; - produces code as output 9 | ;; - and that code is evaluated at runtime 10 | 11 | ;; we can get almost *all* the macro skills we need without ever writing 12 | ;; `defmacro`. you'll see how trivial it is to go from code generation to 13 | ;; macros in one of the exercises here in a bit. 14 | 15 | 16 | ;; a bit of clojure code 17 | (+ 1 2 3) 18 | 19 | ;; another bit of clojure code 20 | ;; ... which, when eval'ed, produces clojure code 21 | (list '+ 1 2 3) 22 | 23 | (quote (+ 1 2 3)) 24 | 25 | ;; back to the code interpretation... 26 | (eval (quote (+ 1 2 3))) 27 | 28 | ;; a shorthand for `quote` 29 | '(+ a b c) 30 | 31 | 32 | ;; quoting applies to each element 33 | (assert (= (list '+ 'a 'b 'c) 34 | '(+ a b c))) 35 | 36 | ;; and it's recursive for nested elements too! 37 | '(+ 1 (+ x y) (* 3 4)) 38 | 39 | ;; undefined symbols 40 | (eval '(+ 1 (+ x y) (* 3 4))) ;; boom! 41 | 42 | ;; so let's define them 43 | (def x 1) 44 | (def y 2) 45 | 46 | ;; let's try this again... 47 | (eval '(+ 1 (+ x y) (* 3 4))) 48 | 49 | ;; [just in case we want to go back to the state where x & y are undefined] 50 | (ns-unmap *ns* 'x) 51 | (ns-unmap *ns* 'y) 52 | 53 | 54 | ;; how does this expression-building relate to macros? 55 | ;; macros produce code as output, and that code gets eval'ed. 56 | 57 | 58 | ;; macroexpand-1 says: just run the macro function - don't `eval` the result 59 | ;; it lets you see the output (code) from the macro function's execution 60 | (macroexpand-1 '(if-not false :oops)) 61 | (macroexpand-1 (macroexpand-1 '(if-not false :oops))) 62 | 63 | ;; just keep expanding until the "verb" isn't a macro 64 | (macroexpand '(if-not false :oops)) 65 | 66 | ;; running just the macroexpander can be useful to look at the generated code 67 | ;; vs. expanding and evaluating it all at once 68 | (if-not false :oops) 69 | (macroexpand '(if-not false :oops)) 70 | ) 71 | 72 | ;; on to the first exercise set: doc/exercises/01_generating_code.md 73 | ;; have fun! 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | (def / *) 96 | -------------------------------------------------------------------------------- /doc/macro_workshop/demo/02_fancy_quoting.clj: -------------------------------------------------------------------------------- 1 | (ns macro-workshop.demo.02-fancy-quoting 2 | (:require [clojure.string :as string])) 3 | 4 | (comment 5 | 6 | (def x 1) 7 | (def y 2) 8 | (+ x y) 9 | (eval '(+ x y)) 10 | 11 | ;; when we're generating code, the namespace where it gets generated is often 12 | ;; different from the place it gets used 13 | (do (in-ns 'macro-workshop.demo.01-generating-code) 14 | (eval '(+ x y))) ;; boom! 15 | 16 | (do (in-ns 'macro-workshop.demo.01-generating-code) 17 | (eval `(+ x y))) ;; how?! wha? 18 | 19 | ;; ^^^ what was the difference? ^^^ 20 | 21 | ;; syntax-quote gives you: 22 | ;; 1. namespace qualification 23 | '(+ a b) 24 | `(+ a b) 25 | 26 | `(* 12 4) 27 | 28 | `(string/join "," [1 2 3]) 29 | 30 | ;; you don't always get exceptions on code interpretation errors 31 | (do (in-ns 'macro-workshop.demo.01-generating-code) 32 | (eval `(/ 12 4))) 33 | 34 | (do (in-ns 'macro-workshop.demo.01-generating-code) 35 | (eval '(/ 12 4))) 36 | ;; ^^ why? ^^ 37 | 38 | ;; syntax-quote encourages interpretation of generated code using the context 39 | ;; where it was written 40 | 41 | 42 | ;; now, another usage for syntax-quote... 43 | ;; this is already kind of verbose 44 | (let [x 3] 45 | (list '+ 1 (list '* 2 x))) 46 | 47 | ;; syntax-quote gives you: 48 | ;; 2. unquoting 49 | (let [x 3] 50 | `(+ 1 (* 2 ~x))) 51 | 52 | ;; syntax-quote encourages code-generating code to look similar to the code 53 | ;; it's generating 54 | 55 | ;; we can add up several things, but this is getting unwieldy: 56 | (let [a 0 b 1 c 2 d 3 e 4 f 5 g 6 h 7 i 8 j 9] 57 | `(+ ~a ~b ~c ~d ~e ~f ~g ~h ~i ~j)) 58 | 59 | ;; let's use our sequence functions! 60 | (let [xs (range 10)] 61 | `(+ ~xs)) 62 | 63 | ;; would that work? 64 | (eval (let [xs (range 10)] 65 | `(+ ~xs))) 66 | ;; ^^^ why? ^^^ 67 | 68 | ;; would apply help? 69 | (eval (let [xs (range 10)] 70 | `(apply + ~xs))) 71 | ;; ^^^ why? ^^^ 72 | 73 | ;; notice that it's way easier to see what's going wrong when we look at the 74 | ;; code that's generated, not at the error messages from eval!!! 75 | ;; the same thing applies for debugging macros (so use `macroexpand-1`!) 76 | 77 | ;; OK, so there's another tiny bit of syntax here: 78 | (let [xs (range 10)] 79 | `(+ ~@xs)) 80 | 81 | ;; ...but does it work? 82 | (eval (let [xs (range 10)] 83 | `(+ ~@xs))) 84 | ;; ^^^ woohoo! ^^^ 85 | 86 | ;; hot tip: you don't actually *need* the syntax in this case: 87 | (let [xs (range 10) 88 | xs-vec (vec xs)] 89 | `(apply + ~xs-vec)) 90 | ;; ^^^ wrap it in an eval if you don't believe me ^^^ 91 | 92 | ;; but ~@ (unquote-splicing) sure is useful when you do need it! 93 | ;; we'll see why soon, but you can't always count on `apply` to save you. 94 | 95 | ;; so you want to generate some bindings... 96 | ;; the code we want to generate: 97 | (let [x 1] 98 | (+ x 2)) 99 | 100 | (eval `(let [x 1] 101 | (+ x 2))) 102 | 103 | ;; let's put together some pieces we already know 104 | (eval `(let [~'x 1] 105 | (+ ~'x 2))) 106 | 107 | ;; wait wtf is ~' ?? let's break it down: 108 | `(+ ~(quote x) 2) 109 | ;; ' <===> quote 110 | 111 | `(+ ~'x 2) 112 | 113 | `(let [~'x 1] 114 | (+ ~'x 2))) 115 | 116 | ;; so we have ~, which gives us an escape hatch to insert whatever code we want 117 | ;; inside a syntax-quote, and we have ', which lets us have non-namespaced 118 | ;; symbols. that gives us enough to generate bindings in a way that works well 119 | ;; enough for now. soon, we'll see other contexts where ~' can cause 120 | ;; problems. 121 | 122 | ;; now for the next exercise set! doc/exercises/02_fancy_quoting.md 123 | -------------------------------------------------------------------------------- /doc/macro_workshop/demo/03_symbol_capture.clj: -------------------------------------------------------------------------------- 1 | (ns macro-workshop.demo.03-symbol-capture) 2 | 3 | (comment 4 | 5 | ;; we've seen two ways to build functions 6 | (defmacro make-adder-verbose [x] 7 | (list 'fn '[y] (list '+ x 'y))) 8 | 9 | (defmacro make-adder [x] 10 | `(fn [~'y] (+ ~x ~'y))) 11 | 12 | ;; there's only a very slight difference we know about, which affects people 13 | ;; overriding clojure.core vars - and those people are being naughty anyway, 14 | ;; right? 15 | (macroexpand-1 '(make-adder-verbose 5)) 16 | (macroexpand-1 '(make-adder 5)) 17 | 18 | ;; this works as expected 19 | (assert (= 14 ((make-adder 4) 10))) 20 | 21 | ;; ok, let's say 4 comes from somewhere else; we'll just extract a local: 22 | (assert (= 14 23 | (let [y 4] 24 | ((make-adder y) 10)))) 25 | 26 | (let [y 4] 27 | ((make-adder y) 10)) 28 | 29 | (macroexpand-1 '(make-adder 4)) 30 | (macroexpand-1 '(make-adder y)) ;; symbol *captured*! 31 | 32 | 33 | ;; we could pick an obscure symbol name that probably won't be captured, like 34 | ;; first-argument-to-make-adder, but there's a better and surer way to fix 35 | ;; the problem: gensym! 36 | (gensym) 37 | (gensym "prefix-can-go-here_") 38 | 39 | (defmacro make-adder-with-gensym [x] 40 | (let [y (gensym)] 41 | `(fn [~y] (+ ~x ~y)))) 42 | 43 | (assert (= 14 44 | (let [y 4] 45 | ((make-adder-with-gensym y) 10)))) 46 | ;; why? find out: 47 | (macroexpand-1 '(make-adder-with-gensym y)) 48 | 49 | ;; we can make it more concise and avoid lots of bindings around expressions 50 | ;; that we're generating 51 | (defmacro make-adder-with-auto-gensym [x] 52 | `(fn [y#] (+ ~x y#))) 53 | 54 | (assert (= 14 55 | (let [y 4] 56 | ((make-adder-with-auto-gensym y) 10)))) 57 | (macroexpand-1 '(make-adder-with-auto-gensym y)) 58 | 59 | ;; syntax-quote gives you: 60 | ;; 3. auto-gensyms 61 | 62 | ;; the auto-gensym is guaranteed-unique, like normal gensym, and the same for 63 | ;; every instance in the syntax-quote 64 | `(x# x# x#) 65 | (apply = `(x# x# x#)) 66 | 67 | ;; but outside the syntax-quote, the auto-gensym is different 68 | (= (first `(x#)) 69 | (first `(x#))) 70 | 71 | ;; same for being lexically nested, but semantically separate syntax-quote 72 | ;; expressions (with an unquote intervening) 73 | (apply = `(x# ~(first `(x#)))) 74 | 75 | ;; if your macro has nested syntax-quotes (i really, really hope it doesn't 76 | ;; come to that), then you may be interested in 77 | ;; https://github.com/ztellman/potemkin#unify-gensyms as a way to, well, 78 | ;; unify your gensyms across multiple syntax-quote expressions. 79 | ) 80 | 81 | ;; time for the next exercise set! doc/exercises/03_symbol_capture.md 82 | 83 | -------------------------------------------------------------------------------- /doc/macro_workshop/demo/04_danger_zone.clj: -------------------------------------------------------------------------------- 1 | (ns macro-workshop.demo.04-danger-zone 2 | (:require [clojure.string :as string])) 3 | 4 | (comment 5 | ;; there are actually several problems with this tiny macro 6 | (defmacro square [x] 7 | `(* ~x ~x)) 8 | 9 | (let [x 3] 10 | (square x)) 11 | 12 | (square 5) 13 | 14 | (square (do (println "hi") 5)) ;; why? 15 | 16 | (macroexpand-1 '(square (do (println "hi") 5))) 17 | 18 | ;; we can fix this by only including the original code 1x in the output code 19 | (defmacro square [x] 20 | `(let [x# ~x] 21 | (* x# x#))) 22 | 23 | (square (do (println "hi") 5)) 24 | 25 | ;; say it with me: "the arguments to a macro are *code*" 26 | 27 | (map square (range 5)) ;; aw man! 28 | 29 | square 30 | 31 | #'square 32 | (type #'square) 33 | (deref #'square) 34 | @#'square 35 | 36 | ;; if you're like me, you may have tried something like... 37 | (map @#'square (range 5)) 38 | ;; ^^ wha? ^^ 39 | 40 | 41 | ;; ignore the {} for now - you'll get there in the BONUS ROUND!!! 42 | (#'square {} {} 5) 43 | (eval (#'square {} {} 5)) 44 | 45 | (map (partial #'square {} {}) (range 5)) 46 | 47 | (last (map (partial #'square {} {}) (range 5))) 48 | 49 | (map (comp eval (partial #'square {} {})) (range 5)) 50 | 51 | ;; but that was silly. we can do much much better: 52 | (map (fn [x] (square x)) (range 5)) 53 | ;; why did that work? 54 | 55 | (macroexpand-1 '(square x)) 56 | 57 | ;; SOOO: macros aren't values! they are *kind* of functions, but you'll find 58 | ;; when working with macros how important functions-as-values are to Clojure 59 | ;; in particular and FP in general 60 | 61 | 62 | ;; let's say we're working with a macro with variable arity: 63 | (defmacro log [& args] 64 | `(println (str "[INFO] " (string/join " | " ~(vec args))))) 65 | ;; ... and we also haven't yet discovered the joys of structured logging so 66 | ;; we don't have to do custom parsing for every server we deploy to. 67 | 68 | (log "column one") 69 | (log "column one" "column two") 70 | (log "column one" "column two" "column three") 71 | ;; cool. 72 | 73 | ;; so how do we define `log-row` to make this work? 74 | (log-row ["column one" "column two" "column three"]) 75 | 76 | (defn log-row [row] 77 | (apply log row)) ;; nooooope. 78 | 79 | (defmacro log-row [row] 80 | `(log ~@row)) ;; yep. 81 | 82 | (log-row ["column one" "column two" "column three"]) 83 | 84 | ;; so we had to write a macro in order to use the underlying macro. 85 | ;; this is common. 86 | 87 | 88 | ;; but WAIT: but does log-row *really* work?!? 89 | (let [x ["column one" "column two" "column three"]] 90 | (log-row x)) 91 | 92 | ) 93 | ;; exercise time! doc/exercises/04_danger_zone.md 94 | -------------------------------------------------------------------------------- /project.clj: -------------------------------------------------------------------------------- 1 | (defproject macro-workshop "0.1.0-SNAPSHOT" 2 | :description "A Clojure macros workshop" 3 | :url "http://clojuremacros.com" 4 | :license {:name "Eclipse Public License" 5 | :url "http://www.eclipse.org/legal/epl-v10.html"} 6 | :dependencies [[org.clojure/clojure "1.8.0"]] 7 | :plugins [[speclj "3.1.0"]] 8 | :profiles {:dev {:dependencies [[speclj "3.1.0"]]}} 9 | :source-paths ["doc" "src"] 10 | :test-paths ["spec"] 11 | :aot [macro-workshop.aot-example]) 12 | -------------------------------------------------------------------------------- /spec/macro_workshop/code_generation_2_spec.clj: -------------------------------------------------------------------------------- 1 | (ns macro-workshop.code-generation-2-spec 2 | (:require [macro-workshop.code-generation-2 :as code] 3 | [speclj.core :refer :all])) 4 | 5 | (describe "code-generation-2" 6 | (describe "generate-addition" 7 | (it "generates an addition expression" 8 | (pending) 9 | (should= 3 (code/add 1 2)))) 10 | 11 | (describe "generate-multiplication" 12 | (it "generates a multiplication expression" 13 | (pending) 14 | (should= 12 (code/multiply 3 4)))) 15 | 16 | (describe "generate-squarer" 17 | (it "generates a squaring function" 18 | (pending) 19 | (should= 81 ((code/make-squarer) 20 | 9)))) 21 | 22 | (describe "generate-hello-world-definition" 23 | (it "generates a Hello World function" 24 | (pending) 25 | (should= "Hello World!\n" 26 | (let [hello-world-fn (code/make-hello-world)] 27 | (with-out-str (hello-world-fn))))))) 28 | -------------------------------------------------------------------------------- /spec/macro_workshop/code_generation_spec.clj: -------------------------------------------------------------------------------- 1 | (ns macro-workshop.code-generation-spec 2 | (:require [macro-workshop.code-generation :as code] 3 | [speclj.core :refer :all])) 4 | 5 | (describe "code-generation" 6 | (describe "generate-addition" 7 | (it "generates an addition expression" 8 | (pending) 9 | (let [code (code/generate-addition 1 2)] 10 | (should (instance? clojure.lang.ISeq code)) 11 | (should= 3 (eval code))) 12 | (should= 7 (eval (code/generate-addition 3 4))))) 13 | 14 | (describe "generate-multiplication" 15 | (it "generates a multiplication expression" 16 | (pending) 17 | (let [code (code/generate-multiplication 3 4)] 18 | (should (instance? clojure.lang.ISeq code)) 19 | (should= 12 (eval code))) 20 | (should= 20 (eval (code/generate-multiplication 4 5))))) 21 | 22 | (describe "generate-squarer" 23 | (it "generates a squaring function" 24 | (pending) 25 | (let [code (code/generate-squarer)] 26 | (should (instance? clojure.lang.ISeq code)) 27 | (should= 81 ((eval code) 9))) 28 | (should= 100 ((eval (code/generate-squarer)) 10)))) 29 | 30 | (describe "generate-hello-world-definition" 31 | (it "generates a Hello World function" 32 | (pending) 33 | (let [code (code/generate-hello-world-definition)] 34 | (should (instance? clojure.lang.ISeq code)) 35 | (should= "Hello World!\n" 36 | (let [hello-world-fn (eval code)] 37 | (with-out-str (hello-world-fn)))))))) 38 | -------------------------------------------------------------------------------- /spec/macro_workshop/fancy_quoting_spec.clj: -------------------------------------------------------------------------------- 1 | (ns macro-workshop.fancy-quoting-spec 2 | (:require [clojure.repl :as repl] 3 | [clojure.string :as string] 4 | [macro-workshop.fancy-quoting :as fancy] 5 | [speclj.core :refer :all])) 6 | 7 | (describe "fancy-quoting" 8 | (describe "math-operations-set" 9 | (it "uses core-namespaced symbols" 10 | (pending) 11 | (should= #{'clojure.core/+ 12 | 'clojure.core/- 13 | 'clojure.core/* 14 | 'clojure.core//} 15 | (fancy/math-operations-set))) 16 | 17 | (it "uses syntax-quote exactly once" 18 | (pending) 19 | (should= ["`"] 20 | (re-seq #"`" 21 | (-> fancy/math-operations-set 22 | repl/source 23 | with-out-str))))) 24 | 25 | (describe "maybe-generator" 26 | (it "generates a None when given 0 arguments" 27 | (pending) 28 | (should= {:type `fancy/None} (fancy/construct-maybe))) 29 | 30 | (it "generates a Some when given 1 argument" 31 | (pending) 32 | (should= {:type `fancy/Some 33 | :value "hi"} 34 | (fancy/construct-maybe "hi"))))) 35 | -------------------------------------------------------------------------------- /spec/macro_workshop/if_nonempty_let_spec.clj: -------------------------------------------------------------------------------- 1 | (ns macro-workshop.if-nonempty-let-spec 2 | (:require [macro-workshop.exercise-macros :as macros] 3 | [speclj.core :refer :all])) 4 | 5 | (describe "if-nonempty-let" 6 | (it "takes the non-empty clause when the binding is non-empty" 7 | (pending) 8 | (should= [:non-empty [0 1 2 3 4]] 9 | (macros/if-nonempty-let [xs (range 5)] 10 | [:non-empty xs] 11 | :empty))) 12 | 13 | (it "takes the empty clause when the binding is empty" 14 | (pending) 15 | (should= :empty 16 | (macros/if-nonempty-let [xs []] 17 | [:non-empty xs] 18 | :empty))) 19 | 20 | (it "takes the non-empty clause for strings" 21 | (pending) 22 | (should= "ohai there" 23 | (macros/if-nonempty-let [x "ohai"] (str x " there") :else))) 24 | 25 | (it "takes the empty clause for an empty string" 26 | (pending) 27 | (should= :else 28 | (macros/if-nonempty-let [x ""] x :else)))) 29 | 30 | 31 | -------------------------------------------------------------------------------- /spec/macro_workshop/secret_magic_spec.clj: -------------------------------------------------------------------------------- 1 | (ns macro-workshop.secret-magic-spec 2 | (:require [macro-workshop.secret-magic :as secret] 3 | [speclj.core :refer :all])) 4 | 5 | (describe "macro-workshop.secret-magic" 6 | (describe "local-env" 7 | (it "emits the locals active where the macro is expanded" 8 | (pending) 9 | (let [x 1 10 | y 2] 11 | (should= 1 (get (secret/local-env) 'x)) 12 | (should= 2 (get (secret/local-env) 'y)))) 13 | 14 | (it "doesn't include locals" 15 | (pending) 16 | (let [result (secret/local-env)] 17 | (should= nil (get result 'x)) 18 | (should= nil (get result 'y)))))) 19 | -------------------------------------------------------------------------------- /spec/macro_workshop/user_friendliness_spec.clj: -------------------------------------------------------------------------------- 1 | (ns macro-workshop.user-friendliness-spec 2 | (:require [macro-workshop.exercise-macros :as macros] 3 | [speclj.core :refer :all])) 4 | 5 | ;; NOTE: Once you've made these tests pass, see what happens if we just call 6 | ;; the macro directly in the tests instead of quoting and using `eval`. 7 | ;; Why does that happen? 8 | 9 | (describe "user-friendliness of if-nonempty-let" 10 | (it "provides a nice error message for bindings that don't look like bindings" 11 | (pending) 12 | (try 13 | (eval `(macros/if-nonempty-let x# :then-branch :else-branch)) 14 | (should= true false) ;; shouldn't get here 15 | (catch AssertionError e 16 | (should (re-seq #"if-nonempty-let requires a name/value vector binding" 17 | (.getMessage e)))))) 18 | 19 | (it "provides a nice error message for bindings that " 20 | (pending) 21 | (try 22 | (eval `(macros/if-nonempty-let [x# 1] :then-branch :else-branch)) 23 | (should= true false) ;; shouldn't get here 24 | (catch AssertionError e 25 | (should (re-seq #"if-nonempty-let requires bound values to be seqable" 26 | (.getMessage e))))))) 27 | -------------------------------------------------------------------------------- /src/macro_workshop/aot_example.clj: -------------------------------------------------------------------------------- 1 | (ns macro-workshop.aot-example 2 | (:gen-class)) 3 | 4 | (defmacro foo [] 5 | (println "This prints during macroexpansion.") 6 | `(do (println "This prints at runtime.") 7 | (+ 1 2))) 8 | 9 | (defn -main [& args] 10 | (if (some #{"--expand"} args) 11 | (do (println "Running: (eval (macroexpand-1 '(foo)))") 12 | (eval (macroexpand-1 `(foo)))) 13 | (do (println "Running: (foo)") 14 | (foo)))) 15 | -------------------------------------------------------------------------------- /src/macro_workshop/code_generation.clj: -------------------------------------------------------------------------------- 1 | (ns macro-workshop.code-generation) 2 | 3 | ;; remember: you can use normal clojure sequence functions to generate code! 4 | 5 | (defn generate-addition [a b] 6 | :replace-me) 7 | 8 | (defn generate-multiplication [a b] 9 | :replace-me) 10 | 11 | (defn generate-squarer [] 12 | :replace-me) 13 | 14 | (defn generate-hello-world-definition [] 15 | :replace-me) 16 | -------------------------------------------------------------------------------- /src/macro_workshop/code_generation_2.clj: -------------------------------------------------------------------------------- 1 | (ns macro-workshop.code-generation-2) 2 | 3 | ;; you ought to be able to reuse your macro-workshop.code-generation solutions 4 | ;; for this - it's almost a literal copy-paste of the function body to the 5 | ;; macro body! 6 | 7 | (defmacro add [x y] 8 | :replace-me) 9 | 10 | (defmacro multiply [x y] 11 | :replace-me) 12 | 13 | (defmacro make-squarer [] 14 | :replace-me) 15 | 16 | (defmacro make-hello-world [] 17 | :replace-me) 18 | -------------------------------------------------------------------------------- /src/macro_workshop/exercise_macros.clj: -------------------------------------------------------------------------------- 1 | (ns macro-workshop.exercise-macros) 2 | 3 | (defmacro if-nonempty-let 4 | "Like if-let, but treats any empty collection (as via `empty?`) as false" 5 | [bindings then else] 6 | :replace-me) 7 | 8 | -------------------------------------------------------------------------------- /src/macro_workshop/fancy_quoting.clj: -------------------------------------------------------------------------------- 1 | (ns macro-workshop.fancy-quoting) 2 | 3 | (defn math-operations-set [] 4 | :replace-me) 5 | 6 | ;; Hint: you actually don't need to define any more vars to get this solved 7 | (defmacro construct-maybe 8 | ([] :replace-me) 9 | ([x] :replace-me)) 10 | 11 | -------------------------------------------------------------------------------- /src/macro_workshop/secret_magic.clj: -------------------------------------------------------------------------------- 1 | (ns macro-workshop.secret-magic) 2 | 3 | (defmacro local-env [] 4 | :replace-me) 5 | --------------------------------------------------------------------------------