CSC/ECE 517 Fall 2011/ch1 1c cm: Difference between revisions
mNo edit summary |
No edit summary |
||
Line 33: | Line 33: | ||
Here is the same example in C#. | Here is the same example in C#. | ||
<pre> | |||
public Func<int, int> addGen(int n) | public Func<int, int> addGen(int n) | ||
{ | { | ||
Line 44: | Line 44: | ||
Console.WriteLine(add5(6)); // 11 | Console.WriteLine(add5(6)); // 11 | ||
Console.WriteLine(add10(6)); // 16 | Console.WriteLine(add10(6)); // 16 | ||
</pre | </pre> | ||
And again in Ruby | And again in Ruby | ||
<pre> | |||
def addGen(n) | def addGen(n) | ||
Proc.new { |x| x + n } | Proc.new { |x| x + n } | ||
Line 58: | Line 58: | ||
puts add5(6) # 11 | puts add5(6) # 11 | ||
puts add10(6) # 16 | puts add10(6) # 16 | ||
</pre | </pre> | ||
===== First-class functions ===== | ===== First-class functions ===== | ||
Line 68: | Line 68: | ||
closures capture variables referenced at the time they are created and the lifetime of those captured variables is extended to at least as long as the lifetime of the closure | closures capture variables referenced at the time they are created and the lifetime of those captured variables is extended to at least as long as the lifetime of the closure | ||
<pre> | <pre> | ||
function multipleClosures() { | function multipleClosures() { | ||
Line 84: | Line 83: | ||
alert(closures[1]()); // 1 | alert(closures[1]()); // 1 | ||
</pre> | </pre> | ||
<pre> | <pre> | ||
public Func<int>[] multipleClosures() | public Func<int>[] multipleClosures() | ||
Line 100: | Line 97: | ||
Console.WriteLine(closures[1]()); // 1 | Console.WriteLine(closures[1]()); // 1 | ||
</pre> | </pre> | ||
<pre> | <pre> | ||
def multipleClosures | def multipleClosures | ||
Line 115: | Line 110: | ||
puts closures[1].call # 1 | puts closures[1].call # 1 | ||
</pre> | </pre> | ||
==== Methods ==== | ==== Methods ==== |
Revision as of 17:29, 5 September 2011
Introduction
Previous 517 classes have written about closures as implemented in several o-o languages, but this topic asks you to identify practical advantages of using closures over using methods. Give examples of programming problems that can be solved more concisely, more extensibly, or more elegantly using closures than using methods. Make sure the examples are organized into a coherent progression.
Closures vs. Methods
introduction of the battle of functional versus object oriented history of the closures with scheme and future languages history of OO methods with C++ and other languages
Definitions
Closures
A closure is a block of executable code together with a reference to the local variables from the environment in which it was created. The local variables are captured by the closure, and their lifetime is extended throughout the lifetime of the closure. Closures can be created by defining a function within the body of another function as the following example illustrates. Note that the scope of the parameter n
is limited to the inside of the addGen
function. However, because addGen
returns a closure, that closure has access to n
whenever it is evoked.
An example of a function returning a closure in JavaScript.
function addGen(n) {
return function (x) {
return x + n;
};
}
var add5 = addGen(5);
var add10 = addGen(10);
alert(add5(6)); // 11
alert(add10(6)); // 16
Here is the same example in C#.
public Func<int, int> addGen(int n) { return x => x + n; } var add5 = addGen(5); var add10 = addGen(10); Console.WriteLine(add5(6)); // 11 Console.WriteLine(add10(6)); // 16
And again in Ruby
def addGen(n) Proc.new { |x| x + n } end add5 = addGen(5) add10 = addGen(10) puts add5(6) # 11 puts add10(6) # 16
First-class functions
Closures are closely related to first-class functions, because typically a closure is a first-class function. A first-class function is a function that can appear anywhere in a program that other first-class values (such as a number or a string) can appear. Namely, a first-class function can be assigned to a variable, passed as an argument to another function, and returned as the return value from a function. Closures are first-class functions that capture the local variables (or free variables) from the site of their definition.
Execution-time environment contains free variables
closures capture variables referenced at the time they are created and the lifetime of those captured variables is extended to at least as long as the lifetime of the closure
function multipleClosures() { var i = 0; return [ function () { return ++i; }, function () { return --i; } ]; } var closures = multipleClosures(); alert(closures[0]()); // 1 alert(closures[0]()); // 2 alert(closures[1]()); // 1
public Func<int>[] multipleClosures() { int i = 0; return new Func<int>[] { () => ++i, () => --i }; } var closures = multipleClosures(); Console.WriteLine(closures[0]()); // 1 Console.WriteLine(closures[0]()); // 2 Console.WriteLine(closures[1]()); // 1
def multipleClosures i = 0 [ Proc.new { i = i + 1 }, Proc.new { i = i - 1 } ] end closures = multipleClosures puts closures[0].call # 1 puts closures[0].call # 2 puts closures[1].call # 1
Methods
Object-oriented programming dates back to at least the middle 1960's. One of the fundamental principals of objects is the notion of encapsulation. One of the earliest descriptions of the methodology, dates back to a 1966 paper describing the SIMULA, a language designed to ease simulation of large systems. The SIMULA language used classes to encapsulate coupled procedures and data into an object, “an object is a self-contained program (block instance) having its own local data and actions defined by a 'class declaration'” [pg 6, Simula]. This 50 year-old concept of fields and procedures is central to the design of today's object-oriented languages such as Smalltalk, Java and C++. The method will be defined using the following criteria:
- An object as first-class data which allows binding of
- finite set of static or instance data (commonly referred to as fields) created by the programmer which hold the state of the object
- procedures or actions modifying the data to change the state of the object
- an ability to access methods across objects (data sharing)
Instance or static class functions
Our definition of a method requires functions which can be bound to a first-class object. All object-oriented languages provide this construct. We can create objects and pass those objects around to be executed by other objects or in the global environment.
Execution-time environment contains instance or static variables
The second point of the method definition is that the we can bind static or instance data to our first-class data object. All object-oriented language also provide this construct.
Object-oriented vs. Functional Programming - Benefits of Closures
Where the benefits of methods have been demonstrated in object-oriented systems, closures are finding their way into languages which haven't had them in the past, like C++ and Java. The C++ proposal for extending the language lists some of the benefits of supporting closures in the traditionally OO language, including notational simplicity, code sharing of common algorithms, and generality [Lamba]. The IBM developerWorks site article lists the some common usages of closures as customization, iterating across collections, and managing resources and enforcing policy.
We will broadly break these down into examples where Closure provide a clean implementation.
- Generic algorithm - iterating across collections
- Callbacks - customizing behavior
- Managing resources and enforcing policy (this is new)
Generic Algorithms
- basic algorithm syntax
- parallel programming (e.g. the MapReduce problem)
Basic Algorithms
The C++ proposal makes light of the fact that closures provide the ability to inject code into generic routines without the programmer having to provide adapters. "The lack of a syntactically light-weight way to define simple function objects is a hindrance to the effective use of several generic algorithms in the Standard Library [C++]
The classic for_each template algorithm in C++ is called using the following syntax:
template <class InputIterator, class OutputIterator, class UnaryFunction> OutputIterator transform(InputIterator first, InputIterator last, OutputIterator result, UnaryFunction op); { for ( ; first != last; ++first, ++result ) *result = op(*first); return result; }
Because C++ does not have Closures, the fourth parameter requires either a named function or an object which supports "operator ()".
// static function int multiply_2(int x) { return x * 2; } class multiply_it { int multiplier; multiple_it(int x) : multiplier(x) {} int operator()(int x) const { return x * multiplier } } std::vect test(3,10); std::vect output(3); std::transform( test.begin(), test.end(), output.begin(), multiply ) std::transform( test.begin(), test.end(), output.begin(), multiply_it(2) )
If we use the static function and we want to change the multiply function, we either have to create a new named function or create a global variable and make sure the global variable is set correctly before the function is executed. Using the function object, we have can easily change the multiplier, but we were forced to create and manage a new class specifically for the integer multiply.
Consider the same code using Ruby and a Closure where the each function where the developer can pass a code block.
test = [10,10,10] output = test.collect { |x| x * 2 }
The Closure is syntactically much cleaner.
Parallel Programming - Map Reduce
Google popularized the technique of Map Reduction with its search algorithm that simultaneously runs the same search algorithm on multiple threads and combines the results to produce a single answer. Within a programming environment Closures allow parallelized reduction.
Callbacks
Callbacks are often cited as the most desirable application of Closures because the environment around the callback is initialized without programmer intervention. In languages that don't provide Closures, interfaces, fields, or even parameters may have to be added to provide thunking and state information to object being called.
There are several forms of callbacks that can demonstrate this capability.
- GUI behavior injection
- generic exception handling
GUI behavior Injection
Behavior injection is used heavily in Javascript libraries where they allow the creation of generic libraries which can be easily extended using a provided function.
Below is an example using the JQuery bind method to provide some capability to an event framework.
$("p").bind("mouseclick", function(event) { $(this).Toggleclass; } )
The Closure is created in the context of each selected DOM object (in this case those objects with type of "p"). The anonymous function is then called when the "mouseenter" event fires. The context of the this pointer is unique to each DOM object because the Closure captured the environment (object) at the time of function creation.
Consider the same callback behavior in a language like Java which does not provide Closures.
public class MouseClick implements MouseListener { [... class implementation ...] public void mousePressed(MouseEvent e) { this.toggleClass; } } window.addMouseListener( [instance of MouseClick class] );
We must first define the interface or function which will provide the callback. It must provide the context under which the callback will execute. If we want access to more of the environment we must make provisions in the MouseClick class by adding state variables or pointers to the environment under which it was instanced.
Generic Exception Handling
It can be useful in exception handling because the context of the executing is maintained in the Closure. We had a guarantee that the objects referenced will still be in scope when the exception is taken.
Resource Management / Policy Enforcement
Closures allow the creation of policies within an application or class. Closures allow injection of user specified code within the generic policy.
- maintaining resources
- resource policy
Maintaining Resources - Simple
This often cited example shows the encapsulation of file base policy using a Closure.
file = fopen(local_filename, 'w') fwrite(file, doc); fclose(file)
File.open(local_filename, 'w') { |f| f.write(doc) }
The Closure is passed to the open method call along with the environment that contains the doc variable.
Resource Policy
The IBM developerWorks site gives an excellent example of a database policy in Ruby.
def do_transaction begin setup_transaction yield commit_transaction rescue roll_back_transaction end end
The user of this policy would be able to call the do_transaction method, providing a Closure that will execute between steps of setup and commit.
Topical References
External Links
http://martinfowler.com/bliki/Closure.html
http://gafter.blogspot.com/2007/01/definition-of-closures.html
http://jibbering.com/faq/notes/closures/
http://onestepback.org/articles/invitationtoruby/reason4.html
http://www.skorks.com/2010/05/closures-a-simple-explanation-using-ruby/
http://www.ibm.com/developerworks/java/library/j-cb01097/index.html
http://samdanielson.com/2007/9/6/an-introduction-to-closures-in-ruby