Racket: Macros. Advanced Functional Programming. Jean-Noël Monette. November 2013

Similar documents
Racket: Modules, Contracts, Languages

Scheme-Style Macros: Patterns and Lexical Scope

Streams, Delayed Evaluation and a Normal Order Interpreter. CS 550 Programming Languages Jeremy Johnson

Why Macros? Scheme-Style Macros: Patterns and Lexical Scope. Macros versus Arbitrary Program Generators. Macros versus Arbitrary Program Generators

User-defined Functions. Conditional Expressions in Scheme

Concepts of programming languages

A brief tour of history

Macros. Pat Hanrahan. CS448h Fall 2015

An introduction to Scheme

Class 6: Efficiency in Scheme

CSE341: Programming Languages Lecture 15 Macros. Zach Tatlock Winter 2018

What is a macro. CSE341: Programming Languages Lecture 15 Macros. Example uses. Using Racket Macros. Zach Tatlock Winter 2018

Streams and Evalutation Strategies

6.184 Lecture 4. Interpretation. Tweaked by Ben Vandiver Compiled by Mike Phillips Original material by Eric Grimson

INF4820. Common Lisp: Closures and Macros

CS 360 Programming Languages Interpreters


Notes on Higher Order Programming in Scheme. by Alexander Stepanov

How to Design Programs

CS 314 Principles of Programming Languages

Computer Science 21b (Spring Term, 2015) Structure and Interpretation of Computer Programs. Lexical addressing

Scheme. Functional Programming. Lambda Calculus. CSC 4101: Programming Languages 1. Textbook, Sections , 13.7

Normal Order (Lazy) Evaluation SICP. Applicative Order vs. Normal (Lazy) Order. Applicative vs. Normal? How can we implement lazy evaluation?

Scheme Quick Reference

An Introduction to Scheme

Principles of Programming Languages Topic: Functional Programming Professor L. Thorne McCarty Spring 2003

CS 61A, Fall, 2002, Midterm #2, L. Rowe. 1. (10 points, 1 point each part) Consider the following five box-and-arrow diagrams.

Introduction to Scheme

Syntax: Meta-Programming Helpers

CS 842 Ben Cassell University of Waterloo

Common LISP-Introduction

SCHEME AND CALCULATOR 5b

Lecture 09: Data Abstraction ++ Parsing is the process of translating a sequence of characters (a string) into an abstract syntax tree.

Scheme Tutorial. Introduction. The Structure of Scheme Programs. Syntax

6.037 Lecture 4. Interpretation. What is an interpreter? Why do we need an interpreter? Stages of an interpreter. Role of each part of the interpreter

Functional Programming. Pure Functional Languages

From Syntactic Sugar to the Syntactic Meth Lab:

Functional Programming. Pure Functional Programming

Title. Authors. Status. 1. Abstract. 2. Rationale. 3. Specification. R6RS Syntax-Case Macros. Kent Dybvig

Functional Programming. Pure Functional Languages

Scheme Quick Reference

Vectors in Scheme. Data Structures in Scheme

SCHEME 7. 1 Introduction. 2 Primitives COMPUTER SCIENCE 61A. October 29, 2015

COP4020 Programming Assignment 1 - Spring 2011

Languages as Libraries

Programming Languages. Thunks, Laziness, Streams, Memoization. Adapted from Dan Grossman s PL class, U. of Washington

Summer 2017 Discussion 10: July 25, Introduction. 2 Primitives and Define

TAIL RECURSION, SCOPE, AND PROJECT 4 11

Scheme in Scheme: The Metacircular Evaluator Eval and Apply

CSE413: Programming Languages and Implementation Racket structs Implementing languages with interpreters Implementing closures

Functional programming with Common Lisp

The Typed Racket Reference

A Brief Introduction to Scheme (II)

Typed Racket: Racket with Static Types

CS1102: Macros and Recursion

Organization of Programming Languages CS3200/5200N. Lecture 11

Design and Analysis of Algorithms Prof. Madhavan Mukund Chennai Mathematical Institute. Module 02 Lecture - 45 Memoization

Case Study: Undefined Variables

CSE 413 Languages & Implementation. Hal Perkins Winter 2019 Structs, Implementing Languages (credits: Dan Grossman, CSE 341)

Procedural abstraction SICP Data abstractions. The universe of procedures forsqrt. Procedural abstraction example: sqrt

CS450 - Structure of Higher Level Languages

RACKET BASICS, ORDER OF EVALUATION, RECURSION 1

6.945 Adventures in Advanced Symbolic Programming

CS 314 Principles of Programming Languages

Macros that Work Together

MORE SCHEME. 1 What Would Scheme Print? COMPUTER SCIENCE MENTORS 61A. October 30 to November 3, Solution: Solutions begin on the following page.

Overview. Elements of Programming Languages. Evaluation order. Evaluation order

Programming Principles

It s Turtles All the Way Up: Self-Extensible Programming Languages in PLT Scheme

The Typed Racket Guide

2D Syntax. Version October 30, 2017

CS 61A Interpreters, Tail Calls, Macros, Streams, Iterators. Spring 2019 Guerrilla Section 5: April 20, Interpreters.

Parser Tools: lex and yacc-style Parsing

JRM s Syntax-rules Primer for the Merely Eccentric

Functional Programming

C311 Lab #3 Representation Independence: Representation Independent Interpreters

CS 314 Principles of Programming Languages. Lecture 16

COP4020 Programming Languages. Functional Programming Prof. Robert van Engelen

Box-and-arrow Diagrams

6.037 Lecture 7B. Scheme Variants Normal Order Lazy Evaluation Streams

Intro. Scheme Basics. scm> 5 5. scm>

Scheme as implemented by Racket

1.3. Conditional expressions To express case distinctions like

Typed Scheme: Scheme with Static Types

CS115 - Module 10 - General Trees

Scheme: Expressions & Procedures

Below are example solutions for each of the questions. These are not the only possible answers, but they are the most common ones.

Programming Languages: Application and Interpretation

SCHEME 10 COMPUTER SCIENCE 61A. July 26, Warm Up: Conditional Expressions. 1. What does Scheme print? scm> (if (or #t (/ 1 0)) 1 (/ 1 0))

CS61A Midterm 2 Review (v1.1)

CS 360 Programming Languages Day 14 Closure Idioms

Evaluating Scheme Expressions

Writing Interpreters Racket eval. CSE341 Section 7. ASTs, Interpreters, MUPL. Sunjay Cauligi. February 21 st, Sunjay Cauligi CSE341 Section 7

An Explicit Continuation Evaluator for Scheme

Extra Lecture #7: Defining Syntax

Chapter 1. Fundamentals of Higher Order Programming

Macros that Work Together

SCHEME 8. 1 Introduction. 2 Primitives COMPUTER SCIENCE 61A. March 23, 2017

Lexical vs. Dynamic Scope

Parser Tools: lex and yacc-style Parsing

Transcription:

Racket: Macros Advanced Functional Programming Jean-Noël Monette November 2013 1

Today Macros pattern-based macros Hygiene Syntax objects and general macros Examples 2

Macros (According to the Racket Guide...) A macro is a new syntactic form that can be expanded into existing forms through a transformer. Let s start with an example. Say we do not have the or construct and we want to implement it in terms of if. 3

Implementing or: First try What are the problems? y is always evaluated. (define (or x y) (if x x y)) (or 3 (/ 2 0)) A function does not work. We need something that would transform the or into a if before running time. 4-8

Using define-syntax-rule define-syntax-rule defines a macro. (define-syntax-rule (or x y) (if x x y)) (or 3 (/ 2 0)) is effectively transformed into (if 3 3 (/ 2 0)). The problem is solved. But we introduced another one: x is evaluated twice. (or (begin (display 3) 3) (/ 2 0)) 9-10

Introducing let Seems like we are good. (define-syntax-rule (or x y) (let ([z x]) (if z z y))) (or 3 (/ 2 0)) (or (begin (display 3) 3) (/ 2 0)) 11-12

Hygiene Consider another example (define-syntax-rule (swap! x y) (let ([tmp x]) (set! x y) (set! y tmp))) What if we pass a variable named tmp (or even set! or let) to swap!? The naive rewriting would produce an erroneous code. Hopefully, the racket macro system is "hygienic", that is it makes sure to avoid clashes between identifiers and it uses lexical scoping. 13-14

More complicated macros (define-syntax or (syntax-rules () [(or) #f] [(or x) x] [(or x y) (let ([z x]) (if z z y))] [(or x y...) (or x (or y...))])) syntax-rules defines several rules by pattern matching.... in the pattern matches several (zero, one or more) occurences of the last pattern ( y in this case).... in the template provides all the bound occurences. 15

Using literal keywords do is part of the macro definition. (define-syntax while (syntax-rules (do) [(while cond do body...) (let loop () (when cond body... (loop)))])) (while (> x 0) do (displayln x) (set! x (- x 1))) 16

A wrong version of while (define-syntax while (syntax-rules (do) [(while cond do body...) (when cond body... (while cond do body...))])) Why? The macro are expanded before runtime. In this case, the expansion never completes. 17-18

Examples Memoization Rewriting 19

Example: Implementing Memoization Memoization is to record the result of previous calls to a procedure to avoid recomputing them again. For instance, a (naive) fibonacci procedure takes exponential time to complete. (define (fib x) (case x [(0 1) 1] [else (+ (fib (- x 1)) (fib (- x 2)))])) Memoization would allow us to reuse already computed results. We will define a macro define-memo to define a procedure with memoization. 20

define-memo (define-syntax-rule (define-memo (f args...) body...) (define f (let ([memo (make-hash)]) (lambda (args...) (cond [(hash-has-key? memo (list args...)) (hash-ref memo (list args...))] [else (let ([res (begin body...)]) (hash-set! memo (list args...) res) res)]))))) Previous results are stored in a (mutable) hash-table. The keys are the arguments of the function. The actual body of the function is executed only if the arguments are not already in the hash-table. 21

define-memo, application (define-syntax-rule (define-memo (f args...) body...) (define f (let ([memo (make-hash)]) (lambda (args...) (cond [(hash-has-key? memo (list args...)) (hash-ref memo (list args...))] [else (let ([res (begin body...)]) (hash-set! memo (list args...) res) res)]))))) (define-memo (fib/m x) (display x) ; to see when we actually execute the body (case x [(0 1) 1] [else (+ (fib/m (- x 1)) (fib/m (- x 2)))])) (fib/m 10) (fib/m 10) 22

Example: Expressions Simplification It is often useful to simplify expressions. We will do it here by rewriting rules. Consider this target piece of code (define s (simplifier [(+,x 0) ->,x] [(*,x 1) ->,x] [(+,x,x) -> (*,x 2)])) (s '(+ (+ y 0) (* y 1))) simplifier returns a function of one argument: the expression to simplify. 23

Simplifying one expression (define-syntax simplifier (syntax-rules (->) [(simplifier (x -> y)...) (lambda (form) (match form [`x `y]... [else form]))])) Each rule is rewriten as a clause in a match expression. The whole code is enclosed in a procedure. -> is part of the macro definition, not a variable. 24

Making it recursive The previous code only simplifies the root of the expression. We need to call the simplification on each sub-expression. We need to repeat the simplification until fix-point. (define ((recur simplify) form) (let loop ([reduced-form (cond [(list? form) (map (recur simplify) form)] [else form])]) (let ([final-form (simplify reduced-form)]) (if (equal? final-form reduced-form) final-form (loop final-form))))) simplify is the function produced by simplifier. 25-26

Putting it together (define-syntax simplifier (syntax-rules (->) [(simplifier (x -> y)...) (recur (lambda (form) ; Added recur here...))])) ;...: the same as before (define s (simplifier [(+,x 0) ->,x] [(*,x 1) ->,x] [(+,x,x) -> (*,x 2)])) (s '(+ (+ y 0) (* y 1))) 27

Going Further Up to now, we have seen macros based on patterns, rewriten to a template. Macros can do more than that, by manipulating the syntax (almost) however you want. For this, we need to introduce syntax objects. 28

Syntax Objects (syntax (+ 1 2)) returns a syntax object representing the data (+ 1 2) with lexical information and source code location. Syntax objects can be manipulated by the macro system. (define-syntax Hello (lambda (stx) (display stx) (datum->syntax stx (cdr (syntax->datum stx))))) (Hello + 1 3) The body of define-syntax must be a procedure taking a syntax object and returning a syntax object. 29-30

syntax-case syntax-rules is a shortcut for "simple" macros: one writes directly the resulting code. In the general form, one can use the whole power of Racket to modify the syntax. syntax-case allows one to match the syntax against patterns. #' is a shortcut for syntax There is also #` and #, for quasisyntax and unsyntax. 31

syntax-case: Example The swap example with syntax. (define-syntax swap (lambda (stx) (syntax-case stx () [(swap a b) #'(let ([tmp a]) (set! a b) (set! b tmp))]))) 32

syntax-case: Example The shortcut version to avoid the lambda. (define-syntax (swap stx) (syntax-case stx () [(swap a b) #'(let ([tmp a]) (set! a b) (set! b tmp))])) This is not better than syntax-rules but wait... 33-34

syntax-case: Example (2) Checking (before runtime) that the arguments make sense. (define-syntax (swap stx) (syntax-case stx () [(swap a b) (cond [(and (identifier? #'a) (identifier? #'b)) #'(let ([tmp a]) (set! a b) (set! b tmp))] [else (raise-syntax-error #f "not an identifier" stx (if (identifier? #'a) #'a #'b))])])) 35

More syntax manipulation syntax-e returns the data in the syntax object. syntax->datum recursively calls syntax-e. datum->syntax creates a syntax object from some data and the lexical information of another syntax object. syntax-list is like syntax-e but it unrolls a whole list. with-syntax is similar to a let statement but for syntax objects. syntax? tells whether the given value is a syntax object. identifier? tells if the syntax object represents an identifier ( syntax-e returns a symbol). 36

Example: Extended Rewriting Now we want to be able to put guards on our rewriting rules. (define s (simplifier [(+,x 0) ->,x] [(*,x 1) ->,x] [(+,x,x) -> (*,x 2)] [(+,k1,k2)? (and (number? k1) (number? k2)) ->,(+ k1 k2)])) (s '(+ (+ y (+ 2-2)) (* y 1))) 37

Extended Rewriting with syntax (define-syntax (simplifier stx) (define (clause expr) (syntax-case expr (? ->) [(x -> y) #'[`x `y]] [(x? z -> y) #'[`x (=> fail) (when (not z) (fail)) `y]])) (syntax-case stx () [(_ expr...) (with-syntax ([(res...) (map clause (syntax->list #'(expr...)))]) #'(simplify (lambda (form) (match form res... [else form]))))])) 38

Breaking Hygiene Sometimes we want to create identifiers that are available outside of the macro. This happens when creating a struct, for instance. (struct box (val)) creates e.g. the procedures box-val and box?. There is a simple way to break hygiene: Use (datum->syntax lex-cont name) where lex-cont is an identifier that is given to the macro. In that case, the new name inherits the same lexical scope. 39-40

Breaking Hygiene: Example Define a for loop with it bound to the current value. (define-syntax (for stx) (syntax-case stx (do) [(for (obj1 objs...) do body...) (with-syntax ([it (datum->syntax #'obj1 'it)]) #'(let loop ([its (list objs...)] [it obj1]) body... (when (not (empty? its)) (loop (cdr its) (car its)))))])) (for (1 2 3 4) do (display it)) ; OK (for (1 2 3 4) do (display its)) ; Does not work 41

Breaking Hygiene: Building names This piece of code shows how to build a name from another one: (datum->syntax #'orig-name (string->symbol (string-append "prefix-" (symbol->string (syntax->datum #'orig-name))))) One need to go from a syntax object to a symbol to a string, manipulate the string, and then all the other way around. 42

Phases We can use normal procedures when defining macros. But macros are expanded before runtime. How is that possible? There are several phases. The identifiers in each phase are only available in that phase, unless imported. One can have even more than two phases. 43-44

Conclusion: word of caution Macros can be very powerful. As any powertool, they must be used with care. In particular, do not use a macro if a procedure can make the work. 45