CSC/ECE 517 Fall 2012/ch1 1w32 cm

From Expertiza_Wiki
Jump to navigation Jump to search

CLOSURES FOR STATICALLY TYPED LANGUAGES

INTRODUCTION

A closure is a function or reference to a function which has a referencing environment — a table storing a reference to each of the non-local variables (also called free variables) of that function. A closure allows a function to access the non-local variables even outside its lexical scope. Closures can typically be treated like any other programming language objects, e.g. they can be stored in variables, passed to functions, and so on.

A closure can be thought of as the marriage between a function and the environment in which it was declared within. Exactly what this marriage looks like at an implementation level differs depending on the language.

Copyrighted Image by Derek Greer, JavaScript Closures Explained, February 2012 [1]


Here is a closure in Python:

def outsideFunc(a):
  def enclosedFunc(b):
     # a is "closed" in the definition of enclosedFunc
     return a + b

 return enclosedFunc

first = outsideFunc(4)
second = outsideFunc(2)

first (5) # returns 9
second (1) # returns 3


Implementation of closures in python is done by function calls. Here, the call to outside_func creates a binding for a that is referenced inside the function enclosedFunc. Each call to outsideFunc creates a new instance of this function, but each instance has a link to a different binding of x. The example shows the closure of a being used to eliminate either a global or a constant, depending on the nature of a. This is the simplest non-trivial example using closures.

IMPORTANCE OF CLOSURES

Some uses of closures:

1. They are very usable for making code more clear and readable. And as we all know clean readable short code is a code that is easier to debug and contains fewer bugs.

For example in Java,

    button.addActionListener(new ActionListener() {
        @Override public void actionPerformed(ActionEvent e) {
            System.out.println("Pressed");
        }
    });

This would be replaced (if Java had closures) with:

button.addActionListener( { System.out.println("Pressed"); } );


2. Closures are used to implement continuation-passing style, and in this manner, hide state. Constructs such as objects and control structures can thus be implemented with closures. Closures typically appear in languages in which functions are first-class values—in other words, such languages allow functions to be passed as arguments, returned from function calls, bound to variable names, etc., just like simpler types such as strings and integers.

For example, consider the following Scheme function:

 Return a list of all students  with at least the threshold grade.
(define (grade threshold)
  (filter
    (lambda (student)
      (>= (student-grade student) threshold))
    student-list))>

In this example, the lambda expression (lambda (student) (>= (student-grade student) threshold)) appears within the function grade. When the lambda expression is evaluated, a closure consisting of the code for the lambda expression and a reference to the threshold variable are created .The reference is a free variable inside the lambda expression.

The closure is then passed to the filter function, which calls it repeatedly to determine which books are to be added to the result list and which are to be discarded. Because the closure itself has a reference to threshold, it can use that variable each time filter calls it. The function filter itself might be defined in a completely separate file.

Here is the same example rewritten in JavaScript, another popular language with support for closures:

// Return a list of all books with at least 'threshold' copies sold.
function bestStudents(threshold) {
  return studentList.filter(
      function (student) { return student.grade >= threshold; }
    );}

The function keyword is used here instead of lambda, and an Array.filter method[10] instead of a global filter function.

3. Using Closures For Privacy:

With closures,private members that are shielded from the outside world are available:

(function(higherLevelFunc){
   function myPrivateDisplayFunction(name) {
      return name;
   }
   higherLevelFunc.display = higherLevelFunc(name) {
      console.log(myPrivateDisplayFunction(name));
   }
 })(window);

In this way private members can be protected from the outside world.

At the top level, the function object is an anonymous function:

(function(higherLevelFunc){
})(window);

This anonymous function can be called right away. It is passed to a global context (window in this case) so the public function can be “exported”, but inner details are hidden. Because the function myPrivateDisplayFunction is a nested function, it exists only within the scope of the closure; so it can be used anywhere inside this scope and not elsewhere JavaScript will hold a reference to the private function for use inside the multiply function, but myPrivateMultiplyFunction cannot be accessed outside of the closure. Consider:

display("Cheng") // => Cheng
myPrivateDisplayFunction("Cheng") // => ReferenceError: myPrivateDisplayFunction is not defined

The closure allows to define a function for private use, while still allowing control over what the rest of the world sees.

4. Because closures delay evaluation i.e., they do not "do" anything until they are called so they can be used to define control structures. For example,all Smalltalk's standard control structures, including branches (if/then/else) and loops (while and for), are defined using objects whose methods accept closures. Users can easily define their own control structures also. In languages that allow assignment, multiple functions can be produced that close over the same environment, enabling them to communicate privately by altering that environment.

CLOSURES IN DYNAMICALLY TYPED LANGUAGES

The following are examples of closure usage in some of the dynamically typed languages.

Perl

The following is a make_adder() function, in which the returned anonymous function contains a reference to a lexical variable outside the scope of that function itself.

sub multiplier {

my $multiply = product;

return sub { product * $multiply };

}

$func1 = multiplier(20);

$func2 = multiplier(555);

Now &$func1($n) is always 20 times whatever $n you pass in, whereas &$func2($n) is always 555 times whatever $n you pass in. The $multiply in the closure sticks around.

Ruby

In Ruby, closures are supported through procs and lambdas. These constructs are very similar, but there are some subtle differences.


class StudentGrade
  def initialize(grade)
    @grade = grade
  end
 
  def printGrade(name)
    lambda {puts "The student #{@name}'s  grade is : #{@grade} "}
  end
end
 
def caller(closureVariable)
  closureVariable.call
end
 
obj1 = StudentGrade.new('A')
 closureCall = obj1.printGrade("Matt")
 
caller(closureCall)

Upon execution,get the following output:

The student Matt's grade is A

The printGrade function creates a closure, using the lambda construct, and then returns it. The closure is assigned to a variable, closureCall which is passed to another function caller, which then calls the closure. In this way a closure can be passed around.

When we called the closure, both the variables name and grade were out of scope in the rest of the program. But when it is called ,they are still in scope as the closure retained the state of these variables

CLOSURES IN STATICALLY TYPED LANGUAGES

Closures increase considerably the level of a language by mixing access to local variables with remote execution of a set of locally-defined statements. However, to date closures have not been added to statically-typed languages because it is difficult to type them and runtime errors occur if local variables that no longer exist are accessed. Although these errors cannot be strictly considered “type errors”, they make the language unsafe, which is against the philosophy of statically-typed languages.

C++

C++ provides partial support for higher order functions using function objects (or "functors"), and has the additional benefit that the function call operator may be overloaded so that functors may be treated generically. A function object is the closest thing to a lexical closure in C++ . Consider the following example:

struct Fobj{
    Fobj(int var)
        : m_var(var){
    }

    void operator()(string arg){
         std::cout << arg << m_var << std::endl;
    }

    int m_param;
};

Fobj f(10);
f("Implementing Function Objects in C++");

It doesn't capture local variables like a closure, local variable values have to be passed explicitly to the constructor instead because the stack frame is destroyed (and hence local variables) when the function returns.

Java

Although Java doesn't support higher order functions directly nor provide any support for lexical  closures, it provides mechanisms for mimicking this behavior. Java's anonymous classes allow a function  to be bundled with an object that can be treated just like a higher order function. It can be bound to  variables, passed to other functions as an argument, and can be returned as the result of a function. However, the function itself is named and therefore cannot be treated in a generic fashion, as true higher order functions can. Consider the following code without closures, written in Java:

public interface Block<T> {
     void invoke(T arg);
    }
    public class Utils {
     public static <T> void forEach(Iterable<T> seq, Block<T> fct) {
       for (T elm : seq)
       fct.invoke(elm);
     }
    }
    public class Test {
     public static void main(String[] args) {
       List<Integer> nums = Arrays.asList(1,2,3);
       Block<Integer> print = new Block<Integer>() {
         public void invoke(Integer arg) {
           System.out.println(arg);
         }
       };
       Utils.forEach(nums,print);
     }
    }

The above example is quite contrived and extremely simple, but then, there are few scenarios in Java, where an interface is implemented and the implementation is passed to a method for execution.

  • Runnable and Callable, which we pass to threads or thread pools for asynchronous execution.
  • Callback interfaces such as ActionListener, which we register for future execution in case a certain event occurs.
  • Comparator, which we pass to a TreeMap for maintaining its sorting order.

In all these cases an interface was used to provide some functionality as an implementation of the interface. The functionality is then passed to a method for immediate or delayed, synchronous or asynchronous execution. Closures would simplify this process by allowing a more concise syntax and increase the readability of the Java source code.Closures would also add some functionality completely new to Java, such as custom control structures.

Challenges due to lack of closures in Statically typed languages

Consider an example, given a list of students objects and a list of student leaders is to be created, which can be determined with an IsLeader property.

Using C#, it probably would be written like this

public static IList Leaders(IList studs)

{

IList result = new ArrayList();

foreach(Student s in studs)

if (s.IsLeader) result.Add(s);

return result;

}

In a language that has Closures, for example, Ruby, it would be written as,

def leaders(studs)

return studs.select {|s| s.isLeader}

end

Essentially select is a method defined on the Ruby collection class. It takes a block of code, a closure, as an argument. Select iterates through the input array, executes the block of code with each element, and returns an array of those elements for which the block evaluated as true.

The workaround in C could be using a function pointer; in Java with an anonymous inner class; and with a delegate in C#. These mechanisms are similar to closures, but there are two telling differences.

The first difference is closures can refer to variables visible at the time they were defined. Consider this method,

def highScorer (studs)

high_score= 90

return studs.select {|s| s.grade > high_score}

end

Here the code in the select block is referring to a local variable defined in the enclosing method. Many of the alternatives to closures in languages that don't have real closures can't do that.

Consider this function,

def scoreMore(aggregate)

return Proc.new {|s| s.grade >aggregate}

end

This function returns a closure, indeed it returns a closure whose behavior depends on the argument sent into it.

highScorer = scoreMore(90)

The variable highScorer contains a block of code (called a Proc in Ruby) that will return whether a tested object has a grade greater than 90.

mary = Student.new

mary.grade = 110

print highScorer.call(mary)

REFERENCES

  1. Sussman and Steele. "Scheme: An interpreter for extended lambda calculus". "... a data structure containing a lambda expression, and an environment to be used when that lambda expression is applied to arguments." (Wikisource)
  2. Wikipedia: Closure (computer science)
  3. Closures in Python
  4. Joe Morrison: Brief introduction to four programming language concepts
  5. Understanding the closures debate Does Java need closures? Three proposals compared By Klaus Kreft and Angelika Langer, JavaWorld.com
  6. Brian Scaturro: Closures: Front to Back
  7. stackoverflow:Why are closures suddenly useful for optimizing programs to run on multiple cores?
  8. perlfaq7 - General Perl Language Issues/ What's a closure?
  9. Alan Skorkin: Closures – A Simple Explanation (Using Ruby)
  10. stackoverflow: Closures: why are they so useful?
  11. stackoverflow: Closures in C++
  12. Wikipedia: Introduction to Programming Languages/Closures
  13. Voegele.com/Programmer’s Corner/Programming Language Comparison
  14. Martin Fowler’s Bliki - Closure


FURTHER READING