Methods and blocks - not that clear when blocks passed into

S

Steven Taylor

Coming from other programming languages, notably Basic based, a little
Java & C++, I find that passing blocks into methods is a little cryptic.
What I mean is that when reading a method description (definition) there
is no reference made to the fact that a block could be passed in as an
argument. To me, it appears as if the method has to be read in
conjunction with how the method is actually called in order to know if a
block is passed or not. If my assertion is correct then the actual
method call(s) in source code could be 100's of lines away from the
method definition.

Is this how things are or are there some techniques to smooth this
process somewhat?
 
P

Phlip

Steven said:
Coming from other programming languages, notably Basic based, a little
Java & C++, I find that passing blocks into methods is a little cryptic.
What I mean is that when reading a method description (definition) there
is no reference made to the fact that a block could be passed in as an
argument. To me, it appears as if the method has to be read in
conjunction with how the method is actually called in order to know if a
block is passed or not. If my assertion is correct then the actual
method call(s) in source code could be 100's of lines away from the
method definition.

This is the shortcut:

def method(args)
yield
end

The longcut lets you treat the block as an object:

def method(args, &block)
block.call
end

Use the shortcut if your method is very close to its call site, and if you have
any reason to upgrade to the longcut, then upgrade and don't look back.

Blocks are mondo-important, and those other (corporate!) languages should be
ashamed they don't have them yet...
Is this how things are or are there some techniques to smooth this
process somewhat?

Write lots of unit tests.
 
S

Steven Taylor

Phlip said:
Blocks are mondo-important, and those other (corporate!) languages
should be
ashamed they don't have them yet...


Write lots of unit tests.

Thanks. I'll look into the 'longcut' version, at least that appears, on
the face of it, to be the explicit version (which suits my way of
thinking).
 
R

Robert Klemme

Thanks. I'll look into the 'longcut' version, at least that appears, on
the face of it, to be the explicit version (which suits my way of
thinking).

It used to be a bit slower though. I haven't done any measurements with
1.9.1 but my rule of thumb is: use explicit block if you need to store
the block or pass it on to another method, use yield otherwise.

Kind regards

robert
 
P

Phlip


Does it cover this situation?

def test_block
method do |x|
assert x == 42
end
end

If the block doesn't call, no assertion catches that problem. You must use this
truly un-Ruby-like hack:

def test_block
and_the_block_got_called = false
method do |x|
assert x == 42
and_the_block_got_called = true
end
assert and_the_block_got_called
end

How to DRY that??
 
7

7stud --

Steven said:
To me, it appears as if the method has to be read in
conjunction with how the method is actually called in order to know if a
block is passed or not.

Not necessarily:

def meth1
yield
end

meth1 {puts "hello"}

--output:--
hello

meth1

--output:--
r1test.rb:2:in `meth1': no block given (LocalJumpError)
from r1test.rb:7


So by reading the definition of meth1, you know that a block has to be
passed in the method call. As for knowing *what* block is passed, yes,
you might have to look through 100's of lines of code to discover that.

On the other hand, if the method definition looks like this:

def mymeth(an_int)
if block_given?
yield an_int
else
puts "hi"
end
end

then you don't know if the method will be called with a block:

mymeth(3) {|val| puts val}

--output:--
3

mymeth(3)

--output:--
hi


But is that much different from looking at this method definition:

def somemeth(x):
if x=="yes"
#40,000 function calls go here, which
#open 50 sockets
#awaken 20 million zomby computers
#search for nuclear launch codes with brute force attacks
#send launch codes to world news organizations to show how
vulnerable world security is
else
puts "have a nice day"
end
end

and thinking...Gee, I don't know what that method will do unless I can
track down the method call and observe the value of x? And then what if
you find:

somemeth(calc_x)

In other words, the method calls another method to calculate its
argument.

If my assertion is correct then the actual
method call(s) in source code could be 100's of lines away from the
method definition.

Heck, in Java method calls probably won't even be in the same file as
the method definition. And in C++, normally an include directive is
used to link function definitions in other files to the function calls
in the current file, which means the function definitions won't be
visible in the current file either.

What if you define a function called myfunc in C++ that takes another
function as an argument? The following is a C++ program that does just
that. Can you tell what myfunc does (*answer below)?


File 1:
------
//main.cpp

#include <iostream>

#include "globals.h"
#include "otherfuncs.h"

using namespace std;

int main()
{
myfunc(addEm); //<--function call. What the heck does that do?

return 0;
}

==============

Supporting files:


File 2:
-------
//otherfuncs.h

#ifndef OTHERFUNCS_H
#define OTHERFUNCS_H

int addEm(int x, int y);

#endif
------------------

File 3:
------
//otherfuncs.cpp

#include "otherfuncs.h"

int addEm(int x, int y)
{
return x + y;
}
--------------------


File 4:
-------
//globals.h

#ifndef GLOBALS_H
#define GLOBALS_H

void myfunc(int (*pfunc)(int, int));

#endif
--------------------

File 5:
-------
#include "globals.h"
using namespace std;

void myfunc(int (*pfunc)(int, int) )
{
cout<<pfunc(3, 4)<<endl;
}
 
R

Robert Klemme

Does it cover this situation?

def test_block
method do |x|
assert x == 42
end
end

If the block doesn't call, no assertion catches that problem. You must
use this truly un-Ruby-like hack:

def test_block
and_the_block_got_called = false
method do |x|
assert x == 42
and_the_block_got_called = true
end
assert and_the_block_got_called
end

How to DRY that??


def block_test
th = Thread.current
th[:block_run] = false
begin
yield
ensure
raise "Block not run!" unless th[:block_run]
end
end

def assert_block_run
Thread.current[:block_run] = true
end

def test_block
block_test do
m do |x|
assert_block_run
assert x == 42
end
end
end

You can easily adjust the scheme to counting of block calls etc.

Cheers

robert
 
D

David Masover

To me, it appears as if the method has to be read in
conjunction with how the method is actually called in order to know if a
block is passed or not.

If your question is how to tell whether a method accepts a block, the answer
is, it does. All methods do. They just might not do anything with it.

If your question is whether a method does anything with the block, there are
three ways to tell.

The first is documentation! On larger projects, like Rails and Merb, the public
API is at least very well documented. Even reading just the HTML pages, you'll
see mention of whether it accepts a block, or an options hash, what options
are significant in that hash, possibly what exceptions it throws, and some
actual usage examples.

The second way is if it has a &foo argument at the end -- for example:

def foo arg1, arg2, &block

Of course, technically, the method could ignore that, so look for that
variable (in this case, 'block') being used.

If all else fails, read the method looking for any 'yield' call. I don't
believe 'yield' will ever do anything but call the current block, inside the
appropriate method.

If your question is how to tell, programmatically, whether the current method
has been passed a block, just call 'block_given?'
 
B

Brian Candler

Phlip said:
def test_block
and_the_block_got_called = false
method do |x|
assert x == 42
and_the_block_got_called = true
end
assert and_the_block_got_called
end

How to DRY that??

Given that you're concerned in testing the number of times the block is
invoked, then I would turn the block into a mock object which verifies
that for you. Proof of concept:

require 'rubygems'
require 'mocha'
require 'test/unit'

class TestBlock < Test::Unit::TestCase
def yields_once(mname, *args)
blk = mock('block')
blk.expects:)call).with { |*x| yield *x; true }
send(mname, *args) { |*x| blk.call(*x) }
end

def my_method
yield 42
#yield 42
end

def test_my_method
yields_once:)my_method) do |x|
assert_equal 42, x
end
end
end

This gives an informative error if the block is not called, or is called
two or more times:

1) Failure:
test_uses_block(TestBlock)
[ert.rb:8:in `yields_once'
ert.rb:18:in `test_uses_block']:
not all expectations were satisfied
unsatisfied expectations:
- expected exactly once, not yet invoked: #<Mock:block>.call()

1 tests, 1 assertions, 1 failures, 0 errors
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,733
Messages
2,569,440
Members
44,832
Latest member
GlennSmall

Latest Threads

Top