Block and Closure Function
Introduction
Lately, a friend of mine just told how awesome functional programming awesome was. One of its feature which catch my interest most is the closure function. In fact, I used this feature a lot in prototype programming (Javascript, Lua) and decided to research about this feature in Ruby. And well, we get block, proc and lambda there.
Block
Block is a cool feature in Ruby, and most of you are using this. We see this a lot when working with Array, this is an example of block:
array = [1, 2, 3, 4, 5]array.each { |x| puts x }array.each do |x| puts xend
Of course, those above blocks are equal. And the question there, how we make our own blocks for class. Luckily, we have the powerful yield keyword to do our work there:
class Game_Party < Game_Unit def each_member members.each { |member| yield member } endend
With this short code, we can use block with Game_Party ($game_party):
$game_party.each_member { |member| puts member.name }$game_party.members.each { |member| puts member.name }
Those 2 lines do the same thing, but in first case, you can control your way to work with each member, and it's a better convention than calling its property and mapping each member of that array.
For example of change the way to work with each member, we have a battle system, and each of end turn, we reset all actors ready state, except for actors who need to rest (because of some skills require them to rest for a few turns):
class Game_Party < Game_Unit def reset_state members.each do |member| member.reset_state # a method to reset for each member yield member if member.is_ready? # only return ready member in block end endend
That way, you can do anything with all members who don't need a rest in new turn:
$game_party.reset_state do |member| # do anything with member that don't need a rest # in short, only member has is_ready? method return trueend$game_party.members do |member| member.reset_state if member.is_ready? # do anything with member that don't need a rest endend
Those two blocks do the same thing, but in case 1, we have a nicer convention, and we can use it as a normal method (reset_state for all members). In short, we can shorten our code in this way.
But wait, we remove the block, only use the method:
$game_party.reset_state
And get a crash! yield only work when a block is given, so we have to change the code a bit to check if the block is given:
class Game_Party < Game_Unit def reset_state members.each do |member| member.reset_state # a method to reset for each member end # separate mapping with yield for better code members.each do |member| yield member if member.is_ready? # only return ready member in block end if block_given? endend
Try again, and it work beautifully! The method block_given? only return true if a block is given.
Those examples are good for now, but what if we have a block that use again and again all the time? Procs and Lambdas will do it for you in a nice and short way.
Proc (Procedure)
Sometimes, you will find that you write a block code over and over again, and you also want to pass the function around method(s) and class(es). Proc is the best way to do this. For creating a new Proc, you can just use below code:
func1 = Proc.new { |arg1, arg2| your_code_here() }func2 = proc { |arg1, arg2| your_code_here() }Those two lines do the same thing, except in ruby 1.8.x, the
func2 will check number of arguments when it's called, whilst func1 doesn't. From ruby 1.9.x,
func1 and
func2 are exactly the same, they don't check number of arguments and will pass
nil value to missing argument(s).
And to call the
Proc, there are two ways to execute them:
func = Proc.new { |a, b| return a + b } # create procfunc.call(1, 2) # will return 3func[1, 2] # also return 3
Lambda
Lambda does the same thing as Proc, except it will check number of argument on call.
# in this example, your_code_here() doesn't raise error when a and b are nillamb1 = lambda { |a, b| your_code_here() }lamb2 = ->(a, { your_code_here() }func = Proc.new { |a, b| your_code_here() }lamb1.call() # raises an errorlamb2.call() # raises an errorfunc.call() # does not raise error
lamb1 and
lamb2 are the same, and syntax in lamb2 is called stabby lambda.
RESERVED FOR USAGE AND MORE INFORMATION!!!