Methods and blocks - not that clear when blocks passed into

Discussion in 'Ruby' started by Steven Taylor, Apr 25, 2009.

  1. 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?
    --
    Posted via http://www.ruby-forum.com/.
    Steven Taylor, Apr 25, 2009
    #1
    1. Advertising

  2. Steven Taylor

    Phlip Guest

    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.
    Phlip, Apr 25, 2009
    #2
    1. Advertising

  3. 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).
    --
    Posted via http://www.ruby-forum.com/.
    Steven Taylor, Apr 25, 2009
    #3
  4. On 25.04.2009 13:24, Steven Taylor wrote:
    > 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).


    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

    --
    remember.guy do |as, often| as.you_can - without end
    http://blog.rubybestpractices.com/
    Robert Klemme, Apr 25, 2009
    #4
  5. On 25.04.2009 13:43, Robert Klemme wrote:
    > On 25.04.2009 13:24, Steven Taylor wrote:
    >> 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).

    >
    > 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/002_Writing_Block_Methods.html

    --
    remember.guy do |as, often| as.you_can - without end
    http://blog.rubybestpractices.com/
    Robert Klemme, Apr 25, 2009
    #5
  6. Steven Taylor

    Phlip Guest

    Robert Klemme wrote:

    >> Write lots of unit tests.


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


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

    --
    Phlip
    Phlip, Apr 25, 2009
    #6
  7. Steven Taylor

    7stud -- Guest

    Re: Methods and blocks - not that clear when blocks passed i

    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.



    --
    Posted via http://www.ruby-forum.com/.
    7stud --, Apr 25, 2009
    #7
  8. On 25.04.2009 14:35, Phlip wrote:
    > Robert Klemme wrote:
    >
    >>> Write lots of unit tests.

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

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


    --
    remember.guy do |as, often| as.you_can - without end
    http://blog.rubybestpractices.com/
    Robert Klemme, Apr 25, 2009
    #8
  9. 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?'
    David Masover, Apr 26, 2009
    #9
  10. 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
    --
    Posted via http://www.ruby-forum.com/.
    Brian Candler, Apr 27, 2009
    #10
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. dee
    Replies:
    9
    Views:
    488
    Joseph Byrns
    Apr 15, 2005
  2. Anand
    Replies:
    2
    Views:
    883
    Anand
    Sep 11, 2003
  3. David

    Response.Clear() doesn't clear

    David, Jan 31, 2008, in forum: ASP .Net
    Replies:
    2
    Views:
    991
    Mark Fitzpatrick
    Jan 31, 2008
  4. InvalidLastName

    Unrecognized element 'add' after <clear></clear>

    InvalidLastName, Feb 26, 2007, in forum: ASP .Net Web Services
    Replies:
    3
    Views:
    917
    Steven Cheng[MSFT]
    Mar 6, 2007
  5. matt
    Replies:
    1
    Views:
    233
    George Ogata
    Aug 6, 2004
Loading...

Share This Page