"no block given" with closure?

B

Bob Sidebotham

When I execute this code, I get "no block given (LocalJumpError)":

class S
def initialize(&b1)
@b1 = b1
end
def each
@b1.call
end
end

s = S.new {[1,2,3].each {|i| yield i }}.each {|v| puts v}

Changing the implied block/yield combination to an explicit proc object
works, as follows:

class S
def initialize(&b1)
@b1 = b1
end
def each(&b2)
@b1.call(b2)
end
end

s = S.new { |p| [1,2,3].each { |i| p.call(i) }}.each {|v| puts v}

This prints the expected 3 lines.

This is with ruby 1.8.2 (2004-09-10) [i686-linux]

It's quite possible that I don't understand how this is supposed to work
:). Any hints would be appreciated.

Thanks,
Bob Sidebotham
 
B

Brian Schroeder

When I execute this code, I get "no block given (LocalJumpError)":

class S
def initialize(&b1)
@b1 = b1
end
def each
@b1.call
end
end

s = S.new {[1,2,3].each {|i| yield i }}.each {|v| puts v}

Changing the implied block/yield combination to an explicit proc object
works, as follows:

class S
def initialize(&b1)
@b1 = b1
end
def each(&b2)
@b1.call(b2)
end
end

s = S.new { |p| [1,2,3].each { |i| p.call(i) }}.each {|v| puts v}

This prints the expected 3 lines.

This is with ruby 1.8.2 (2004-09-10) [i686-linux]

It's quite possible that I don't understand how this is supposed to work
:). Any hints would be appreciated.

Thanks,
Bob Sidebotham

I'd say, that as first closure @b1 does not have any block when it is
instantiated, so I't won't know of the block given to each. Therefore "no
block given".

On the other hand in the second example you stuff the block into the first
closure, such that it knows about the block.

So the bottom line would be: a closure remembers the state at its
creation, but it does not know about the state when it is called.

I hope it is correct and understandable.

regards,

Brian Schröder
 
F

Florian Gross

Bob said:
When I execute this code, I get "no block given (LocalJumpError)":

class S
def initialize(&b1)
@b1 = b1
end
def each
@b1.call
end
end

s = S.new {[1,2,3].each {|i| yield i }}.each {|v| puts v}

The yield looks for blocks only in the method where it is used.

I think this is by design.

You might be able to work around this by using eval with a binding. But
that wouldn't be any better than the solution where you yield the block.

Regards,
Florian Gross
 
R

Robert Klemme

Bob Sidebotham said:
When I execute this code, I get "no block given (LocalJumpError)":

class S
def initialize(&b1)
@b1 = b1
end
def each
@b1.call
end
end

s = S.new {[1,2,3].each {|i| yield i }}.each {|v| puts v}

Changing the implied block/yield combination to an explicit proc object
works, as follows:

class S
def initialize(&b1)
@b1 = b1
end
def each(&b2)
@b1.call(b2)
end
end

s = S.new { |p| [1,2,3].each { |i| p.call(i) }}.each {|v| puts v}

This prints the expected 3 lines.

This is with ruby 1.8.2 (2004-09-10) [i686-linux]

It's quite possible that I don't understand how this is supposed to work
:). Any hints would be appreciated.

Possibly. The yield in the first example has no block that it can invoke,
much the same as when you invoke "yield" directly:
LocalJumpError: no block given
from (irb):1

You make it a bit too complicated, IMHO. If you just want to invoke some
block on an Enumerable, then you can have that directly (the yield block in
the first example basically just forwards the element). So you could do
this as well:

class S
def initialize(enum)
@enum = enum
end
def each(&b)
@enum.each &b
end
end

s = S.new([1,2,3]).each {|v| puts v}

OTOH, if you want to use the block to generate a value which is used for the
enumeration you could do

class S
def initialize(&create)
@create = create
end
def each(&b)
@create.call.each &b
end
end

s = S.new { [1,2,3] }.each {|v| puts v}

The subtle difference between these two is, that the second approach creates
something on each invocation of S#each and iterates over it while in the
first case all iterations run through the same instance:
s = S.new { [rand(10), rand(10)] }
=> # said:
s.each {|v| puts v}
1
6
=> [1, 6]0
6
=> [0, 6]?> s.each {|v| puts v}
0
5
=> [0, 5]
Hope this makes things a bit clearer.

Kind regards

robert
 
B

Bob Sidebotham

Robert said:
You make it a bit too complicated, IMHO. If you just want to invoke
some block on an Enumerable, then you can have that directly (the yield
block in the first example basically just forwards the element). So you
could do this as well:

Thanks Robert (and others) for your help. I had thought that yield somehow
dynamically found the first block up the call stack and called that.
Evidently, that's wrong, and yield will only call a block directly
associated with the method.

I don't think I'm making this too complicated. The example I gave was
distilled down from some stuff in Kansas. In Kansas, you can write:

kh.select:)Customer) { |c| c.cust_id > 100 }.each do |cust|
puts "#{cust.cust_name}"
end

The nested blocks in my original example correspond to the above. In the
current implementation of Kansas, the database query is executed all at
once, and returns an array of records--that array is then passed back from
the block, above, for iteration. I wanted this to be truly iterative so I
wrapped up the sql in a class with an each method, as below:

class KSSelection
def initialize(&doselect)
@doselect = doselect
end
def each(&userblock)
@doselect.call(userblock)
end
end

def select(*args)
tables,sql,read_only = check_query_args(*args)

...

KSSelection.new do |userblock|
@dbh.select_all(sql) do |row|
userblock.call add_object(
tables[0].new.load(row.to_h, self, read_only))
end
end
end

This code works (but of course, it didn't work when I naively used yield,
instead of userblock.call, thinking that yield was dynamically scoped). If
there's some way to do this without the stub KSSelection class, that'd be
really neat, but I don't think there is.

Bob

P.S. Is there an actual Ruby language reference that would answer questions
like this?
 
R

Robert Klemme

Bob Sidebotham said:
Thanks Robert (and others) for your help. I had thought that yield somehow
dynamically found the first block up the call stack and called that.
Evidently, that's wrong, and yield will only call a block directly
associated with the method.

I don't think I'm making this too complicated. The example I gave was
distilled down from some stuff in Kansas. In Kansas, you can write:

kh.select:)Customer) { |c| c.cust_id > 100 }.each do |cust|
puts "#{cust.cust_name}"
end

The nested blocks in my original example correspond to the above. In the
current implementation of Kansas, the database query is executed all at
once, and returns an array of records--that array is then passed back from
the block, above, for iteration. I wanted this to be truly iterative so I
wrapped up the sql in a class with an each method, as below:

Ah, I see. Sounds reasonable especially for large results.
class KSSelection
def initialize(&doselect)
@doselect = doselect
end
def each(&userblock)
@doselect.call(userblock)
end
end

def select(*args)
tables,sql,read_only = check_query_args(*args)

...

KSSelection.new do |userblock|
@dbh.select_all(sql) do |row|
userblock.call add_object(
tables[0].new.load(row.to_h, self, read_only))
end
end
end

This code works (but of course, it didn't work when I naively used yield,
instead of userblock.call, thinking that yield was dynamically scoped). If
there's some way to do this without the stub KSSelection class, that'd be
really neat, but I don't think there is.

I dunno Kansas so this might not work or might not do what you want. But
what about this simple passing on of the block?:

def select(*args, &b)
...
@dbh.select_all(sql, &b)
end

Bob

P.S. Is there an actual Ruby language reference that would answer
questions like this?

There's a pickaxe online version as well as a comprehensive lib
documentation reachable via http://www.ruby-doc.org/

Regards

robert
 

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,766
Messages
2,569,569
Members
45,042
Latest member
icassiem

Latest Threads

Top