Struggling with Blocks

N

Newbie

Perhaps I wasn't clear enough in my question (or misunderstood your answer). I'm trying to
understand a design decision - why isn't do/end used for begin/end scenarios?
 
N

Newbie

So, from a language design perspective, what would be wrong with this?

do
stuff
do
risky stuff
rescue
SOS
end
more stuff
end while bored

or

do
risky stuff
rescue
SOS
end while bored
 
K

Ken Bloom

"do" doesn't work this way -- it's an iterator (loosely speaking). "begin"
is not an iterator. They are different. There are good reasons to make this
distinction.

What *are* those reasons? That's the whole point of his question.
 
L

Logan Capaldo

There is a need for both "do" and "begin" blocks. There is a need to
distinguish syntactically between a block that must be fed with items, and
one that must not be fed with items. To combine "do" and "begin" would lead
to syntactical ambiguity ... and surely then someone would ask why the two
purposes of "do" were not more clearly distinguished in the syntax.

If "do" and "begin" were to be merged, it would be a little like the
ambiguous use of "<<" in C++. In one context, it shifts bits:

int x = 1,y;

y = x << 4; // y = 16

In another context, "<<" inserts items into a stream:

iostream x;
bool y;

y = x << 4; // y = true if the operation was successful

See the problem? Without my clear declarations directly above each case, you
would have a hard time distinguishing cases that use the same syntax. This
would make program listings hard to interpret and debug (a fact in C++).

The multiple uses of "<<" in C++ is a well-known example, but the point I am
making is that it's important to avoid ambiguous syntax in language design.
"do ... end" always has a stream, and when the stream is exhausted, the
block exits. "begin ... end" never has a stream. It's easy to remember and
easy to read.

The end result of responding to every request for creative syntax variations
is called ... umm ... "Perl". :)
Excellent argument, poor choice of example code *cough*.

a = 1 #=> 1
a << 1 #=> 2
$stdout << 4 # outputs '4'
 
J

Joseph

Excellent explanation Paul... I guess many newbies, and not so new are
grateful.

Regards,

Jose L. Hurtado
Web Developer
Toronto, Canada
 
H

Hal Fulton

Paul said:
"do" reads from a list of items and provides them to its controlling block:

These two forms are interchangeable. One can argue that "do" is an implicit
"{ ... }" block, or the reverse. But the point is "do" receives items and
operates on them one at a time, then exits when its stream is empty.

Not strictly true. Reemember that the iteration comes from the fact
that the each method is invoking the block multiple times.

Here's a do/end that will only execute once (and by the way
has no parameters):

Dir.chdir(some_dir) do process_something end

And by the way, the rescue that can appear between def and end
is rather an example of what some people want, IIUC. My impression
is that do/end don't have a rescue basically to avoid unneeded
complexity and because putting rescue inside {} for orthognality
looks questionable.


Hal
 
L

Logan Capaldo

Hal said:
Not strictly true.

Yes, strictly true, IMHO.
Reemember that the iteration comes from the fact
that the each method is invoking the block multiple times.

Not if there is only one item.

array = []

array << "one item"

array.each do |item|
# one item
end
Here's a do/end that will only execute once (and by the way
has no parameters):

Dir.chdir(some_dir) do process_something end

In this example, "do" acts on its stream until it is empty. The stream
happens to empty after one item, because that is what the stream contains.
That's a bit of a stretch, don't you think? What exactly is in "the
stream"? I can pretty much gurantee you that Dir.chdir looks like:

def Dir.chdir(directory)
old_directory = Dir.pwd
begin
chdir_without_block(directory)
yield
ensure
chdir_without_block(old_directory)
end
end

I see state, but no stream. What about instance_eval? That's not a
"stream" is it? Heck, lambda { |x, y| x + y }.call(1, 2), where's the
stream in that?
 
G

gwtmp01

"do" reads from a list of items and provides them to its
controlling block:

(implicit "do")

array.each { |item|
# do something here
}

(explicit "do")

array.each do |item|
# do something here
end

These two forms are interchangeable. One can argue that "do" is an
implicit
"{ ... }" block, or the reverse. But the point is "do" receives
items and
operates on them one at a time, then exits when its stream is empty.

This just doesn't sound right to me. do/end and {} are part of the
syntax of
a method call in ruby. They aren't operators. There is no
requirement that
the code in a do/end or {} block be used within some sort of a looping
or iterative context. For example:

File.open('datafile') do |fd|
# do something with the open file handle: fd
end

A do/end block is simply a snippet of code and a set of variable
bindings that
are made available to the called method. The called method can
ignore the block,
call it one or more times, call it conditionally, pass it along to
some other
method, and so on.

Gary Wright
 
L

Logan Capaldo

Logan Capaldo wrote:

/ ...


Yes, as does the iostream 'x' in my example (y = x << 4), and y is set to
"true" unless the operation fails. Remember, the example is C++, not Ruby,
solely to point out a language ambiguity there.
Entirely my point, the exact same ambiguity exists in ruby. Why can't be
also have the do ... end ambiguity since we've already introduced
ambiguities. Ruby is full ambiguities (some might suggest that's what
makes Duck-typing such a useful paradigm). My point was that saying
"Ruby shouldn't have an ambiguity, look at this ambiguity in this other
language", when ruby has the exact same ambiguity is sort of counter
productive.
 
M

MonkeeSage

A do/end block is simply a snippet of code and a set of variable
bindings that
are made available to the called method. The called method can
ignore the block,
call it one or more times, call it conditionally, pass it along to
some other
method, and so on.

That's what I was thinking. do...end / {...} is an implicit closure,
begin...end is a simple grouping construct. Also do...end has delayed
evaluation (like a quoted expression in Lisp), where begin...end is
evaluated immediately. That also means that do...end can be assigned to
a name (which results in similar functionality to a Lisp macro; e.g., a
= lambda { |x,y| puts x[y] }; a.call({:tree, 'cat'}, :tree)), and can
be passed around and so on; but begin...end cannot be.

This may help the OP understand the difference a little better:
http://www.artima.com/intv/closures.html

Regards,
Jordan
 
K

Ken Bloom

"do" reads from a list of items and provides them to its controlling block:

(implicit "do")

array.each { |item|
# do something here
}

(explicit "do")

array.each do |item|
# do something here
end

These two forms are interchangeable. One can argue that "do" is an implicit
"{ ... }" block, or the reverse. But the point is "do" receives items and
operates on them one at a time, then exits when its stream is empty.

"begin" doesn't get fed with items, it has a different purpose. It
demarcates a controlled block, to which a "rescue" clause might apply, or
to which a "while" test might apply, or others. And a "begin" block won't
persist on its own. Without some internal block that does something
repetitive (or a "while" test at the end), the "begin" block will exit in
one pass.

There is a need for both "do" and "begin" blocks. There is a need to
distinguish syntactically between a block that must be fed with items, and
one that must not be fed with items. To combine "do" and "begin" would lead
to syntactical ambiguity ... and surely then someone would ask why the two
purposes of "do" were not more clearly distinguished in the syntax.

If "do" and "begin" were to be merged, it would be a little like the
ambiguous use of "<<" in C++. In one context, it shifts bits:

int x = 1,y;

y = x << 4; // y = 16

In another context, "<<" inserts items into a stream:

iostream x;
bool y;

y = x << 4; // y = true if the operation was successful

See the problem? Without my clear declarations directly above each case, you
would have a hard time distinguishing cases that use the same syntax. This
would make program listings hard to interpret and debug (a fact in C++).

The multiple uses of "<<" in C++ is a well-known example, but the point I am
making is that it's important to avoid ambiguous syntax in language design.
"do ... end" always has a stream, and when the stream is exhausted, the
block exits. "begin ... end" never has a stream. It's easy to remember and
easy to read.

The end result of responding to every request for creative syntax variations
is called ... umm ... "Perl". :)

Let me try a direction in answering this question. I don't know anything
about the internals of Ruby to know whether this is right or not, but
perhaps someone can confirm.

do..end and {...} blocks are lexical closures that remember their
surrounding binding. This requires setup and teardown code for dealing
with bindings that could otherwise go out of scope.

begin...end can't be passed around like do...end, and so it doesn't need
the same kind of setup and teardown code for dealing with bindings.

Different syntax is needed because the ruby interpreter isn't smart enough
to know which situation is which.

Is this close to correct?
 
H

Hal Fulton

Paul said:
I was wrong. I should have thought a bit more deeply about what I was
saying. See my other post on this topic.

It's inspiring when someone can say the three magic words
"I was wrong" and move on.

That sounds sarcastic, but it's not meant to be. Can you
consider how much farther along the human race would be
if we all developed this skill?


Hal
 
H

Hal Fulton

Ken said:
do..end and {...} blocks are lexical closures that remember their
surrounding binding. This requires setup and teardown code for dealing
with bindings that could otherwise go out of scope.

begin...end can't be passed around like do...end, and so it doesn't need
the same kind of setup and teardown code for dealing with bindings.

Different syntax is needed because the ruby interpreter isn't smart enough
to know which situation is which.

Is this close to correct?

Disclaimer: I don't know the internals of Ruby either. This is only
guesswork on my part, and stands a large chance of being wrong.

I believe what you're saying is close to correct.

You're correct in saying that rescue requires some extra setup and
teardown. That seems intuitively obvious to me.

My guess is that "begin" is like an extra hint to the interpreter,
a foreshadowing if you will, that there is probably going to be a
rescue (and thus some setup/teardown).

Note that def/end and begin/end are much rarer in code than do/end
and {}.

So if we start allowing rescue on do/end and {}, we expose the
interpreter to a lot of guessing or tentatively holding onto
information or "looking ahead." Stuff like: Do the setup, but only
if there's a rescue forthcoming. Or do the setup, but throw it away
if we never see a rescue.


Hal
 
N

Newbie

Thanks for the link. I think I understand the difference, but surely this is an implementation
issue? What would be the ambiguity in allowing, for example,

do
risky stuff
rescue
SOS
ensure
more stuff
end while bored?

There is no difference to the programmer whether or not the interpreter treats this as a closure.
There may be a small performance penalty, but someone who needs to count CPU cycles isn't going to
use Ruby. Surely reducing the size/complexity of the language (by dropping a redundant keyword)
would have been more important?
(e-mail address removed) wrote:
That's what I was thinking. do...end / {...} is an implicit closure,
begin...end is a simple grouping construct. Also do...end has delayed
evaluation (like a quoted expression in Lisp), where begin...end is
evaluated immediately. That also means that do...end can be assigned to
a name (which results in similar functionality to a Lisp macro; e.g., a
= lambda { |x,y| puts x[y] }; a.call({:tree, 'cat'}, :tree)), and can
be passed around and so on; but begin...end cannot be.

This may help the OP understand the difference a little better:
http://www.artima.com/intv/closures.html
 
R

Rick DeNatale

Hal Fulton wrote:
Yes, you are right (an I am quite wrong in the above quotation). The "do ...
end" block is simply a closure with an alternate syntax, and the calling
method provides the data and any repetition that may exist.

---------------------------------------------------

#! /usr/bin/ruby

def my_funct
1.upto(8) do |x|
yield x
end
end

my_funct do |item|
puts item
end

(or, equivalently)

my_funct { |item|
puts item
}

So it appears that the syntactic role played by "do" is that it receives and
acts on the data fed to it by the calling method, once (barring any
repetitions internal to the do ... end block). IOW it is a closure block,
nothing more. Any looping is in the hands of the calling method.

While it is true in that in the most common case of do..end (or {..}),
i.e. when used as an argument to an iterator method, there are
argument involved, it's not necessarily so.

It's a syntactic 'feature' of Ruby that a do..end can only appear in a
method call, well actually there are two exceptions, see below. The
method being called has other options with what it can do with the
block than yielding a series of values to it.

irb(main):001:0> to_s_proc = lambda do |arg| arg.to_s; end
=> #<Proc:0xb7dd99f8@(irb):1>

irb(main):002:0> to_s_proc.call(1)
=> "1"

In this case lamba takes the block literal delimited by do..end and
turns it into a proc. The proc can later be called by anyone with a
reference to it. An example of such a use might be to implement a
'call-back' in a GUI framework, where the proc would be something to
be called, say when a button was pushed, although in this case a proc
with a side-effect is more likely to be used. Note that there's no
necessity for such a 'call-back' proc to take any arguments.

And it's possible to use a proc passed in on method call in a later iterator

irb(main):003:0>[1, 2, 1.3].map(&to_s_proc)
=> ["1", "2", "1.3"]

Now for those exceptions I noted above.

until boolean_expression do
body
end

and

while boolean_expression do
body
end

These both execute body either until or while boolean_expression
evaluates to true. In these two cases do..end are purely syntactic
entities, no block is created.

Another construct might LOOK like an exception:

for name [,name]... in expression do
body
end

But it really isn't. Here the do..end does delimit a proc, and this
construct acts as if it were:

expression.each do | name [,name]... |
body
end

except for one little quirk, in this case any local variables defined
in body will be accessible outside the loop.
So it appears the only difference between "do ... end" and "begin ... end"
is that "begin ... end" doesn't receive data from a caller.

Now we've just seen three cases where do doesn't receive data at all.

So, what's the real deal with BEGIN..END. What it really does is to
delimit a series of statements which are treated like an anonymous
method which is called in place. In other words when the begin is
entered the equivalent of a execution context is placed on the call
stack. It's this which enables the error handling. The rescue
clauses get associated with this call context.

When an exception is raised, the call stack gets scanned for a context
which contains a matching rescue clause.

So a more accurate statement of the difference between do..end, and
begin..end is that entering a begin..end stacks an execution context,
as does entering a method, whereas do..end does not.

And that's why you can put rescue clauses in a method definition, or
in a begin..end expression, but not directly in do..end.
 
M

Morton Goldberg

So, what's the real deal with BEGIN..END. What it really does is to
delimit a series of statements which are treated like an anonymous
method which is called in place.

That's what I thought until recently, but there is a difference.
Consider:

<code>
a = 1
begin
a = 2
b = 3
p [a, b] # => [2, 3]
end
p a # => 2
p b # => 3
</code>

If the begin ... end behaved _exactly_ like an anonymous method, we
would see:

<code>
a = 1
begin
a = 2
b = 3
p [a, b] # => [2, 3]
end
p a # => 1
p b # => raises NameError
</code>

Frankly, I would be happier with the latter behavior.

Regards, Morton
 
R

Rick DeNatale

So, what's the real deal with BEGIN..END. What it really does is to
delimit a series of statements which are treated like an anonymous
method which is called in place.

That's what I thought until recently, but there is a difference.
Consider:

<code>
a = 1
begin
a = 2
b = 3
p [a, b] # => [2, 3]
end
p a # => 2
p b # => 3
</code>

If the begin ... end behaved _exactly_ like an anonymous method, we
would see:

<code>
a = 1
begin
a = 2
b = 3
p [a, b] # => [2, 3]
end
p a # => 1
p b # => raises NameError
</code>

Frankly, I would be happier with the latter behavior.

I have to admit that I was struggling a bit when I made the analogy to
an anonymous method. The key is that begin pushes a new stack frame
onto the call stack.

You are right about the scoping of local variables.
 
G

gwtmp01

It's inspiring when someone can say the three magic words
"I was wrong" and move on.

That sounds sarcastic, but it's not meant to be. Can you
consider how much farther along the human race would be
if we all developed this skill?

+10


Gary Wright
 
M

Morton Goldberg

I have to admit that I was struggling a bit when I made the analogy to
an anonymous method. The key is that begin pushes a new stack frame
onto the call stack.

You are right about the scoping of local variables.

The problem -- for me -- is that I can also make the analogy that
'begin ... end' is like 'lambda do ... end.call' with about the same
degree of accuracy (or inaccuracy if you prefer). This second analogy
would reflect the scoping rules better, but is a worse fit than your
analogy when 'rescue', 'else', 'ensure', are added to the equation.
If 'lambda do ... end' were to acquire the semantics of 'lambda do;
begin ... end; end', then the analogy would be (almost?) exact and
the OP less confused, but perhaps I'm begging the question more than
clarifying anything. Also, such a change might introduce problems
that haven't occurred to me. So I'm not proposing an RCR.

Nonetheless, I have sympathy for the OP. The different semantics of
'begin ... end' and 'do ... end' _are_ confusing.

Regards, Morton
 

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

No members online now.

Forum statistics

Threads
473,763
Messages
2,569,563
Members
45,039
Latest member
CasimiraVa

Latest Threads

Top