# Discussion 13: Macros

# Discussion 13 Vitamin

To encourage everyone to watch or attend discussion orientation, we have created small discussion vitamins. Each vitamin you complete will give you an extra credit point towards the course. Please answer all of the questions in this form after discussion orientation and before tutorial. If you have tutorial right after discussion, please complete within 3 hours after.

# Macros

Previously, we've seen how we can create macro-like functions in Python using f-strings.
Now let's talk about real macros, in Scheme. So far we've been able to
define our own procedures in Scheme using the
`define`

special form. When we call these procedures, we have to follow
the rules for evaluating call expressions, which involve evaluating all the
operands.

In the scheme project, we saw that special form expressions do not follow the evaluation rules of call expressions. Instead, each special form has its own rules of evaluation, which may include not evaluating all the operands. Wouldn't it be cool if we could define our own special forms where we decide which operands are evaluated? Consider the following example where we attempt to write a function that evaluates a given expression twice:

```
scm> (define (twice f) (begin f f))
twice
scm> (twice (print 'woof))
woof
```

Since `twice`

is a regular procedure, a call to `twice`

will
follow the same rules of evaluation as regular call expressions; first we
evaluate the operator and then we evaluate the operands. That means that
`woof`

was printed when we evaluated the operand `(print 'woof)`

.
Inside the body of `twice`

, the name `f`

is bound to the value
`undefined`

, so the expression `(begin f f)`

does nothing at all!

We have a problem here: we need to prevent the given expression from
evaluating until we're inside the body of the procedure. This is where the
`define-macro`

special form, which has identical syntax to the regular
`define`

form, comes in:

```
scm> (define-macro (twice f) (list 'begin f f))
twice
```

`define-macro`

allows us to define what's known as a **macro**,
which is simply a way for us to combine unevaluated input expressions together
into another expression. When we call macros, the operands are not evaluated,
but rather are treated as Scheme data. This means that any operands that are
call expressions or special form expression are treated like lists.

If we call `(twice (print 'woof))`

, `f`

will actually be bound to
the list `(print 'woof)`

instead of the value `undefined`

.
Inside the body of `define-macro`

, we can insert these expressions into
a larger Scheme expression. In our case, we would want a `begin`

expression that looks like the following:

`(begin (print 'woof) (print 'woof))`

As Scheme data, this expression is really just a list containing three
elements: `begin`

and `(print 'woof)`

twice, which is exactly
what `(list 'begin f f)`

returns. Now, when we call `twice`

,
this list is evaluated as an expression and `(print 'woof)`

is evaluated
twice.

```
scm> (twice (print 'woof))
woof
woof
```

To recap, macros are called similarly to regular procedures, but the rules for evaluating them are different. We evaluated lambda procedures in the following way:

- Evaluate operator
- Evaluate operands
- Apply operator to operands, evaluating the body of the procedure

However, the rules for evaluating calls to macro procedures are:

- Evaluate operator
- Apply operator to unevaluated operands
- Evaluate the expression returned by the macro
**in the frame it was called in**.

## Questions

### Q1: Or Macro

Implement `or-macro`

, which takes in two expressions and `or`

's them together (applying short-circuiting rules). However, do this without using the `or`

special form. You may also assume the name `v1`

doesn't appear anywhere outside this macro.

The behavior of the `or`

macro procedure is specified by the following doctests:

```
scm> (or-macro (print 'bork) (/ 1 0))
bork
scm> (or-macro (= 1 0) (+ 1 2))
3
```

**Your Answer**Run in 61A Code

**Solution**

```
(define-macro (or-macro expr1 expr2)
`(let ((v1 ,expr1)) (if v1 v1 ,expr2)))
```

### Q2: Replicate

Write a macro that takes an expression `expr`

and a number `n`

and
repeats the expression `n`

times. For example, `(repeat-n expr 2)`

should behave the same as `(twice expr)`

from the introduction section of this worksheet.
It's possible to pass in a combination as the second argument (e.g. `(+ 1 2)`

) as long as
it evaluates to a number. Be sure that you evaluate this expression in your
macro so that you don't treat it as a list.

Complete the implementation for `repeat-n`

so that its behavior matches the doctests below.

```
scm> (repeat-n (print '(resistance is futile)) 3)
(resistance is futile)
(resistance is futile)
(resistance is futile)
scm> (repeat-n (print (+ 3 3)) (+ 1 1)) ; Pass a call expression in as n
6
6
```

You may find it useful to implement the `replicate`

procedure, which takes in a value `x`

and a number `n`

and returns a list with `x`

repeated `n`

times. We've provided some examples for how `replicate`

should function here:

```
scm> (replicate '(+ 1 2) 3)
((+ 1 2) (+ 1 2) (+ 1 2))
scm> (replicate (+ 1 2) 1)
(3)
scm> (replicate 'hi 0)
()
```

Hint: Feel free to check out Discussion 11 and copy over your implementation of`replicate`

!

Hint 2: Consider which method to build your list (`list`

,`cons`

, or`quasiquote`

) might help make your implementation simpler.

**Your Answer**Run in 61A Code

**Solution**(define (replicate x n)

```
(if (= n 0) nil
(cons x (replicate x (- n 1))))
```

)

(define-macro (repeat-n expr n)

` (cons 'begin (replicate expr (eval n)))`

)

### Q3: List Comprehensions

Recall that list comprehensions in Python allow us to create lists out of iterables:

`[<map-expression> for <name> in <iterable> if <conditional-expression>]`

Define a procedure to implement list comprehensions in Scheme that can create lists
out of lists. Specifically, we want a `list-of`

macro that can be called as
follows:

`(list-of <map-expression> 'for <name> 'in <list> 'if <conditional-expression>)`

The symbols for, in, and if must be quoted when calling list-of so that they will not be evaluated. The program will error if they have not been previously defined.

Calling `list-of`

will return a new list constructed by doing the following for
each element in `<list>`

:

- Bind
`<name>`

to the element. - If
`<conditional-expression>`

evaluates to a truth-y value, evaluate`<map-expression>`

and add it to the result list.

Here are some examples:

```
scm> (list-of '(* x x) 'for 'x 'in ''(3 4 5) 'if '(odd? x))
(map (lambda (x) (* x x)) (filter (lambda (x) (odd? x)) (quote (3 4 5))))
scm> (eval (list-of '(* x x) 'for 'x 'in ''(3 4 5) 'if '(odd? x)))
(9 25)
scm> (list-of ''hi 'for 'x 'in ''(1 2 3 4 5 6) 'if '(= (modulo x 3) 0))
(map (lambda (x) (quote hi)) (filter (lambda (x) (= (modulo x 3) 0)) (quote (1 2 3 4 5 6))))
scm> (eval (list-of ''hi 'for 'x 'in ''(1 2 3 4 5 6) 'if '(= (modulo x 3) 0)))
(hi hi)
scm> (eval (list-of '(car e) 'for 'e 'in ''((10) 11 (12) 13 (14 15)) 'if '(list? e)))
(10 12 14)
```

Hint:You may use the built-in`map`

and`filter`

procedures. Check out the Scheme Built-ins reference for more information.You may also find it helpful to look at the expression returned by list-of in the example above.

**Your Answer**Run in 61A Code

**Solution**

```
(define (list-of map-expr for var in lst if filter-expr)
`(map (lambda (,var) ,map-expr) (filter (lambda (,var) ,filter-expr) ,lst)))
```

### Q4: List Comprehension Macro

We managed to create a version of list comprehension in scheme, but we had to add a few extra steps to our implementation. We needed to quote parameters, make an extra call to eval at the end to actually get our answer, and quote the symbols `for`

, `in`

, and `if`

to prevent our program from throwing an Error.

To make things easier, we can use the `define-macro`

special form to simplify this process.

Now use a macro to implement the `list-of`

macro that can be called as
follows:

`(list-of <map-expression> for <name> in <list> if <conditional-expression>)`

Calling `list-of`

will return a new list constructed by doing the following for
each element in `<list>`

:

- Bind
`<name>`

to the element. - If
`<conditional-expression>`

evaluates to a truth-y value, evaluate`<map-expression>`

and add it to the result list.

Here are some examples:

```
scm> (list-of (* x x) for x in '(3 4 5) if (odd? x))
(9 25)
scm> (list-of 'hi for x in '(1 2 3 4 5 6) if (= (modulo x 3) 0))
(hi hi)
scm> (list-of (car e) for e in '((10) 11 (12) 13 (14 15)) if (list? e))
(10 12 14)
```

Hint:Again, you may use the built-in`map`

and`filter`

procedures. Check out the Scheme Built-ins reference for more information.If you're having a hard time with the question revisit Question 7 (If Macro Scheme) from Discussion 12.

**Your Answer**Run in 61A Code

**Solution**

```
(define-macro (list-of map-expr for var in lst if filter-expr)
`(map (lambda (,var) ,map-expr) (filter (lambda (,var) ,filter-expr) ,lst)))
```

Even though `for`

, `in`

and `if`

don't show up at all in the final Scheme
expression returned by the macro, we still need them as macro parameters
to ensure we can match the number of terms in the list comprehension form.

From there, we simple need to construct the body of the macro such that when evaluated it creates an expression identical to the expressions returned by the procedure version of list-of which we defined above.

### Q5: Shapeshifting Macros (Tutorial)

When writing macros in Scheme, we often create a list of symbols that evaluates to a desired Scheme expression. In this question, we'll practice different methods of creating such Scheme lists.

We have executed the following code to define `x`

and `y`

in our current environment.

```
(define x '(+ 1 1))
(define y '(+ 2 3))
```

We want to use `x`

and `y`

to build a list that represents the following expression:

`(begin (+ 1 1) (+ 2 3))`

What would be the result of calling `eval`

on a quoted version of the expression above?

`(eval '(begin (+ 1 1) (+ 2 3)))`

Now that we know what this expression should evaluate to, let's build our scheme list.

How would we construct the scheme list for the expression `(begin (+ 1 1) (+ 2 3))`

using quasiquotation?

``(begin ,x ,y)`

How would we construct this scheme list using the `list`

special form?

`(list 'begin x y)`

How would we construct this scheme list using the `cons`

special form?

`(cons 'begin (cons x (cons y nil)))`

### Q6: Max Macro (Tutorial)

Define the macro `max`

, which takes in two expressions `expr1`

and `expr2`

and returns the maximum of their values. If they have the same value, return the first expression. **For this question, it's okay if your solution evaluates expr1 and expr2 more than once.** As an extra challenge, think about how you could use the

`let`

special form to ensure that `expr1`

and `expr2`

are evaluated only once.```
scm> (max 5 10)
10
scm> (max 12 12)
12
scm> (max 100 99)
100
```

First, try using quasiquotation to implement this macro procedure.

**Your Answer**Run in 61A Code

**Solution**

```
(define-macro (max expr1 expr2)
`(if (>= ,expr1 ,expr2) ,expr1 ,expr2) )
```

Now, try writing this macro using the `list`

special form.

**Your Answer**Run in 61A Code

**Solution**

```
(define-macro (max expr1 expr2)
(list 'if (list '>= expr1 expr2) expr1 expr2) )
```

Finally, write this macro using the `cons`

special form.

**Your Answer**Run in 61A Code

**Solution**

```
(define-macro (max expr1 expr2)
(cons 'if
(cons
(cons '>= (cons expr1 (cons expr2 nil)))
(cons expr1 (cons expr2 nil))
)
) )
```

### Q7: When Macro (Tutorial)

Using macros, let's make a new special form, `when`

, that has the
following structure:

```
(when <condition>
(<expr1> <expr2> <expr3> ...))
```

If the condition is not false (a truthy expression), all the subsequent
operands are evaluated in order and the value of the last expression is
returned. Otherwise, the entire `when`

expression evaluates to
`okay`

.

```
scm> (when (= 1 0) ((/ 1 0) 'error))
okay
scm> (when (= 1 1) ((print 6) (print 1) 'a))
6
1
a
```

**Your Answer**Run in 61A Code

**Solution**

```
(define-macro (when condition exprs)
`(if ,condition ,(cons 'begin exprs) 'okay) )
```