CSC/ECE 517 Fall 2007/wiki1 2 pk

From Expertiza_Wiki
Jump to navigation Jump to search

Callbacks

A callback is code that is passed as an argument to other code. The exact behavior of a callback can be determined at runtime by passing different (yet compatible) function pointers or handles thereby uncoupling the caller from the callee. This makes callbacks easier to use than polymorphism and generic programming.

Statically scoped languages like C or C++, implement callbacks using references or pointers to code and data outside its own scope. Dynamically scoped languages (like Ruby and Smalltalk) use closures to access application data.

Closures

Closures are functions which are evaluated in an environment containing bound variables. They are blocks of code which meets three criteria:

  • They can be passed around as a value.
  • They can be executed on demand.
  • They can refer to variables from the context in which it was created.

Closures were initally developed in the 1960s in a language called Scheme and later adopted in other languages including Ruby and Smalltalk.

Examples

The code below illustrates a simple case of closure use.

def thrice
    yield #Pass control back to block
    yield
    yield
end


x = 5
puts "value of x before: #{x}"
thrice { x += 1 }
puts "value of x after: #{x}"

The closure(or block) { x+=1 } is passed as a parameter to the function thrice. The value of x before thrice is called is 5. On execution the value becomes 8 i.e. the block {x+=1} was executed thrice.

Blocks are essentially chunks of code which can be passed around as arguments to functions where they can be invoked any number of times. Note that all blocks are closures, however the reverse may not always be true because closures can refer to the variables which were visible in the lexical environment of the function in which they were defined.

def highPaid(emps)
  threshold = 150
  return emps.select {|e| e.salary > threshold}
end

Note that the closure passed to emps.select can access the local variable threshold.

Closures can also refer to the arguments of the function in which it is defined. For example, conside the ruby code snippet below.

def paidMore(amount)
  return Proc.new {|e| e.salary > amount}
end

#Create new employee
john = Employee.new
john.salary = 200

#Call closure
highPaid = paidMore(150)
print highPaid.call(john)

Note that the closure can refer to variable amount even after the function paidMore is done executing.

Creation Of Closures In Ruby

There are two ways to define a closure in Ruby.

  • Using Proc
  • Using lambda

The fundamental difference in the two lies in the way the return from the closure is handled. The following code snippet helps illustrates this.

def foo
    f = Proc.new { return "return from foo from inside proc" }
    f.call # control leaves foo here
    return "return from foo"
  end
 
  def bar
    f = lambda { return "return from lambda" }
    f.call # control does not leave bar here
    return "return from bar"
  end
 
  puts foo # prints "return from foo from inside proc"
  puts bar # prints "return from bar"

We see that the use of return in a closure defined using Proc, will return control not only from the closure but also from the enclosing function. On the other hand, the use of return in a closure defined using lambda will result in the return of control to the enclosing function.

Semantic Differences Between Ruby And Smalltalk Closures

This choice afforded to the programmers in Ruby is not available in all languages. For example, Smalltalk only allows the creation of closures similar to the Proc kind of closure in Ruby. For example, consider the Smalltalk code snippet below.

  foo
    | xs |
    xs := #(1 2 3 4).
    xs do: [:x | ^x].
    ^0
  bar
    Transcript show: (self foo) "prints 1"

Here we see that the control shifts out of foo after the first time the closure is invoked. This is because the closure is executed at the same level as the enclosing function(in this case foo).

foo
    ^[ x: | ^x ]
  bar
    | f |
    f := self foo.
    f value: 123 "error!"

The function foo seems to be defined to return a closure which in turn just returns the parameter that is passed to it. However the above code gives an error as the closure is valid only within the scope of the function in which it was defined, i.e. after foo has been called, the closure also ceases to exist.

In summary, Closures in Smalltalk are essentially blocks since access to the local lexical environment is not permitted (except for local variables defined inside the block). Ruby, on the other hand, supports both blocks and true closures.

References

Closures (Wikipedia) [1]

Callback (Wikipedia) [2]

Closures in Ruby - Paul Cantrell [3]

Closures - Martin Fowler [4]

Blocks and Closures in Ruby [5]

Lecture notes provided by Dr. Gehringer on Blocks and Closures [6]