This is a continuation of the article I knocked together a week or so ago, last time I did a Ruby course @ CodeSchool. I've beend ead slack at keeping up with those courses... I meant to do a coupla this weekend, but fiddled around with CF, JSoup and the Adobe ColdFusion docs instead. Oops.
Last time I talked about "blocks" in Ruby, and demonstrated my superficial understanding of them. The course I gleaned that info from also covered procs and lambdas too. So I'll compare that lot today...
Blocks
Firstly, here's a recap on blocks. Blocks are basically - as far as I can tell - just a chunk of code that one can pass to a function - over and above any of its passed-in arguments - which the function can then use (or ignore, I guess). Here's a quick example:def string_array_changer! string_array
string_array.each_with_index do |element, index|
yield element, index
end
end
maori_numbers = ["tahi", "rua", "toru", "wha", "rima", "ono", "whitu", "waru", "iwa", "tekau"]
string_array_changer! maori_numbers do |number, index|
maori_numbers[index] = number.upcase
end
puts maori_numbers
Here we have a function string_array_changer!, which takes an array loops over it, and passes each element and its index into the loop and within the loop yields that info to a block. The block I use in this case simply upper-cases the passed-in value, and sets it back in the array.
Note that the function has a ! at the end. This is another Ruby convention like the the usage of "?" to signify a boolean function (eg: is_boolean? would be a Ruby equivalent of isBoolean() in CFML). ! means "this function changes the value it's working on".
OK, so that's all simple enough. You and I would effect the same results by passing a callback into the function (and coding the function to expect it, obviously), and one can do that in Ruby too, but Ruby seems to like these blocks.
Procedures
One problem with blocks is that they are inline, and not reusable. Looking at my code above, that block doesn't have any sort of reference, so to use the same code again, one needs to type it in again. This is similar to how JQuery seems to encourage writing event handler callbacks inline, rather than using function references. I really don't like that approach, it seems very ill-organised to me. And obviously promotes code repetition.In Ruby one can give a block a reference, and in doing so, it "becomes" a procedure (or Proc in Ruby parlance). Or another way to look at it, I think, is a block is an anonymous procedure, or an inline procedure or something.
Here's a modification of the code example from before demonstrating giving a block a name, and using it as a procedure.
def string_array_changer! string_array, &code
string_array.each_with_index do |element, index|
string_array[index] = code.call(element)
end
end
maori_numbers = ["tahi", "rua", "toru", "wha", "rima", "ono", "whitu", "waru", "iwa", "tekau"]
ucase = Proc.new do |number|
number.upcase
end
string_array_changer! maori_numbers, &ucase
puts maori_numbers
It, otherwise, does exactly the same thing as the first example. Note that we're specifically defining the block as a proc, and now there's an argument expecting it too. This is becoming more familar to a CFML developer now. The CFML equivalent would be:
function string_array_changer(string_array, code) {
for (var index=1; index <= arrayLen(string_array); index++){
string_array[index] = code(string_array[index]);
}
return string_array;
}
maori_numbers = ["tahi", "rua", "toru", "wha", "rima", "ono", "whitu", "waru", "iwa", "tekau"];
string_array_changer(maori_numbers, function(number){
return ucase(number);
}
);
writeDump(maori_numbers);
Blocks as Procedures
One thing I skipped over when transforming the code from using blocks to using procs... one can actually just use a block as a proc without specifically declaring it as one. See this variation:def string_array_changer!(string_array, &code)
string_array.each_with_index do |element, index|
string_array[index] = code.call(element)
end
end
maori_numbers = ["tahi", "rua", "toru", "wha", "rima", "ono", "whitu", "waru", "iwa", "tekau"]
string_array_changer! maori_numbers do |number|
number.upcase
end
puts maori_numbers
Here I still have an inline block, and I'm not doing anything to suggest it's a proc (ie: no Proc.new). However the function expects a proc, and this all just works using the block as a proc anyhow.
Basically a block is just an anonymous proc. And it has that weirdo syntactical conceit that if a function is called with a block, the block is always available in the function, which can yield to it. I'm still not sold, and I prefer the more declarative approach.
Lambdas
"But wait... there's more..." as the over-used and never-that-clever-to-begin-with cliche goes. Ruby has another way of doing inline function expressions (as they would be in CFML) over and above blocks. Lambdas. Here's the same old code (can you remember the Maori numbers for 1-10 yet? Me: I've got 1-6 down-pat, and ten. Keep forgetting whitu, waru and iwa. I'm useless at languages)), using a lambda instead:def string_array_changer! string_array, code
string_array.each_with_index do |element, index|
string_array[index] = code.call(element)
end
end
maori_numbers = ["tahi", "rua", "toru", "wha", "rima", "ono", "whitu", "waru", "tekau"]
string_array_changer! maori_numbers, lambda {|number|
number.upcase
}
puts maori_numbers
Basically a lambda is just what we'd call a function expression. So we have a block which is an anonymous procedure, and a lambda which is an anonymous method. And the difference between procs and methods is fairly esoteric, I think.
One difference between procs and lambdas is that lambdas check how many arguments they're passed, and barf if it's not the expected number. And procs don't: one can just pass as many as one wants.
Also - and this is a bit odd if you ask me - procs can't have return statements. Remember how everything in Ruby is an expression, and the value of the last statement in a block/proc/lambda is considered the return from that construct? Well this is OK, and saves us that last usage of return at the bottom of the code block (I'm using "block" here in the general sense, not the Ruby sense). However what if you need to have early returns from your function? Well it seems you can't with a proc in Ruby. Because a proc can't have a return statement. I gather this is because the code in a proc as seen as in the same context for this sort of thing as its calling code (so like an inline <cfinclude>?), so if it had a return, it'd be returning from the calling code, not its own code. Or something like that. It's a bit bloody daft if you ask me.
So for me: lambdas will always win over inline procs/blocks.
Methods
A Method is to a lambda what a proc is to a block: a means of giving the thing a name / object reference. Here's another variation of the same code, using a method:def string_array_changer! string_array, code
string_array.each_with_index do |element, index|
string_array[index] = code.call(element)
end
end
maori_numbers = ["tahi", "rua", "toru", "wha", "rima", "ono", "whitu", "waru", "iwa", "tekau"]
def ucase number
return number.upcase
end
string_array_changer! maori_numbers, method(:ucase)
puts maori_numbers
Here we define our function ucase, and then pass it as a method to the string_array_changer! function as an argument. Note that to demonstrate it's a function not a proc, I have put a return statement in it. And the code works.
So this is just the same as a named function expression in CFML, as opposed to an inline anonymous one.
Conclusion
Well that was all very cool. Pleasingly - having not touched Ruby for a few weeks - I was able to key in that first example code without reference to docs, and it was right first time. I needed a small amount of help with the lambda and method syntax vagaries, but that's cool: it's the first time I've written one, other than during the course.I'm not convinced by blocks & procs when there are the more traditional lambda / method mechanisms there to use. I don't see why they need both approaches. Maybe it's like tags & script in CFML? Or old-skool vs nu-skool? Or just personal preference. Dunno yet.
There's a great blog article on all this: "Understanding Ruby Blocks, Procs and Lambdas" by Robert Sosinski. I got that from googling: I don't know Robert from a bar of soap. I used that as a sanity check and reference for all the bumpf I said above. I hope I translated it into Adam-speak correctly.
I'm still not done writing up that course, btw. They finished up with DLSs: Domain Specific Languages, which pull on all this block/proc/lambda/method stuff. I need to get my brain further around that stuff before I write it up. I hope this was useful to you in the mean time. It did rather help me formalise some things in my head.
Cheers.
--
Adam