Local, Higher-Order, and Dispatched Procedures
Simple Local (Nested) Procedures
Nested functions can access outside functions.
def saveData(f : File) = { def wr(s : String) = { writeStringToFile(f, s) } def wrInt(x : Int) = { wr(integerToString(x)) } wr("<body>") wr("This is my page.") wrInt(2008) wr("</body>") }
Here function 'wr' accesses both 's' and 'f'
- note that 'f' can be at different places relative to stack top
How does code for 'wr' access 'f' ?
Several alternative solutions:
- pass extra 'static link' for the enclosing function ('saveData' for 'wr')
- chase links when
- maintain global display array of most recent procedure of static nesting depth 'i'
- avoids chasing links
- make enclosing function arguments explicit: lambda lifting
- modifies both function definition and function call
Previous example after lambda lifting is simply:
def wr(s : String, f : File) = { writeStringToFile(f, s) } def wrInt(x : Int, f : File) = { wr(integerToString(x), f) } def saveData(f : File) = { wr("<body>", f) wr("This is my page.", f) wrInt(2008, f) wr("</body>", f) }
Global Function Pointers
To pass global functions, we simply pass their addresses
We need to know the calling convention for all function values that can be called
var exlaim = "!!!" def simpleNotify(msg : String) = { println(msg) } def noisyNotify(msg : String) = { beep(1024) popUp(msg) beep(2048) popUp(msg + exclaim) beep(512) } def alarm(time : Int, notify : msg -> unit) = { if (currentTime==time) { alert("Wake up!") } } alarm(0700, simpleAlert) alarm(0800, noisyAlert)
When 'notify' is called in 'alarm', it will be passed string argument on stack
- it knows how to access global variables
This works in e.g. C and Pascal
Higher-Order Functions
None of the previous techniques works when nested functions are returned as arguments
Consider example:
def greet(firstLine : String, message : String) = { println(firstLine) println(message) } def getGreeter(title : String) : String => unit = { var myLine = "Dear " + title def myGreeter(name : String) = { println(myLine + " " + name) myLine = myLine + " " println("How are you doing today?") } myGreeter } val g = getGreeter("Mr") val g1 = getGreeter("") g("Santa") g1("Schmutzli")
When g returns a function, how can this function know what the value of 'myLine' is?
- 'myLine' value must remain even after 'getGreeter' returns
- cannot allocate frame for getGreeter on stack, must use heap
- to invoke 'g' and 'g1' must obtain both code pointer and frame pointer
A pair of code and frame pointer is called closure
Higher-order function can be encoded as an object with 'apply' method (as in Scala)
- closure is then an object with one method
More details:
- Tiger book, Chapter 15
- Simon L. Peyton Jones, The Implementation of Functional Programming Languages, Prentice-Hall, 1987
Dynamic Method Dispatch in Object-Oriented Languages
How do we know which method is invoked here in the following:
var x : Object; println(x.toString())
The invoked method depends on the dynamic type of 'x'.
We can view the method to execute as object field (of function type)
Simple implementation: store function pointers in the object
But all objects have same code pointers
- introduce a table for each dynamic type, Virtual method table (vtable)
- dynamic type of object give a pointer to corresponding vtable
How can we call methods in supertypes?
- for single inheritance, we arrange methods in sequence, from superclass to subclass
- superclasses ignore entries from subclass
More information:
- Tiger book, Chapter 14