INF4820. Common Lisp: Closures and Macros

Similar documents
FUNKCIONÁLNÍ A LOGICKÉ PROGRAMOVÁNÍ 4. LISP: PROMĚNNÉ, DALŠÍ VLASTNOSTI FUNKCÍ, BLOKY, MAPOVACÍ FUNKCIONÁLY, ITERAČNÍ CYKLY,

INF4820: Algorithms for Artificial Intelligence and Natural Language Processing. More Common Lisp

Common LISP-Introduction

Common Lisp. Blake McBride

Debugging in LISP. trace causes a trace to be printed for a function when it is called

FUNKCIONÁLNÍ A LOGICKÉ PROGRAMOVÁNÍ 5. LISP: MAKRA, DATOVÁ STRUKTURA ZÁZNAM

Announcement. Overview. LISP: A Quick Overview. Outline of Writing and Running Lisp.

Functional programming with Common Lisp

A little bit of Lisp

INF4820: Algorithms for Artificial Intelligence and Natural Language Processing. Common Lisp Fundamentals

Department of Computer and information Science Norwegian University of Science and Technology

Functional programming techniques

Recursion & Iteration

Symbolic Programming. Dr. Zoran Duric () Symbolic Programming 1/ 89 August 28, / 89

Robot Programming with Lisp

A Brief Introduction to Common Lisp

CS 480. Lisp J. Kosecka George Mason University. Lisp Slides

Common LISP Tutorial 1 (Basic)

Meet the Macro. a quick introduction to Lisp macros. Patrick Stein / TC Lisp Users Group /

Functional Programming with Common Lisp

19 Machine Learning in Lisp

CS 842 Ben Cassell University of Waterloo

Allegro CL Certification Program

A Genetic Algorithm Implementation

CSCI 2210: Programming in Lisp. Progn. Block. CSCI Programming in Lisp; Instructor: Alok Mehta 1. ANSI Common Lisp, Chapters 5-10

Artificial Intelligence Programming

Lambda Calculus and Lambda notation in Lisp II. Based on Prof. Gotshalks notes on Lambda Calculus and Chapter 9 in Wilensky.

Lisp Basic Example Test Questions

Announcements. Today s Menu

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

Jatha. Common Lisp in Java. Ola Bini JRuby Core Developer ThoughtWorks Studios.

Project 2: Scheme Interpreter

Modern Programming Languages. Lecture LISP Programming Language An Introduction

Lecture Notes on Lisp A Brief Introduction

Extra Lecture #7: Defining Syntax

Functional Programming. Pure Functional Programming

Every language has its own scoping rules. For example, what is the scope of variable j in this Java program?

Functions, Conditionals & Predicates

(defun fill-nodes (nodes texts name) (mapcar #'fill-node (replicate-nodes nodes (length texts) name) texts))

a little more on macros sort of like functions, but..

Names, Bindings, Scopes

(defvar *state* nil "The current state: a list of conditions.")

UMBC CMSC 331 Final Exam

Functional Languages. CSE 307 Principles of Programming Languages Stony Brook University

Loading Multiple Versions of an ASDF System in the Same Lisp Image

CSCI337 Organisation of Programming Languages LISP

CIS4/681 { Articial Intelligence 2 > (insert-sort '( )) ( ) 2 More Complicated Recursion So far everything we have dened requires

Notes on Higher Order Programming in Scheme. by Alexander Stepanov

Streams and Lazy Evaluation in Lisp

UMBC CMSC 331 Final Exam

Imperative, OO and Functional Languages A C program is

Lambda Calculus see notes on Lambda Calculus

More Scheme CS 331. Quiz. 4. What is the length of the list (()()()())? Which element does (car (cdr (x y z))) extract from the list?

Lecture #2 Kenneth W. Flynn RPI CS

LFE - a lisp on the Erlang VM

Allegro CL Certification Program

XLISP PLUS. Reference Manual. Version 3.0

Lexical vs. Dynamic Scope

Evaluating Scheme Expressions

Allegro CL Certification Program

Object Oriented Programming (OOP)

Some Naughty Bits. Jeremy Brown. January 7-8, 2003

Section 10: LISP to Scheme. Evolution of Software Languages

Introduction to Lisp

MIDTERM EXAMINATION - CS130 - Spring 2005

An introduction to Scheme

Basics of Using Lisp! Gunnar Gotshalks! BLU-1

LISP. Everything in a computer is a string of binary digits, ones and zeros, which everyone calls bits.

11/6/17. Functional programming. FP Foundations, Scheme (2) LISP Data Types. LISP Data Types. LISP Data Types. Scheme. LISP: John McCarthy 1958 MIT

1 CLWEB INTRODUCTION 1

TAIL RECURSION, SCOPE, AND PROJECT 4 11

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

This should prevent residual sexism, racism, favoritism lurking in the unconscious of your professor & TA from biasing grading!!

Symbolic Computation and Common Lisp

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

CS 360 Programming Languages Interpreters

(defmacro while (condition &body body) `(iterate loop () (if,condition (loop)))))

CL1 Manual. Alan Bawden ABSTRACT

Robot Programming with Lisp

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

Macroexpand-All: An Example of a Simple Lisp Code Walker

Associative Database Managment WIlensky Chapter 22

(defmacro for ((var start stop) &body body) (do ((,var,start (1+,var)) (limit,stop)) ((>,var

Introduction to LISP. York University Department of Computer Science and Engineering. York University- CSE V.

Review of Functional Programming

Topic III. LISP : functions, recursion, and lists References: Chapter 3 of Concepts in programming languages by J. C. Mitchell. CUP, 2003.

FUNKCIONÁLNÍ A LOGICKÉ PROGRAMOVÁNÍ 2. ÚVOD DO LISPU: ATOMY, SEZNAMY, FUNKCE,

Associative Database Managment

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

4. Functional Programming Language-Oriented Programming

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

Lecture #5 Kenneth W. Flynn RPI CS

Putting the fun in functional programming

Lisp. Versions of LISP

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

A Quick Introduction to Common Lisp

Midterm Examination (Sample Solutions), Cmput 325

Algorithms for AI and NLP (INF4820 Lisp & FSAs)

Fall 2018 Discussion 8: October 24, 2018 Solutions. 1 Introduction. 2 Primitives

Concepts of programming languages

Transcription:

INF4820 Common Lisp: Closures and Macros Erik Velldal University of Oslo Oct. 19, 2010 Erik Velldal INF4820 1 / 22

Topics for Today More Common Lisp A quick reminder: Scope, binding and shadowing Closures Anonymous functions Functions that return functions Memoization Code that generates code: Macros Erik Velldal INF4820 2 / 22

Some Terminology (from Seibel 2005) Binding form A form introducing a variable such as a function definition or a let expression. Scope The area of the program where the variable name can be used to refer to the variable s binding. Lexically scoped variables can be referred to only by code that is textually within their binding form. Shadowing When binding forms are nested and introduce variables of the same name, the innermost binding shadows the outer bindings. Erik Velldal INF4820 3 / 22

A Reminder: Bindings, Scope and Shadowing (setq foo 24) (let ((foo 42) (bar foo)) (print bar)) 24 Erik Velldal INF4820 4 / 22

A Reminder: Bindings, Scope and Shadowing (setq foo 24) (let ((foo 42) (bar foo)) (print bar)) 24 (let* ((foo 42) (bar foo)) (print bar)) 42 Erik Velldal INF4820 4 / 22

A Reminder: Bindings, Scope and Shadowing (setq foo 24) (let ((foo 42) (bar foo)) (print bar)) 24 (let* ((foo 42) (bar foo)) (print bar)) 42 (let ((foo 42)) (let ((bar foo)) (print bar))) 42 Erik Velldal INF4820 4 / 22

Closures Local variables in Common Lisp are based on lexical scoping. But, in CL the concept of closures still makes possible the use of variable references in functions that are called in code outside the scope of the binding form that introduced the variables. Provides many of the advantages of global variables and OO, without the disadvantages. Confused yet? Some examples will help... Erik Velldal INF4820 5 / 22

First, a rather boring example (no closures here) A function count() using the special variable *c*. (defvar *c* 0) (defun count (action &optional (n 1)) (case action (:add (incf *c* n)) (:sub (decf *c* n)) (:print (format t "~&Current count: ~d.~%" *c*)))) (count :add 5) 5 (count :print) Current count: 5. *c* 5 Erik Velldal INF4820 6 / 22

An Example Using Closures A version of count() based on a closure over a free variable. (let ((c 0)) (defun count (action &optional (n 1)) (case action (:add (incf c n)) (:sub (decf c n)) (:print (format t "~&Current count: ~d.~%" c))))) (count :sub 11) -11 (count :add) -10 (count :print) Current count: -10. Erik Velldal INF4820 7 / 22

Combining Closures with Anonymous Functions A function that returns an anonymous function with it s own closure: (defun make-count () (let ((c 0)) # (lambda (action &optional (n 1)) (case action (:add (incf c n)) (:sub (decf c n)) (:print (format t "~&Current count: ~d.~%" c)))))) (setq count1 (make-count)) (setq count2 (make-count)) (funcall count1 :add 5) 5 (funcall count2 :sub) -1 (funcall count1 :print) Current count: 5. (funcall count2 :print) Current count: -1. Erik Velldal INF4820 8 / 22

Closures + Anonymous Functions (Lambda expressions) When a function is defined in a non-null lexical environment, we say that it closes over and captures the bindings of its free variables. The power of closures is often best seen in combination with anonymous functions (as in make-count()). Erik Velldal INF4820 9 / 22

Closures + Anonymous Functions (Lambda expressions) When a function is defined in a non-null lexical environment, we say that it closes over and captures the bindings of its free variables. The power of closures is often best seen in combination with anonymous functions (as in make-count()). In fact, you ve probably already used closures in combination with anonymous functions in settings like: (let ((n 2)) (mapcar # (lambda(x) (expt x n)) (1 2 3))) where # (lambda(x) (expt x n)) closes over the free variable n. Erik Velldal INF4820 9 / 22

Memoization A general technique for speeding up (repeated) calls to a (computationally expensive) function. By caching the computed return values, subsequent calls can retrieve the value by look-up. Erik Velldal INF4820 10 / 22

Memoization A general technique for speeding up (repeated) calls to a (computationally expensive) function. By caching the computed return values, subsequent calls can retrieve the value by look-up. memoize() below takes a function as an argument and returns a new anonymous memoized version of that function, with a closure that contains a hash-table for storing results. Erik Velldal INF4820 10 / 22

Memoization A general technique for speeding up (repeated) calls to a (computationally expensive) function. By caching the computed return values, subsequent calls can retrieve the value by look-up. memoize() below takes a function as an argument and returns a new anonymous memoized version of that function, with a closure that contains a hash-table for storing results. (defun memoize (fn) (let ((cache (make-hash-table :test # equal))) # (lambda (&rest args) (multiple-value-bind (val stored-p) (gethash args cache) (if stored-p val (setf (gethash args cache) (apply fn args))))))) Erik Velldal INF4820 10 / 22

Memoization (cont d) (defun fib (n) (if (or (zerop n) (= n 1)) 1 (+ (fib (- n 1)) (fib (- n 2))))) (setq mem-fib (memoize # fib)) Erik Velldal INF4820 11 / 22

Memoization (cont d) (defun fib (n) (if (or (zerop n) (= n 1)) 1 (+ (fib (- n 1)) (fib (- n 2))))) (setq mem-fib (memoize # fib)) (time (funcall mem-fib 30)) ; real time 27,248 msec ; space allocation: ; 63,594,037 cons cells, 6,721,623,680 other bytes 1346269 (time (funcall mem-fib 30)) ; real time 0 msec ; space allocation: ; 31 cons cells, 240 other bytes 1346269 Erik Velldal INF4820 11 / 22

Memoization (cont d) Our memoized Fibonacci function isn t taking full advantage of the memoization... Why? Erik Velldal INF4820 12 / 22

Memoization (cont d) Our memoized Fibonacci function isn t taking full advantage of the memoization... Why? The recursive calls within the function are still using the original non-memoized version! A quick and dirty fix: (setf (symbol-function fib) mem-fib) A good exercise: Write an improved version of memoize() that does this automatically! (PS: It should also save a copy of the original function object so we can unmemoize later.) Erik Velldal INF4820 12 / 22

Memoization (cont d) Our memoized Fibonacci function isn t taking full advantage of the memoization... Why? The recursive calls within the function are still using the original non-memoized version! A quick and dirty fix: (setf (symbol-function fib) mem-fib) A good exercise: Write an improved version of memoize() that does this automatically! (PS: It should also save a copy of the original function object so we can unmemoize later.) Other useful improvements we could make: Specialized data structures and/or equality tests depending on argument type. Canonical sorting of arguments. Support for multiple return values, keyword arguments, etc. Erik Velldal INF4820 12 / 22

Macros Pitch: programs that generate programs. Macro expansion time vs runtime Allows us to control or prevent the evaluation of arguments. Erik Velldal INF4820 13 / 22

Macros Pitch: programs that generate programs. Macro expansion time vs runtime Allows us to control or prevent the evaluation of arguments. Some examples of important Common Lisp built-in macros come in the form of control structures: Conditioning: and, or, if, when, unless, cond, case,... Looping / iteration: do, do*, dolist, dotimes, loop,... Also definitions and assignment: defun, defvar, defparameter, setf,... Erik Velldal INF4820 13 / 22

Defining Our Own Macros With defmacro we can write Lisp code that generates Lisp code. Important operators when writing macros : Quote suppresses evaluation. : Backquote also suppresses evaluation, but..,: A comma inside a backquoted form means the following subform should be evaluated.,@: Comma-at splices lists. Erik Velldal INF4820 14 / 22

Defining Our Own Macros With defmacro we can write Lisp code that generates Lisp code. Important operators when writing macros : Quote suppresses evaluation. : Backquote also suppresses evaluation, but..,: A comma inside a backquoted form means the following subform should be evaluated.,@: Comma-at splices lists. (let ((x 3) (y (1 2))) (a (,(if (oddp x) b c)),@y)) Erik Velldal INF4820 14 / 22

Defining Our Own Macros With defmacro we can write Lisp code that generates Lisp code. Important operators when writing macros : Quote suppresses evaluation. : Backquote also suppresses evaluation, but..,: A comma inside a backquoted form means the following subform should be evaluated.,@: Comma-at splices lists. (let ((x 3) (y (1 2))) (a (,(if (oddp x) b c)),@y)) (a (c) 1 2) Erik Velldal INF4820 14 / 22

Example: A Rather Silly Macro CL-USER(53): (defmacro dolist-reverse ((e list) &rest body) (let ((r (reverse,list))) (dolist (,e r),@body))) DOLIST-REVERSE Erik Velldal INF4820 15 / 22

Example: A Rather Silly Macro CL-USER(53): (defmacro dolist-reverse ((e list) &rest body) (let ((r (reverse,list))) (dolist (,e r),@body))) DOLIST-REVERSE CL-USER(54): (dolist-reverse (x (list 1 2 3)) (print x)) 3 2 1 NIL Erik Velldal INF4820 15 / 22

Example: A Rather Silly Macro CL-USER(53): (defmacro dolist-reverse ((e list) &rest body) (let ((r (reverse,list))) (dolist (,e r),@body))) DOLIST-REVERSE CL-USER(54): (dolist-reverse (x (list 1 2 3)) (print x)) 3 2 1 NIL All according to plan. Or...? Erik Velldal INF4820 15 / 22

Unintended variable capture can be a pitfall... CL-USER(55): (let ((r 42)) (dolist-reverse (x (list 1 2 3)) (print (list x r)))) (3 (3 2 1)) (2 (3 2 1)) (1 (3 2 1)) NIL Not quite what we wanted... The variable r of the enclosing let-form is shadowed by the let in the macro-expansion! Remedy: Use gensym() at macro expansion time to create unique variable names used in the expansion. Erik Velldal INF4820 16 / 22

gensym to the rescue! Below, r is a variable whose value is the (unique) name of another variable. CL-USER(56): (defmacro dolist-reverse ((e list) &rest body) (let ((r (gensym))) (let ((,r (reverse,list))) (dolist (,e,r),@body)))) DOLIST-REVERSE CL-USER(57): (let ((r 42)) (dolist-reverse (x (list 1 2 3)) (print (list x r)))) (3 42) (2 42) (1 42) Erik Velldal INF4820 17 / 22

Other common pitfalls when writing macros Unintentionally evaluating arguments multiple times. Evaluating arguments in an order unexpected by the caller (i.e. not left to right). A useful debugging tool: macroexpand-1(). Erik Velldal INF4820 18 / 22

Macro-expansion time vs runtime What happens when we use a macro? (1) First the expression specified by the macro definition is built, the so-called macro-expansion... (2)... and then that expression is evaluated in place of the original macro call. Step (1) happens when Lisp parses the program prior to compiling, and step (2) when the code is called at runtime. Two levels of code: Expander code (the code used in the macro to generate its expansion) Expansion code (the code in the expansion itself) Erik Velldal INF4820 19 / 22

A more useful example; dolist-permutations (defmacro dolist-permutations (lists &body body) (if (null (cdr lists)) (dolist,(first lists),@body) (dolist,(first lists) (dolist-permutations,(cdr lists),@body)))) Erik Velldal INF4820 20 / 22

A more useful example; dolist-permutations (defmacro dolist-permutations (lists &body body) (if (null (cdr lists)) (dolist,(first lists),@body) (dolist,(first lists) (dolist-permutations,(cdr lists),@body)))) The macro includes calls to other macros, including itself. Using self-reference / recursion in macros requires care to not send the compiler into an infinite loop! Recursion in expander code (as here) vs recursion in expansion code. While expanding dolist-permutations we recur over forms, not values (which are unavailable at expansion time). Erik Velldal INF4820 20 / 22

Using our new macro, dolist-permutations CL-USER(190): (dolist-permutations ((x (a b)) (y (1 2 3)) (z (foo))) (print (list x y z))) (A 1 FOO) (A 2 FOO) (A 3 FOO) (B 1 FOO) (B 2 FOO) (B 3 FOO) NIL Erik Velldal INF4820 21 / 22

For More on Macros... See Paul Graham s On Lisp: http://www.paulgraham.com/onlisp.html (Out of print but freely available online.) Includes several chapters on writing Lisp macros. Erik Velldal INF4820 22 / 22