CSC/ECE 517 Fall 2010/ch1 1b YL: Difference between revisions
Line 37: | Line 37: | ||
* Control structure using closure - Whether language supports closure for control structure or not. | * Control structure using closure - Whether language supports closure for control structure or not. | ||
== Closures in Ruby == | == Closures in Ruby == |
Revision as of 05:59, 8 September 2010
Closures in Ruby vs. closures in other languages
Closures
Before discussing semantic and implementation differences of closure implementation in different languages, closures should be discussed first.
A closure is a first-class function with free variables that are bound in the lexical environment. Closures:Wikipedia
Such a function is said to be "closed over" its free variables. In this definition first-class function is the function which is treated as first-class objects. Specifically, this means that the language supports constructing new functions during the execution of a program, storing them in data structures, passing them as arguments to other functions, and returning them as the values of other functions.
So closure can be explained as nothing but the behavior or in programming language function which can be passed around like any other argument and still remembering the original context where these functions are declared first. This way it breaks out of the standard function concept where the variables inside functions can be referenced within the scope of the function. However, in closures these variables can be referred out of the scope of the local functions. This ability of accessing original context makes closure different from normal objects.
Closure concept can be illustrated with the example in JavaScript.
// Return a list of all employees with at least 'threshold' salary.
function highlyPaidEmployees(threshold)
{
return empList.filter( function (emp) { return emp.salary >= threshold; } );
}
In above javascript function highlyPaidEmployees
, another anonymous function is defined. This function is passed to the Array.filter
method with free variable threshold. This free variable is assigned the value that is passed during the function call of highlyPaidEmployees. Thus the anonymous function defined inside is called repeatedly by filter method with free variable threshold having assigned the value passed to the highlyPaidEmployees. This follows both the properties defined in the closure definition. One is that the function is passed as an argument and function is able to access free variable out of its context.
Closure is supported by many languages. Ruby is famous for extensive use of closure. In next few sections comparison of closure implementation is different languages is done.
Basis of differences in implementations of closure
Even though many languages support closure concept there are significant differences in closure implementation in different languages. Some languages support closure concepts partially. Considering all these following are the point on the basis of which closures in different languages differ.
- First-class function - Whether language supports passing function as an argument
- Out of context reference - Whether language supports refering variable out of scope of its context
- Copy of variable or reference to a variable location - If language supports Out of context reference of variable then how is it implemented. Whether variable value is copied or same copy of variable is referred.
- Function currying - It is the technique by which function with multiple arguments can be called as a function with chain of functions each with single argument. Some languages support it and some do not.
- Control structure using closure - Whether language supports closure for control structure or not.
Closures in Ruby
In Ruby closures can be implemented using Blocks, Proc objects and lambda expressions.
Blocks
Blocks are like closures, because they can refer to variables from their defining context. Within the body of the invoked method, the code block may be called using the yield keyword. Following example explains the concept of blocks in Ruby.
def thrice yield yield yield end x = 5 puts "value of x before: #{x}" thrice { x += 1 } puts "value of x after: #{x}"
In above example x is incremented every time yield is called. Every yield call calls block {x += 1} and it remembers value of x between calls. Even though x is not defined in thrice method, block accesses x from the location where this block was defined. Thus A block remembers the context in which it was defined, and it uses that context whenever it is called.
Proc
But if we want to use same code block multiple times then concept of block is not helpful. As it requires passing this block of code everytime. Instead we can use Proc which is same as procedure in other languages.
class Array def iterate!(code) self.each_with_index do |n, i| self[i] = code.call(n) end end end array_1 = [1, 2, 3, 4] square = Proc.new do |n| n ** 2 end array_1.iterate!(square) puts array_1.inspect
In above example square Proc is created and passed to the iterate method which iterates over each element of array to square it. Thus code.call inside iterate method executes square Proc for each array element.
Lambda
Some languages support anonymous function or lambda. Proc in Ruby acts similar to lambda or anonymous function. Lambdas are also available in Ruby.
class Array def iterate!(code) self.each_with_index do |n, i| self[i] = code.call(n) end end end array = [1, 2, 3, 4] array.iterate!(lambda { |n| n ** 2 }) puts array.inspect
In above example instead of creating square Proc lambda expression for the same is created and passed around. Lambdas seem to be exactly the same as Procs. However, there are two differences. Differences are provided at Ruby blocks procs and lambdas
Control Structures Using Closure
In Ruby, control structures can be implemented using closure. In languages like C or C++ loops provide all the details like iterator, boundary values of iterator etc. Instead these things are abstracted in Ruby using closure.
3.times {puts "Hello"}
In above example Hello is printed three times and this is achieved using Blocks in Ruby. Implementing control structure using closure also reduces lines of code.
Currying in Ruby Using Closure
Ruby supports function currying. Currying means creating a new function out of an existing function by fixing the value of some of its input parameters. A simple example illustrates the concept. Consider a function which raises its first parameter to the power specified by the second parameter.
def power(x,y) val = 1 1.upto(y) {|a| val = val * x} val end square = lambda {|x| power(x,2)} cube = lambda {|x| power(x,3)} puts square.call(4) puts cube.call(3)
Thus in above example square and cube functions are created by fixing power parameter to 2 and 3. Instead of defining a new method, the existing one is curried. In Ruby, we can curry a method using lambda.
Closures in other languages
Closures in Lisp
Lisp is famous for its usage of Closure.
(let ((counter 0)) (defun new-id () (incf counter)) (defun reset-id () (setq counter 0))) --> RESET-ID (new-id) --> 1 (new-id) --> 2 (reset-id) --> 0
Closures can be retrurned from functions and then call them with FUNCALL:
(defun make-adder (n) #'(lambda (x) (+ x n))) --> MAKE-ADDER (funcall (make-adder 2) 5) --> 7 (funcall (make-adder 12) 500) --> 512
Thus Lisp allows referencing variables out of context. Also in above example make-adder method returns anonymous function which can be used to pass as an argument. Thus closures in Lisp satisfies the properties of closures.
Closures in ML
Closures in C
The C language does not support closures directly, but it has some extensions which add closures like functionality. Nested functions are similar to closures.
A nested function is a function defined inside another function. It's scope is limited to the enclosing function. The nested function can access all the variables of the containing function that are visible at the point of its definition. This is called lexical scoping.
int arrayProcess (int *array, int offset, int size) { int arrayAccess (int *array, int index) { return array[index + offset]; } int i; /* ... */ for (i = 0; i < size; i++) /* ... */ access (array, i) /* ... */ }
Here the inner function arrayAccess can refer to outer function's variable 'offset'.
The nested function can be called from outside the enclosing function by passing its address to another function:
It is possible to call the nested function from outside the scope of its name by storing its address or passing the address to another function:
int arrayProcess (int *array, int size) { void store (int index, int value) { array[index] = value; } intermediate (store, size); }
Here, the address of the function store is passed to the function intermediate as an argument. So, intermediate can call store with index and value arguments. But, intermediate can call store so long as the enclosing function arrayProcess does not exit.
It is not safe to call the nested function through its address after its enclosing function is exited. But, if the nested function does not refer to anything that has gone out of scope, then it will work.
Another closure like feature C language provides is function pointers.
int addition(int a, int b) { return a + b; }
// <ptr2Func> is a pointer to a function which takes two int values and returns an int void getPoniter(int (*ptr2Func)(int, int)) { int result = (*ptr2Func)(10,20); // call using function pointer printf("Result = " + result); }
void passPoniter() { int (*ptr2Function)(int, int) = NULL; ptr2Function = &addition; //& is optional here but prefered getPoniter(ptr2Function); }
Function pointers are nothing else than variables. In the code above, ptr2Function is pointer to function addition which takes two integers and returns one integer. It is then passed to another function getPointer(), which can then call the function addition using that pointer. Function pointer can be passed as an argument which is one of the desired property of the closures.
Closures in C++
C++ does not support nested function as C. But, it supports function pointers similar to C. Also, C++ has some libraries like Boost libraries which provide lambda expressions.
Here is a simple examples of Boost lambda expressions in Standard Template Library(STL).
list<int> v(10); for_each(v.begin(), v.end(), _1 = 1);
In the C++ version of lambda expressions, formal parameters have predefined names. In the current version of the library, there are three such predefined formal parameters, called placeholders: _1, _2 and _3. They refer to the first, second and third argument of the function defined by the lambda expression. The expression _1 = 1 creates a lambda functor which assigns the value 1 to every element in v.
More on Boost libraries can be found at Boost libraries
Thus C++ supports first-class functions as it has function pointers and they can be passed as arguments. It also supports control structures in terms of Boost library lambda expressions.
Closures in Java
A closure in Java is implemented using anonymous function. It is declared using the following syntax { formal parameters => statements expression }. For example, { int i => i + 1 } is a function that takes a single int argument and returns its value incremented by 1. A closure can be invoked by the invoke method. For example, { int i => i + 1 }.invoke(1) will call the closure with argument 1. Below is the Java class implementing closure.
public class SimpleClosure { public static void main(String[] args) { // function with one argument; return value is the passed value incremented by 1 int answer = {int x => x+1 }.invoke(10); System.out.println(answer); } }
In above main function, answer is the function with no arguments. It has body which increments passed value by 1 and returns it. In above case answer will be 11.
Each function that takes multiple arguments can be transformed into a function that takes a single argument. For example, function plus can be transformed into anotherPlus:
{ int, int => int } plus = { int x, int y => x + y }; { int => { int => int } } anotherPlus = { int x => { int y => x + y } }; int threePlusFour = anotherPlus.invoke(3).invoke(4);
Thus Java implements function currying using anonymous functions.
Comparison
Comparison of closures in different languages can be done on the basis of the factors defined in section 2. Below is the tabular comparison.
Factor | Closures in Ruby | Closures in Lisp | Closures in ML | Closures in C | Closures in C++ | Closures in Java |
---|---|---|---|---|---|---|
First-class function | Yes | Yes | Yes | Yes | Yes | Yes |
Out of context variable reference | Yes | Yes | Yes | No | Yes | Yes |
Function currying | Yes | Yes | Yes | No | Yes | No |
Control structure using closures | Yes | No | No | No | Yes | No |
Conclusion
Closures are extensively used in Ruby. Not only advanced features but very simple language features like control structures is also implemented using closure. Because of this Ruby has always less lines of code.
Closure is becoming popular day by day and hence it is being introduced and used in non-functional languages also.
References
2) Ruby blocks, procs and lambdas