Forum: Ruby Methods and blocks - not that clear when blocks passed into

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
378ee4b664db2c1c5e08deb01f6dd44d?d=identicon&s=25 Steven Taylor (superjacent)
on 2009-04-25 10:17
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?
Aafa8848c4b764f080b1b31a51eab73d?d=identicon&s=25 Phlip (Guest)
on 2009-04-25 13:16
(Received via mailing list)
Steven Taylor wrote:

> 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.
378ee4b664db2c1c5e08deb01f6dd44d?d=identicon&s=25 Steven Taylor (superjacent)
on 2009-04-25 13:24
Phlip wrote:

> 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.

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).
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (Guest)
on 2009-04-25 13:48
(Received via mailing list)
On 25.04.2009 13:24, Steven Taylor wrote:
> 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
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (Guest)
on 2009-04-25 13:50
(Received via mailing list)
On 25.04.2009 13:43, Robert Klemme wrote:
>>
>> 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.

PS: I have blogged about a related topic recently:

http://blog.rubybestpractices.com/posts/rklemme/00...
Aafa8848c4b764f080b1b31a51eab73d?d=identicon&s=25 Phlip (Guest)
on 2009-04-25 14:40
(Received via mailing list)
Robert Klemme wrote:

>> Write lots of unit tests.

> PS: I have blogged about a related topic recently:
>
> http://blog.rubybestpractices.com/posts/rklemme/00...

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??
54404bcac0f45bf1c8e8b827cd9bb709?d=identicon&s=25 7stud -- (7stud)
on 2009-04-25 14:46
Steven Taylor wrote:
> 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;
}
--------------------



myfunc displays 7 to the screen.
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (Guest)
on 2009-04-25 16:01
(Received via mailing list)
On 25.04.2009 14:35, Phlip wrote:
>
>     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
1bc63d01bd3fcccc36fb030a62039352?d=identicon&s=25 David Masover (Guest)
on 2009-04-26 04:23
(Received via mailing list)
On Saturday 25 April 2009 03:17:50 Steven Taylor wrote:
> 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?'
753dcb78b3a3651127665da4bed3c782?d=identicon&s=25 Brian Candler (candlerb)
on 2009-04-27 10:45
Phlip wrote:
>    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
This topic is locked and can not be replied to.