CSC/ECE 517 Fall 2011/ch1 1c ka
Introduction
This article deals with understanding Closures and Methods (mostly in Dynamically typed languages) and provides a pragmatic comparison between closures and methods. Methods in Statically typed languages are procedures or functions that perform a specific task. These methods also have a local scope, meaning all the variables and constants defined within a method are visible only inside it. The statically typed languages(such as C,C++) and some dynamic languages (such as Ruby) provide Object orientation through Classes and Objects, methods acting as a means to provide access to the Objects' state. On the other hand, some dynamically typed languages (such as JavaScript) have utilized this property of methods to support encapsulation of data and hence provide an Object Oriented programming environment. Looking at the ways in which methods have been exploited, they can be applied to many different problem domains to implement the solutions elegantly. But there are certain problem domains, where methods are not enough to provide a flexible,elegant implementation. When we look at other solutions to these problems, we stumble upon Closures as the best possible alternatives. Closures are blocks of code that can be passed around like objects, which have the property that they are "bound" to the context (or state of the program) in which they were created. Hence Closures are not dependent on the presence of Objects nor on functions in which they were created, as opposed to methods whose existence is tied to life time of Objects to which they belong to.
Overview
Methods
In Object Oriented Programming, a Method is a Subroutine (or Procedure or Function) associated with a class. They define the behavior exhibited by the associated Class instances at runtime. Methods defined within a Class are said to be "bound" to the class.[1] The binding can be either Static or Dynamic binding. Dynamic binding provides polymorphic behavior, where in the decision about which code to be executed is made at run time, not at compile time. Methods have access to data stored in an instance of the class they are associated with and are thereby able to control the state of the instance. But unlike Closures, methods have access to this state only as long as the object (class instance) exists. [2]
Following is a Ruby example that shows how methods can be used to statically bind to the state of an object:
class Myclass def initialize(value) @balance = value end def get_balance @balance end def set_balance(value) @balance = value end end obj = Myclass.new(10) puts obj.get_balance #prints 10 obj.set_balance(20) puts obj.get_balance #prints 20
Here class Myclass
is defined with an instance variable @balance
. An object of this class is created and stored in obj
. The method get_balance
is used to retrieve the state of this object and the method set_balance
is used to manipulate the state of the object. Here the methods are statically bound to the object obj
and have access to its state as long as this object is in existence. [3]
In some dynamic languages such as JavaScript, this exact same behavior is supported only by means of methods. Consider the example:
function Myclass(value) { var interest = 1.25; //Private variable this.balance = value*interest; //Instance variable this.get_balance = function(){ return this.balance }; this.set_balance = function(value){ this.balance = value*interest; } } var obj = new Myclass(10); alert(obj.get_balance()); //Outputs 12.5 obj.set_balance(20); alert(obj.get_balance()); //Outputs 25
In languages like C++ and Java, methods can be modified to provide dynamic binding to the objects. This means that, same functionality can be reused without making changes to any code dynamic binding. Following C++ example illustrates dynamic binding of methods to objects and how useful it is:
class Vehicle //Base class defines some common functions of all the Vehicles { private: int tires; int doors; int steering; public: virtual void accelerate(); //Provides the dynamic binding }; class Car: public Vehicle { private: //Car specific data public: virtual void accelerate(); //Implements accelerate() function specific to Cars };
In order to use this class, we cab write the following code:
//Exposed as a library functions.lib void accelerate(Vehicle *base) { base->accelerate(); } //This will link against functions.lib int main() { Car *obj = new Car(); accelerate(obj); //Calls the function defined in functions.lib }
Suppose we have to write another application that uses functions.lib and Vehicle class, say for Truck, then we'll write:
class Truck:public Vehicle { private: //Truck specific data public: virtual void accelerate(); }; int main() { Truck *obj = new Truck(); accelerate(obj); //Calls the function defined in functions.lib }
Notice here that accelerate()
is defined only once in functions.lib, but invokes the correct class' accelerate()
function dynamically based on which class instance it has been passed.
This behavior is commonly referred to as polymorphism. This is something that cannot be supported only by using Closures. This example also illustrates how methods can be used to implement libraries that enable code reuse.
Instance methods
Instance methods are bound to the first class object and have access to the variable and other data of the object. The instance methods receive an implicit 'this' pointer which points to the object on which the method was called.
Static methods
Some of the methods do not require the class to be instantiated for them to be called. They do not have access to the data for the class instance nor do they have access to the implicit pointers such as 'this'.
Special methods
Methods such as constructors and destructors are used for creation and removal of objects respectively. Almost all OO languages provide constructs for operator overloading which allow to customize behavior of operators for a class. Conversion operator methods allow the compiler to provide an object of a different class. Copy-assignment methods override the assignment operator for the objects of a class.
Accessor methods
Objects have some data that is not visible to the outside world. These data items are called private items and are visible only to the methods of the class. This feature is required to provide encapsulation. Accessor methods are used to provide access to these variables (state) from other parts of the program.
Closures
The concept of closures has been around for a long time. Closures were first implemented fully as a language feature in 1960 in the programming language Scheme. A closure is a function value which is treated as an object and which contains references to the variable names in the lexical environment which was active when the closure was evaluated. In simple terms closures can be thought of as blocks of code which can be passed around as objects and which have access to the context in which they were first declared. This means that the state of the program at the time the Closure was created can be saved and and reused in any part of the program. This allows you to separate out the control structures, logical operators, loop operators etc. from the details of how they will be used. [4] [5] [6]
In Ruby, for example, closures can be defined as lambda functions or as a new instance of Proc, as shown the following code snippet.
def test(block) block.call(20) end b = 10 block1 = Proc.new {|a| a+b} #Creates a Closure puts block1.call(13) #Output : 23 puts test(block1) #Output : 30 b = 20; puts test(block1) #Output : 40
Here we are creating a Closure using Proc.The Closure, stored in block1 has got access to the lexical context (in this case variable b). Hence when we pass block1
to the method test
, it will output 20 + value of b (10) = 30. But when we update the value of b
(to 20) in the context, the same is reflected in the next call to test
. [7]
Here's another example using JavaScript:
function test(arg){ alert('test '+arg(20)); } var b = 10; var block = function(a){ return a + b; } ; //Creates a Closure alert('block '+block(13)); //Output : 23 test(block); //Output : 30 b = 20; test(block); //Output : 40
Practical significance of Closures
Dynamic Object Creation
In Object oriented languages, classes are used to represent real world objects. This is possible because, classes have their private members,private and public functions,etc which enable object orientation. Closures can support similar functionality with much more ease, without having to create Classes,Objects,etc. Hence Closures form one of the most handy yet powerful tools of dynamic languages. [8] [9] For Example,
myobj = (function () { var privatefunction = function () { alert('hello'); } var privateVariable = "Private" return { publicfunction : function () { privatefunction(); } } })(); //One way to access the object is : myobj.publicfunction();
The myobj closure contains a private function and a private variable which cannot be accessed by anybody outside myobj. It also provides an external interface using the function publicfunction(). You can simply access the method publicfunction() without actually instantiating any class or object.
This feature is useful in situations where one off objects are required. If the programming language does not have the ability to define closures, the programmer would need to write a separate class for such objects. Following example achieves the same goal as the example above, by defining a class in Java.
Class MyObj { private void privatefunction() { alert('hello'); } private String privateVariable = "Private" public void publicfunction() { privatefunction(); } };
We then need to instantiate the class using the new
operator in Java, which creates an instance and assigns it to myObject
.
MyObj myObject = new MyObj(); myObject.publicfunction();
This class needs to be compiled separately and imported where the object of this class is required. This is not convenient in cases where one off instance of the class is required to perform a certain operation. Closures come in handy in such situations.
Provide APIs
Closures can be used to implement interfaces. When developing Application Programming Interface (API) libraries, we will have to often provide an interface definition and a set of functions that can be called upon it. Closures are perfectly suitable for this kind of implementation. [10] E.g Suppose you are writing a JavaScript library for providing a set of Calendar APIs. The interface could be defined this way:
Calendar = (function () { var calendarName = "file.db" //Unique Identifier for this Calendar Object return { addCalendarEntry : function (entry) { //Operates on Calendar file.db (as specified by calendarName param) } } })();
A user of this API library could use it as:
Calendar.addCalendarEntry(calendarEntry);
without worrying about its initializtions, instantiation,etc.
The same result can't be achieved by using methods alone. One way to do this could be -- defining an interface (abstract class), and exposing APIs from it, which will involve much longer complex code.
Event Handling and Callbacks
Closures can be best used for Event handling in case of Asynchronous calls. [11]This is especially true about AJAX and Web Service calls. E.g.
function asyncAPI(uniqueId, user_cb) { function callback(result) // This will be invoked with "result" once httpAsyncCall() completes { user_cb(result,uniqueId) } jsObj.httpAsyncCall(callback) //Actual Client-server call }
In this case, the function asyncAPI() immediately returns after making the httpAsyncCall to the server. Later, when httpAsyncCall() finishes, it will invoke the callback() function passed to it. But because of closures, the callback will continue to have access to the values of uniqueId and user_cb, even after asyncAPI returns. Hence it can invoke the appropriate user_cb() function once it gets the result. Compare this to the implementation without using Closures):
function callback(result) // This will be invoked with "result" once httpAsyncCall() completes { var cbobj = global_array.pop() cbobj.user_cb(result,cbobj.uniqueId) } function asyncAPI(uniqueId, user_cb) { cbobj = {"user_cb" : user_cb, "uniqueId" : uniqueId} //Create a callback object - cbobj out of the input params global_array.push(cbobj) //Store the object on to a Global stack jsObj.httpAsyncCall(callback) //Actual Client-server call }
Here we have to manage the Global stack in order to maintain callback
and uniqueId
values. Also we will need a whole new framework if we have to support back to back calls to asyncAPI
.
Design Patterns
Design patterns are templates of code which can be reused in design of systems. These are reusable solutions which provide a skeleton to solve repeatedly occurring problems. These patterns can be applied in the Object Oriented design of a system to show relationships between the objects. More information about design patters can be found here or here. Closures can be used to implement design patterns. The following example shows a closure for Factory design pattern.
function showHelp(help) { alert('showHelp'+help); } function makeHelpCallback(help) { //Factory function //Make some initializations return function() { showHelp(help); }; } var rubyHelp = makeHelpCallback("Ruby"); var jsHelp = makeHelpCallback("JavaScript"); //These handles can be passed to anybody and later invoked like: rubyHelp(); //Will pop up "help" specific to Ruby jsHelp(); //Will pop up "help" for JavaScript
The above example shows how simple and easy it is to write the factory design pattern using closures. The return
statement in the function makeHelpCallback
returns a function. This function can be used as an object and passed around as parameter to other methods. Also, the methods inside the return block are the members of the returned object.
Following example shows an implementation of the factory design pattern in Java. To example defines factory methods to produce different kinds of balls.
interface BallFactory { public Ball getBall(); } class FootballFactory implements BallFactory { public Ball getBall() { return new Football(); } } class SoccerballFactory implements BallFactory { public Ball getBall() { return new OSXButton(); } } interface Ball { public String type(); } class Football implements Ball { public String type() { return("I'm a Football ball"); } } class Soccerball implements Ball { public String type() { return("I'm a Soccer ball"); } } class Producer { public Producer(BallFactory factory) { Ball ball = factory.getBall(); System.out.println(ball.type()); } } public class ProducerMain { public static void main(String[] args) { new Producer(new SoccerballFactory); } }
Looking at the example in Java, we get an idea how cumbersome it is to use the Factory design pattern in that language. Similar amount of code is required to implement factory pattern in other OO languages such as C# and C++. Here, we had to define an interface for the factory patterns. This interface is implemented by the factory classes FootballFactory
and SoccerballFactory
. We also need to define an interface for the object which is to be produced, that is the Ball
interface. This interface is implemented by the classes Football
and Soccerball
. The constructor of the Producer
class accepts the type of the factory and then calls the getBall()
method of that factory. This examples shows the benefit of closures in implementing design patterns.
Pre-processing computations
Closures can be used for pre-processing and storing the results of complex computations.
Dictionary = (function () { //does sorting of the entire word list sortWordList(); return { search : function (word) { //Searches for the given "word" in the sorted list of words } } })();
Here the function sortWordList() and the surrounding code (except the return statement) is executed the moment this JavaScript file is included (or parsed). Since sorting the entire word list could be a time consuming operation, this takes place before any request for searching arrives. Once this is done, anytime a call to search() can be executed efficiently on the already sorted list available to it. There's no easy way to achieve this without using Closures. This functionality is not possible with methods.
Continuation passing style
Continuation Passing Style (CPS) is a style of programming used in functional programming languages in which no function returns a value. Instead, the languages which support continuations accept an explicit parameter which is a continuation function that is applied to the result of the called function. The following example taken from here demonstrates the use of continuation passing style in Scheme programming language to calculate a Pythagorean triplet.
(define (pyth& x y k) (*& x x (lambda (x2) (*& y y (lambda (y2) (+& x2 y2 (lambda (x2py2) (sqrt& x2py2 k))))))))
Here, k
is the extra parameter passed to the function. This is the continuation function. The result of a function is passed to the continuation function which then passes its result to the next continuation function and so on.
Deferred execution
Deferred execution allows you to control when a block of code will be executed. This is required in situations where the execution of code could potentially get lengthy or in cases where execution of the code is not always necessary. In the example given below, a block of code is passed to the function which then converts it into a closure and saves it for later execution. The closure is actually executed when the saved closure is invoked using the .call
method on the closure. The code is written in Ruby programming language which uses the lambda construct to define closures.
class Greeter def initialize(name) @saved = lambda {puts "Welcome, #{name}!"} end def greet() @saved.call end end grtr = Greeter.new "Harry" puts "Deferred Execution" grtr.greet # Outputs "Welcome, Harry!"
Some Disadvantages of using closures
- Closures capture variables and in some languages (such as JavaScript), chains of scope/contexts. As long as closures are active, this memory cannot be garbage collected. Hence excessive usage of Closures is not recommended especially in limited memory environments.
- Closures can also lead to memory leaks due to formation of circular references. This is not an issue in purely garbage collection systems. But in Garbage collection with reference counting, the system fails to identify a circular reference, hence leading to memory leaks. [12]
References
3. What is an Instance Variable
4. Closures - A simple explanation using Ruby by Alan Skorkin
7. Understanding Procs, Blocks and Methods
8. Closures - Why are they so useful
9. On Scoping, Closures, Methods and Messages
10 Benefits of closures primarily for PHP
12 Mozilla Developer Network - Closures
13 Taking advantage of closures in ECMA script
14 Blocks and Closures in Ruby