Environments

Similar documents
Functions & First Class Function Values

Bindings & Substitution

Functional abstraction. What is abstraction? Eating apples. Readings: HtDP, sections Language level: Intermediate Student With Lambda

Functional abstraction

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

The Environment Model. Nate Foster Spring 2018

An Introduction to Functions

The Environment Model

Functional Programming. Pure Functional Programming

Compilers. Type checking. Yannis Smaragdakis, U. Athens (original slides by Sam

SCHEME AND CALCULATOR 5b

Higher-Order Functions (Part I)

CSE341: Programming Languages Lecture 17 Implementing Languages Including Closures. Dan Grossman Autumn 2018

TAIL RECURSION, SCOPE, AND PROJECT 4 11

CS 360 Programming Languages Interpreters

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

CSE341 Spring 2017, Final Examination June 8, 2017

CSCC24 Functional Programming Scheme Part 2

CS115 - Module 9 - filter, map, and friends

Closures. Mooly Sagiv. Michael Clarkson, Cornell CS 3110 Data Structures and Functional Programming

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

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

CS115 - Module 10 - General Trees

CMSC 330: Organization of Programming Languages

CS 415 Midterm Exam Spring 2002

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

CS558 Programming Languages

Functional Programming. Pure Functional Languages

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

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

CSE341 Spring 2017, Final Examination June 8, 2017

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

CS558 Programming Languages

CPS 506 Comparative Programming Languages. Programming Language Paradigm

Functional Programming. Pure Functional Languages

PROFESSOR: Last time, we took a look at an explicit control evaluator for Lisp, and that bridged the gap between

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

A Small Interpreted Language

Higher-Order Functions

Introduction to Typed Racket. The plan: Racket Crash Course Typed Racket and PL Racket Differences with the text Some PL Racket Examples

Module 9: Binary trees

Recap: Functions as first-class values

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

Chapter 15. Functional Programming Languages

CS558 Programming Languages

Variables. Substitution

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

April 2 to April 4, 2018

Quick announcement. Midterm date is Wednesday Oct 24, 11-12pm.


;; definition of function, fun, that adds 7 to the input (define fun (lambda (x) (+ x 7)))

OCaml. ML Flow. Complex types: Lists. Complex types: Lists. The PL for the discerning hacker. All elements must have same type.

Functional Programming. Big Picture. Design of Programming Languages

Closures. Mooly Sagiv. Michael Clarkson, Cornell CS 3110 Data Structures and Functional Programming

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

Functional Programming

CS61A Notes Week 6: Scheme1, Data Directed Programming You Are Scheme and don t let anyone tell you otherwise

Module 8: Local and functional abstraction

Concepts of programming languages

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

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

The Compiler So Far. CSC 4181 Compiler Construction. Semantic Analysis. Beyond Syntax. Goals of a Semantic Analyzer.

Local definitions and lexical scope

Local definitions and lexical scope. Local definitions. Motivating local definitions. s(s a)(s b)(s c), where s = (a + b + c)/2.

CS61A Midterm 2 Review (v1.1)

Write a procedure powerset which takes as its only argument a set S and returns the powerset of S.

Module 9: Trees. If you have not already, make sure you. Read How to Design Programs Sections 14, 15, CS 115 Module 9: Trees

CS 314 Principles of Programming Languages

;;; Determines if e is a primitive by looking it up in the primitive environment. ;;; Define indentation and output routines for the output for

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

Basics of Using Lisp! Gunnar Gotshalks! BLU-1

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

CSE 413 Midterm, May 6, 2011 Sample Solution Page 1 of 8

ENVIRONMENT MODEL: FUNCTIONS, DATA 18

Homework 1. Notes. What To Turn In. Unix Accounts. Reading. Handout 3 CSCI 334: Spring, 2017

Variables and Bindings

Compiler construction

CS558 Programming Languages

CSE341, Spring 2013, Final Examination June 13, 2013

Spring 2018 Discussion 7: March 21, Introduction. 2 Primitives

Semantic Analysis. Outline. The role of semantic analysis in a compiler. Scope. Types. Where we are. The Compiler Front-End

Common LISP-Introduction

An Explicit Continuation Evaluator for Scheme

Plan (next 4 weeks) 1. Fast forward. 2. Rewind. 3. Slow motion. Rapid introduction to what s in OCaml. Go over the pieces individually

Programming Languages. Streams Wrapup, Memoization, Type Systems, and Some Monty Python

So what does studying PL buy me?

Lecture 13 CIS 341: COMPILERS

Running FAE Programs Natively

CS558 Programming Languages Winter 2018 Lecture 4a. Andrew Tolmach Portland State University

Begin at the beginning

Fall 2017 Discussion 7: October 25, 2017 Solutions. 1 Introduction. 2 Primitives

Typical workflow. CSE341: Programming Languages. Lecture 17 Implementing Languages Including Closures. Reality more complicated

CMSC 330: Organization of Programming Languages. Operational Semantics

Lecture 16: Static Semantics Overview 1

CS 275 Name Final Exam Solutions December 16, 2016

CMSC330. Objects, Functional Programming, and lambda calculus

CSCI B522 Lecture 11 Naming and Scope 8 Oct, 2009

Tail Calls. CMSC 330: Organization of Programming Languages. Tail Recursion. Tail Recursion (cont d) Names and Binding. Tail Recursion (cont d)

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

Comp 311: Sample Midterm Examination

Transcription:

Environments PLAI Chapter 6 Evaluating using substitutions is very inefficient To work around this, we want to use a cache of substitutions. We begin evaluating with no cached substitutions, then collect them as we encounter bindings. When we reach an identifier it is no longer an error we must consult the substitution cache at that point.

Environments 2017-03-02 PLAI Chapter 6 Evaluating using substitutions is very inefficient To work around this, we want to use a cache of substitutions. We begin evaluating with no cached substitutions, then collect them as we encounter bindings. When we reach an identifier it is no longer an error we must consult the substitution cache at that point. 1. When evaluating with substitutions, at each scope, we copy a piece of the program AST. This includes all function calls which implies an impractical cost (function calls should be cheap!).

Initial Implementation of Cache Functionality First, we need a type for a substitution cache. For this we will use a list of lists of two elements each a name and its value FLANG: 1 ;; a type for substitution caches: ( define- type Binding [ bind ( name : symbol ) ( val : FLANG )]) ( define- type- alias SubstCache ( listof Binding ))

We an empty substitution cache, a way to extend 2 ( define empty- subst empty ) ( define ( extend id expr sc) ( cons ( bind id expr ) sc)) And a way to look things up: 3 ( define ( lookup name sc) (if ( empty? sc) ( error ' lookup ( string- append " no binding ")) ( type- case Binding ( first sc) [ bind ( first- name first- val ) (if (eq? name first-name ) first- val ( lookup name ( rest sc)))])))

Formal Rules for Cached Substitutions The formal evaluation rules are now different. Evaluation carries along a substitution cache Our substition code all has the following behaviour: 4 lookup (x,empty - subst ) = error! lookup (x, extend (x,e,sc)) = E lookup (x, extend (y,e,sc)) = lookup (x,sc) if `x '!= `y '

Now we can write the new rules for eval. The change to arithmetic and fun is small, just pass an extra parameter. 5 eval (N,sc) = N eval ({+ E1 E2},sc) = eval (E1,sc) + eval (E2,sc) eval ({ fun {x} E},sc) = { fun {x} E}

Identifiers need to be looked up: 6 eval (x,sc) = lookup (x,sc) subst is replaced by extend and lookup 7 eval ({ with {x E1} E2},sc) = eval (E2, extend (x, eval (E1,sc),sc)) eval ({ call E1 E2},sc) = eval (Ef, extend (x, eval (E2,sc),sc)) if eval (E1,sc) = { fun {x} Ef} = error! otherwise

Evaluating with Substitution Caches 8 ;; evaluates FLANG expressions by reducing them to expressions ( define ( eval expr sc) ( type- case FLANG fval [ Num (n) expr ] [ Add (l r) ( arith-op + ( eval l sc) ( eval r sc))] [ Sub (l r) ( arith-op - ( eval l sc) ( eval r sc))] [ Mul (l r) ( arith-op * ( eval l sc) ( eval r sc))] [ Div (l r) ( arith-op / ( eval l sc) ( eval r sc))] [ With ( bound- id named- expr bound- body ) ( eval bound- body ( extend bound- id ( eval named- expr sc) sc))] [Id ( name ) ( lookup name sc)] [ Fun ( bound-id bound-body ) expr ]

Evaluating with Substitution Caches 2017-03-02 8 ;; evaluates FLANG expressions by reducing them to expressions ( define ( eval expr sc) ( type-case FLANG fval [ Num (n) expr ] [ Add (l r) ( arith-op + ( eval l sc) ( eval r sc))] [ Sub (l r) ( arith-op - ( eval l sc) ( eval r sc))] [ Mul (l r) ( arith-op * ( eval l sc) ( eval r sc))] [ Div (l r) ( arith-op / ( eval l sc) ( eval r sc))] [ With ( bound-id named-expr bound-body ) ( eval bound-body ( extend bound-id ( eval named-expr sc) sc))] [Id ( name ) ( lookup name sc)] [ Fun ( bound-id bound-body ) expr ] 1. the whole point is that we don t really do substitution, but use the cache instead. The lookup rules, and the places where extend is used replaces subst, and therefore specifies our scoping rules. 2. Also note that the rule for call is still very similar to the rule for with, but it looks like we have lost something the interesting bit with substituting into fun expressions.

9 [ Call ( fun-expr arg-expr ) ( let ([ fval ( eval fun-expr sc)]) ( type- case FLANG fval [( Fun bound- id bound- body ) ( eval bound- body ( extend bound- id ( eval arg- expr sc) sc))] [ else ( error 'eval ( string append " `call ' expects a function, got: " ( to-string fval )))]))])

Again, note that we don t need subst anymore, but the rest of the code (the data type definition, parsing, and arith-op ) is exactly the same.

Finally, we need to make sure that eval is initially called with an empty cache. This is easy to change in our main run entry point: 10 ( define ( run s-exp ) ( let ([ result ( eval ( parse-sx s-exp ) empty-subst )]) ( type- case FLANG result [( Num n) n] [ else ( error 'run ( string- append " evaluation returned a non- number: " ( to-string result )))])))

First, some fairly fancy looking things work: 11 ( test ( run '{ call { fun {x} {+ x 1}} 4}) 5) ( test ( run '{ with { add3 { fun {x} {+ x 3}}} { call add3 1}}) 4) ( test ( run '{ with { add3 { fun {x} {+ x 3}}} { with { add1 { fun {x} {+ x 1}}} { with {x 3} { call add1 { call add3 x }}}}}) 7)

By tracing, we see the substution cache acts like a stack 12 ( trace lookup ) ( test ( run '{ with { identity { fun {x} x}} { with { foo { fun {x} {+ x 1}}} { call { call identity foo } 123}}}) 124)

OTOH, we have three failing tests; the reasons look different, but turn out to be related. 13 ( test ( run '{ with {x 3} { with {f { fun {y} {+ x y }}} { with {x 5} { call f 4}}}}) 7) ( test ( run '{ call { with {x 3} { fun {y} {+ x y }}} 4}) 7) ( test ( run '{ call { call { fun {x} { call x 1}} { fun {x} { fun {y} {+ x y }}}} 123}) 124)

Dynamic and Lexical Scopes Scope has been problem for lots of language implementors, including the first version of Lisp. What should the following evaluate to: 14 ( run '{ with {x 3} { with {f { fun {y} {+ x y }}} { with {x 5} { call f 4}}}} ')

The answer depends on whether we have dynamic or static scope. Static Scope (also called Lexical Scope): In a language with static scope, each identifier gets its value from the scope of its definition, not its use. Dynamic Scope: In a language with dynamic scope, each identifier gets its value from the scope of its use, not its definition. Which is our new (caching) evaluator? Racket uses lexical scope, our new evaluator uses dynamic, the old substitution-based evaluator was static etc.

The answer depends on whether we have dynamic or static 2017-03-02 scope. Static Scope (also called Lexical Scope): In a language with static scope, each identifier gets its value from the scope of its definition, not its use. Dynamic Scope: In a language with dynamic scope, each identifier gets its value from the scope of its use, not its definition. Which is our new (caching) evaluator? Racket uses lexical scope, our new evaluator uses dynamic, the old substitution-based evaluator was static etc. 1. As a side-remark, Lisp began its life as a dynamically-scoped language. The artifacts of this were (sort-of) dismissed as an implementation bug. When Scheme was introduced, it was the first Lisp dialect that used strictly lexical scoping, and Racket is obviously doing the same. (Some Lisp implementations used dynamic scope for interpreted code and lexical scope for compiled code!) In fact, Emacs Lisp is one of the only live dialects of Lisp that is still dynamically scoped by default.

Compare a version of the above code in Racket: 15 ( let ((x 3)) ( let ((f ( lambda (y) (+ x y)))) ( let ((x 5)) (f 4)))) and the Emacs Lisp version (which looks almost the same): 16 ( let ((x 3)) ( let ((f ( lambda (y) (+ x y)))) ( let ((x 5)) ( funcall f 4))))

what happens when we use another function on the way?: 17 ( defun blah ( func val ) ( funcall func val )) ( let ((x 3)) ( let ((f ( lambda (y) (+ x y)))) ( let ((x 5)) ( blah f 4))))

note that renaming identifiers can lead to different results change that val to x : 18 ( defun blah ( func x) ( funcall func x)) ( let ((x 3)) ( let ((f ( lambda (y) (+ x y)))) ( let ((x 5)) ( blah f 4))))

Consider this Emacs Lisp function: 19 ( defun return- x () x) ( return-x ) ;; Error ( let ((x 5)) ( return-x )) ;; OK ( defun foo (x) ( return-x )) ( foo 5) ;; OK

Fun with dynamic scoped racket 20 ( require plai/ dynamic ) ( define x 123) ( define ( getx ) x) ( define ( bar1 x) ( getx )) ( define ( bar2 y) ( getx )) ( test ( getx ) 123) ( test ( let ([x 456]) ( getx )) 456) ( test ( getx ) 123) ( test ( bar1 999) 999) ( test ( bar2 999) 123)

21 ( require plai/ dynamic ) ( define ( foo x) ( define ( helper ) (+ x 1)) helper ) ( define x 123) ( test (( foo 0)) 124) ;; and * much* worse: ( define ( add x y) (+ x y)) ( test ( let ([+ *]) ( add 6 7)) 42)

Dynamic versus Lexical Scope Leaving aside the question of whether dynamic scope is good for your mental health, what matches our original substitution semantics? Remember this was supposed to be an optimization Consider our nemesis again, under the slow substitution evaluator: 22 ( trace eval ) ( run '{ with {x 3} { with {f { fun {y} {+ x y }}} { with {x 5} { call f 4}}}})

Dynamic versus Lexical Scope 2017-03-02 Leaving aside the question of whether dynamic scope is good for your mental health, what matches our original substitution semantics? Remember this was supposed to be an optimization Consider our nemesis again, under the slow substitution evaluator: 22 ( trace eval ) ( run '{ with {x 3} { with {f { fun {y} {+ x y }}} { with {x 5} { call f 4}}}}) 1. Note how bad the last example gets: you basically cannot call any function and know in advance what it will do.

As an example what you lose, dynamic scope does not allow using functions as objects: 23 ( require plai/ dynamic ) ( define ( kons x y) ( lambda (n) ( case n [( first ) x] [( second ) y] [ else ( error " oops ")]))) ( define my- pair ( kons 1 2)) ( test ( my-pair 'first ) 1)

As an example what you lose, dynamic scope does not allow 2017-03-02 using functions as objects: 23 ( require plai/dynamic ) ( define ( kons x y) ( lambda (n) ( case n [( first ) x] [( second ) y] [ else ( error " oops ")]))) ( define my-pair ( kons 1 2)) ( test ( my-pair 'first ) 1) 1. Subsitution caching is a very important optimization, which without it lots of programs become too slow to be feasible, so you might claim that you re fine with the modified semantics...

In a dynamic scoped language, you don t even know if this is valid code until run time: 24 ( define ( foo ) x) Racket uses the same rule for evaluating a function as well as its values This makes dynamic scope more powerful/dangerous: 25 ( require plai/ dynamic ) ( define ( add x y) (+ x y)) ( let ([+ -]) ( add 1 2))

In a dynamic scoped language, you don t even know if this is 2017-03-02 valid code until run time: 24 ( define ( foo ) x) Racket uses the same rule for evaluating a function as well as its values This makes dynamic scope more powerful/dangerous: 25 ( require plai/dynamic ) ( define ( add x y) (+ x y)) ( let ([+ -]) ( add 1 2)) 1. When this is evaluated in a dynamically-scoped language, we do get a function as a result, but the values bound to x and y are now gone! 2. Lisp-2 s uses a different name-space for functions

Many so-called scripting languages begin their lives with dynamic scoping. In languages without first-class functions, problems of dynamic scope are not as obvious. For example, bash has local variables, but they have dynamic scope: 26 x=" the global x" print_ x () { echo " The current value of x is \"$ x\""; } foo () { local x="x from foo "; print_x ; } print_x ; foo ; print_x

Many so-called scripting languages begin their lives with 2017-03-02 dynamic scoping. In languages without first-class functions, problems of dynamic scope are not as obvious. For example, bash has local variables, but they have dynamic scope: 26 x=" the global x" print_x () { echo " The current value of x is \"$ x\""; } foo () { local x="x from foo "; print_x ; } print_x ; foo ; print_x 1. The main reason for starting with dynamic scope, as we ve seen, is that implementing it is extremely simple (no, nobody does substitution in the real world! (Well, almost nobody...)).

Perl began its life with dynamic scope for variables that are declared local : 27 $x=" the global x"; sub print_ x { print " The current value of x is \"$ x \"\ n"; } sub foo { local ($x); $x="x from foo "; print_x ; } print_x ; foo ; print_x ; local and some builtin variables still have dynamic scope, but my introduces lexical scope. 28 my $x=" the global x"; sub print_ x { print " The current value of x is \"$ x \"\ n"; } sub foo { my $x="x from foo "; print_x ; } print_x ; foo ; print_x ;

in Emacs 24, emacs lisp acquires optional lexical scope. 29 ; -*- mode: emacs- lisp ; lexical- binding: t-*- ( let ((x 3)) ( let ((f ( lambda (y) (+ x y)))) ( let ((x 5)) ( funcall f 4)))) Early versions of python ( = 2.1) printed 1 for; 30 x = 1 def f1 (): x = 2 def inner (): print inner () f1 () x

Note that this is not dynamic scope either, since the following fails to compile 31 def orange_ juice (): return x*2 def foo (x): return orange_ juice () foo (2) Where the following works, in (dynamic) racket 32 ( require plai/ dynamic ) ( define ( orange- juice ) (* x 2)) ( define ( foo x) ( orange-juice )) ( foo 2)

Another example, which is an indicator of how easy it is to mess up your scope is the following Ruby 1.8 bug running in irb : 33 % irb irb ( main ) :001:0 > x = 0 => 0 irb ( main ) :002:0 > lambda { x x}. call (5) => 5 irb ( main ) :003:0 > x => 5

There are some advantages for dynamic scope. Dynamic scope makes it easy to have a configuration variable easily change for the extent of a calling piece of code. Optional dynamically scoped variables are useful the problem of dynamic scoping is that all variables are modifiable. It is sometimes desirable to change a function dynamically (for example, see Aspect Oriented Programming ), but if all functions can change, no code can be reliable. It makes recursion immediately available for example, dynamic scope gives us easy loops: 34 { with {f { fun {x} { call f x }}} { call f 0}}

Controlled Dynamic Scope racket provides parameters for dynamic scopy, internally used by plai/dynamic 35 ( define location ( make- parameter " here ")) ( define ( foo ) ( location )) ( parameterize ([ location " there "]) ( foo )) ( foo ) ( parameterize ([ location " in a house "]) ( list ( foo ) ( parameterize ([ location " with a mouse "]) ( foo )) ( foo ))) ( location )