[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.ruby

Only if the object exists

Gavin Kistner

6/11/2005 3:46:00 PM

If I write code like this:
self.foo.bar if self.foo
then ruby runs the #foo method twice. (Javascript is the same way.)

If #foo is a lengthy process which I know has no side-effects, this
is wasteful. But, I need to ensure that #foo didn't return nil. I
have (at least) three choices:

if ( tmp=foo )
puts tmp.bar
end

( tmp=foo ) && ( puts tmp.bar )

puts tmp.bar if ( tmp=foo )

...except that the last doesn't work, if 'tmp' has not been
previously seen as a local variable. Unfortunately, that's the form
that I really think looks best.


Would it be possible to have the parser recognize the single-line 'if/
unless' case and process the right side before the left side (the
order in which the code is executed)?


--
"When I am working on a problem I never think about beauty. I only
think about how to solve the problem. But when I have finished, if
the solution is not beautiful, I know it is wrong."
- R. Buckminster Fuller

10 Answers

Douglas Livingstone

6/11/2005 5:03:00 PM

0

On 6/11/05, Gavin Kistner <gavin@refinery.com> wrote:
> If I write code like this:
> self.foo.bar if self.foo
> then ruby runs the #foo method twice. (Javascript is the same way.)
>

I'd probably go for:

result = foo
result.bar unless result.nil?

If it has to go one one line, could be:

t = foo and puts t.bar

but you still have the temp var.

Douglas


Gavin Kistner

6/11/2005 10:09:00 PM

0

On Jun 11, 2005, at 11:03 AM, Douglas Livingstone wrote:
> On 6/11/05, Gavin Kistner <gavin@refinery.com> wrote:
>> If I write code like this:
>> self.foo.bar if self.foo
>> then ruby runs the #foo method twice. (Javascript is the same way.)
>
> I'd probably go for:
>
> result = foo
> result.bar unless result.nil?
>
> If it has to go one one line, could be:
>
> t = foo and puts t.bar

So you've added 1-2 more options to my list, but the intent of my
post was not how to rewrite it differently, but to request that
perhaps the parser could be a little more extra-intelligent about
determining the scope/guaranteed presence of the local variable.


Robert Klemme

6/12/2005 3:36:00 PM

0

Gavin Kistner <gavin@refinery.com> wrote:
> On Jun 11, 2005, at 11:03 AM, Douglas Livingstone wrote:
>> On 6/11/05, Gavin Kistner <gavin@refinery.com> wrote:
>>> If I write code like this:
>>> self.foo.bar if self.foo
>>> then ruby runs the #foo method twice. (Javascript is the same way.)
>>
>> I'd probably go for:
>>
>> result = foo
>> result.bar unless result.nil?
>>
>> If it has to go one one line, could be:
>>
>> t = foo and puts t.bar
>
> So you've added 1-2 more options to my list, but the intent of my
> post was not how to rewrite it differently, but to request that
> perhaps the parser could be a little more extra-intelligent about
> determining the scope/guaranteed presence of the local variable.

Why change the parser if there are ways to do this that don't require such
heavy language modifications?

Btw, here's another option if you need that pattern more often

class Dummy
def method_missing(*a,&b) self end
end
DUMMY = Dummy.new

(foo || DUMMY).bar

Apart from that I'd go with the "and" variant:

tmp = foo and tmp.bar

Kind regards

robert

Mark Hubbart

6/12/2005 4:03:00 PM

0

On 6/12/05, Robert Klemme <bob.news@gmx.net> wrote:
> Gavin Kistner <gavin@refinery.com> wrote:
> > On Jun 11, 2005, at 11:03 AM, Douglas Livingstone wrote:
> >> On 6/11/05, Gavin Kistner <gavin@refinery.com> wrote:
> >>> If I write code like this:
> >>> self.foo.bar if self.foo
> >>> then ruby runs the #foo method twice. (Javascript is the same way.)
> >>
> >> I'd probably go for:
> >>
> >> result = foo
> >> result.bar unless result.nil?
> >>
> >> If it has to go one one line, could be:
> >>
> >> t = foo and puts t.bar
> >
> > So you've added 1-2 more options to my list, but the intent of my
> > post was not how to rewrite it differently, but to request that
> > perhaps the parser could be a little more extra-intelligent about
> > determining the scope/guaranteed presence of the local variable.
>
> Why change the parser if there are ways to do this that don't require such
> heavy language modifications?

IMHO, it wouldn't be a "heavy language modification" (irrespective of
the work required). I've always felt uncomfortable with the fact that
an assignment in a statement modifier doesn't show up for the
statement it is modifying, even though it is executed first. I feel
the following two examples should be equivalent:

if t = foo
t.bar
end

-- and:

t.bar if t = foo

I know I've tried variations on the previous before many times,
forgetting that it doesn't work. I end up settling for the "t = foo
and t.bar", which doesn't read as clearly to me.

cheers,
Mark

> Btw, here's another option if you need that pattern more often
>
> class Dummy
> def method_missing(*a,&b) self end
> end
> DUMMY = Dummy.new
>
> (foo || DUMMY).bar
>
> Apart from that I'd go with the "and" variant:
>
> tmp = foo and tmp.bar
>
> Kind regards
>
> robert
>
>
>


Gavin Kistner

6/12/2005 4:27:00 PM

0

On Jun 12, 2005, at 9:40 AM, Robert Klemme wrote:
> Why change the parser if there are ways to do this that don't
> require such heavy language modifications?

I suppose because it seems to me that the only reason that:

tmp.bar if tmp=foo

fails to work is a slight naiveté by the parser. I expected it to
work, as there is no semantic difference between that and the code:

if tmp=foo
tmp.bar
end

That it does not work is consistent, however, with the odd fact that
introduction of a line like:

tmp = x if false

may affect the later scope of 'tmp'. I guess that's the real
disconnect for me. I'm coming from JavaScript, where the scoping for
a variable is not based on a top-down parsing, but is based on
whether the 'var' keyword declares the variable as local ANYWHERE in
the function. It strikes me as odd that this code:

def foo
10.times{ i = i ? i+1 : 1 }
puts i = i ? i+1 : 1
10.times{ i = i ? i+1 : 1 }
puts i = i ? i+1 : 1
end

results in "1" and "12" being written, instead of "11" and "22".

Ruby is the first language I've seen where the scope of a variable is
not the same throughout the same block. If this is on purpose and/or
really cool and useful in situations I haven't considered, then I'll
accept "tmp.bar if tmp=foo" not working as a necessary casualty. But
it seems to me that the variable variable scope may be due to an
implementation flaw in the pre-processing of code, not a conscious
design decision.

I'm not even proposing the 'massive' undertaking of scanning the
entire method for "tmp\s*=" outside of any blocks to predetermine if
tmp should be local (though IMO that should be done). I'm simply
suggesting that the parser conceptually invert the order of single-
line "yyyy if/unless xxxx" to process xxxx first.




> class Dummy
> def method_missing(*a,&b) self end
> end
> DUMMY = Dummy.new
>
> (foo || DUMMY).bar

Interesting!


> Apart from that I'd go with the "and" variant:
>
> tmp = foo and tmp.bar

My old self who likes to use boolean guards as inline conditionals
would do that. My new self who wants to make code very readable and
clear to the casual observer is trying to use the words 'if' and
'unless'. (YMMV, but IMHO using booleans as conditionals is cool but
non-obvious. I know I've had multiple C++ coders look at my
JavaScript code using that syntax and say "...wtf is this? What does
that do?")

Douglas Livingstone

6/13/2005 2:36:00 AM

0

On 6/12/05, Gavin Kistner <gavin@refinery.com> wrote:

> def foo
> 10.times{ i = i ? i+1 : 1 }
> puts i = i ? i+1 : 1
> 10.times{ i = i ? i+1 : 1 }
> puts i = i ? i+1 : 1
> end
>
> results in "1" and "12" being written, instead of "11" and "22".
>
> Ruby is the first language I've seen where the scope of a variable is
> not the same throughout the same block.

{ i = i ? i+1 : 1 } is a new block. Setting i inside this block
doesn't bubble up to the parent unless i is already defined.

Compare with:

def bar
i = 0
10.times{ i = i ? i+1 : 1 }
puts i = i ? i+1 : 1
10.times{ i = i ? i+1 : 1 }
puts i = i ? i+1 : 1
end

I'd call it a bug, we'll see if it still happens in Ruby2.

Duglas


Douglas Livingstone

6/13/2005 2:49:00 AM

0

On 6/12/05, Mark Hubbart <discordantus@gmail.com> wrote:
> IMHO, it wouldn't be a "heavy language modification" (irrespective of
> the work required). I've always felt uncomfortable with the fact that
> an assignment in a statement modifier doesn't show up for the
> statement it is modifying, even though it is executed first. I feel
> the following two examples should be equivalent:
>
> if t = foo
> t.bar
> end
>
> -- and:
>
> t.bar if t = foo
>

Hmm, using a Ruby 1.8.x:

irb(main):001:0> class Foo
irb(main):002:1> def name
irb(main):003:2> 'foo'
irb(main):004:2> end
irb(main):005:1> end
=> nil
irb(main):006:0> foo = Foo.new
=> #<Foo:0x2be07f0>
irb(main):007:0> bar.name if bar = foo
NameError: undefined local variable or method `bar' for main:Object
from (irb):7
irb(main):008:0> bar = nil
=> nil
irb(main):009:0> bar.name if bar = foo
=> "foo"

Seems like the same "reading ahead of itself" problem of trying to do
the whiteout quiz on a file without perfect syntax. Example:

irb(main):012:0> 89u33@$T$#$b if false
SyntaxError: compile error
(irb):12: syntax error
89u33@$T$#$b if false
^
from (irb):12

Should you get a syntax error on code never called?

Douglas


Robert Klemme

6/13/2005 7:12:00 AM

0

Douglas Livingstone wrote:
> On 6/12/05, Mark Hubbart <discordantus@gmail.com> wrote:
>> IMHO, it wouldn't be a "heavy language modification" (irrespective of
>> the work required). I've always felt uncomfortable with the fact that
>> an assignment in a statement modifier doesn't show up for the
>> statement it is modifying, even though it is executed first. I feel
>> the following two examples should be equivalent:
>>
>> if t = foo
>> t.bar
>> end
>>
>> -- and:
>>
>> t.bar if t = foo
>>
>
> Hmm, using a Ruby 1.8.x:
>
> irb(main):001:0> class Foo
> irb(main):002:1> def name
> irb(main):003:2> 'foo'
> irb(main):004:2> end
> irb(main):005:1> end
> => nil
> irb(main):006:0> foo = Foo.new
> => #<Foo:0x2be07f0>
> irb(main):007:0> bar.name if bar = foo
> NameError: undefined local variable or method `bar' for main:Object
> from (irb):7
> irb(main):008:0> bar = nil
> => nil
> irb(main):009:0> bar.name if bar = foo
> => "foo"
>
> Seems like the same "reading ahead of itself" problem of trying to do
> the whiteout quiz on a file without perfect syntax. Example:
>
> irb(main):012:0> 89u33@$T$#$b if false
> SyntaxError: compile error
> (irb):12: syntax error
> 89u33@$T$#$b if false
> ^
> from (irb):12
>
> Should you get a syntax error on code never called?

Yes, of course! Syntax analysis comes before execution. Every
programming language (whether compiled or interpreted) needs at least
syntactic correct input.

And btw: don't trust IRB when it comes to local variables!

Kind regards

robert

Nakada, Nobuyoshi

6/13/2005 9:20:00 AM

0

Hi,

At Sun, 12 Jun 2005 00:45:56 +0900,
Gavin Kistner wrote in [ruby-talk:145166]:
> Would it be possible to have the parser recognize the single-line 'if/
> unless' case and process the right side before the left side (the
> order in which the code is executed)?

Not impossible, but a dirty hack because current implementation is one
pass parser.


Index: parse.y
===================================================================
RCS file: /cvs/ruby/src/ruby/parse.y,v
retrieving revision 1.387
diff -U2 -p -u -r1.387 parse.y
--- parse.y 12 Jun 2005 16:56:05 -0000 1.387
+++ parse.y 13 Jun 2005 09:11:49 -0000
@@ -150,4 +150,5 @@ struct parser_params {
NODE *parser_eval_tree_begin;
NODE *parser_eval_tree;
+ NODE *vcalls;
#else
/* Ripper only */
@@ -304,8 +305,16 @@ static void top_local_setup_gen _((struc
#else
#define remove_begin(node) (node)
+/* FIXME */
+# define local_cnt(x) 3
+# define local_id(x) 1
+# define dyna_in_block() 1
#endif /* !RIPPER */
static int lvar_defined_gen _((struct parser_params*, ID));
#define lvar_defined(id) lvar_defined_gen(parser, id)

+#define lvar_count() (dyna_in_block() ? lvtbl->dname_size : lvtbl->cnt)
+static void replace_vcall _((struct parser_params *, int));
+static void merge_vcalls _((struct parser_params *));
+
#define RE_OPTION_ONCE 0x80

@@ -678,4 +687,5 @@ stmts : none
/*%%%*/
$$ = block_append($1, newline_node(remove_begin($3)));
+ merge_vcalls(parser);
/*%
$$ = dispatch2(stmts_add, $1, $3);
@@ -733,9 +743,14 @@ stmt : kALIAS fitem {lex_state = EXPR_F
%*/
}
- | stmt kIF_MOD expr_value
+ | stmt kIF_MOD
+ {
+ $<num>$ = lvar_count();
+ }
+ expr_value
{
/*%%%*/
- $$ = NEW_IF(cond($3), $1, 0);
- fixpos($$, $3);
+ replace_vcall(parser, $<num>3);
+ $$ = NEW_IF(cond($4), $1, 0);
+ fixpos($$, $4);
if (cond_negative(&$$->nd_cond)) {
$$->nd_else = $$->nd_body;
@@ -743,12 +758,17 @@ stmt : kALIAS fitem {lex_state = EXPR_F
}
/*%
- $$ = dispatch2(if_mod, $3, $1);
+ $$ = dispatch2(if_mod, $4, $1);
%*/
}
- | stmt kUNLESS_MOD expr_value
+ | stmt kUNLESS_MOD
+ {
+ $<num>$ = lvar_count();
+ }
+ expr_value
{
/*%%%*/
- $$ = NEW_UNLESS(cond($3), $1, 0);
- fixpos($$, $3);
+ replace_vcall(parser, $<num>3);
+ $$ = NEW_UNLESS(cond($4), $1, 0);
+ fixpos($$, $4);
if (cond_negative(&$$->nd_cond)) {
$$->nd_body = $$->nd_else;
@@ -756,15 +776,20 @@ stmt : kALIAS fitem {lex_state = EXPR_F
}
/*%
- $$ = dispatch2(unless_mod, $3, $1);
+ $$ = dispatch2(unless_mod, $4, $1);
%*/
}
- | stmt kWHILE_MOD expr_value
+ | stmt kWHILE_MOD
+ {
+ $<num>$ = lvar_count();
+ }
+ expr_value
{
/*%%%*/
+ replace_vcall(parser, $<num>3);
if ($1 && nd_type($1) == NODE_BEGIN) {
- $$ = NEW_WHILE(cond($3), $1->nd_body, 0);
+ $$ = NEW_WHILE(cond($4), $1->nd_body, 0);
}
else {
- $$ = NEW_WHILE(cond($3), $1, 1);
+ $$ = NEW_WHILE(cond($4), $1, 1);
}
if (cond_negative(&$$->nd_cond)) {
@@ -772,15 +797,20 @@ stmt : kALIAS fitem {lex_state = EXPR_F
}
/*%
- $$ = dispatch2(while_mod, $3, $1);
+ $$ = dispatch2(while_mod, $4, $1);
%*/
}
- | stmt kUNTIL_MOD expr_value
+ | stmt kUNTIL_MOD
+ {
+ $<num>$ = lvar_count();
+ }
+ expr_value
{
/*%%%*/
+ replace_vcall(parser, $<num>3);
if ($1 && nd_type($1) == NODE_BEGIN) {
- $$ = NEW_UNTIL(cond($3), $1->nd_body, 0);
+ $$ = NEW_UNTIL(cond($4), $1->nd_body, 0);
}
else {
- $$ = NEW_UNTIL(cond($3), $1, 1);
+ $$ = NEW_UNTIL(cond($4), $1, 1);
}
if (cond_negative(&$$->nd_cond)) {
@@ -788,5 +818,5 @@ stmt : kALIAS fitem {lex_state = EXPR_F
}
/*%
- $$ = dispatch2(until_mod, $3, $1);
+ $$ = dispatch2(until_mod, $4, $1);
%*/
}
@@ -805,4 +835,5 @@ stmt : kALIAS fitem {lex_state = EXPR_F
yyerror("BEGIN in method");
}
+ $<node>1 = parser->vcalls;
local_push(0);
/*%
@@ -818,4 +849,5 @@ stmt : kALIAS fitem {lex_state = EXPR_F
NEW_PREEXE($4));
local_pop();
+ parser->vcalls = $<node>1;
$$ = 0;
/*%
@@ -2732,4 +2764,5 @@ primary : literal
yyerror("class definition in method body");
class_nest++;
+ $<node>1 = parser->vcalls;
local_push(0);
$<num>$ = ruby_sourceline;
@@ -2747,4 +2780,5 @@ primary : literal
nd_set_line($$, $<num>4);
local_pop();
+ parser->vcalls = $<node>1;
class_nest--;
/*%
@@ -2768,4 +2802,5 @@ primary : literal
in_single = 0;
class_nest++;
+ $<node>1 = parser->vcalls;
local_push(0);
/*%
@@ -2782,4 +2817,5 @@ primary : literal
fixpos($$, $3);
local_pop();
+ parser->vcalls = $<node>1;
class_nest--;
in_def = $<num>4;
@@ -2798,4 +2834,5 @@ primary : literal
yyerror("module definition in method body");
class_nest++;
+ $<node>1 = parser->vcalls;
local_push(0);
$<num>$ = ruby_sourceline;
@@ -2813,4 +2850,5 @@ primary : literal
nd_set_line($$, $<num>3);
local_pop();
+ parser->vcalls = $<node>1;
class_nest--;
/*%
@@ -2825,4 +2863,5 @@ primary : literal
cur_mid = $2;
in_def++;
+ $<node>1 = parser->vcalls;
local_push(0);
/*%
@@ -2842,4 +2881,5 @@ primary : literal
fixpos($$, $4);
local_pop();
+ parser->vcalls = $<node>1;
in_def--;
cur_mid = $<id>3;
@@ -2854,4 +2894,5 @@ primary : literal
/*%%%*/
in_single++;
+ $<node>1 = parser->vcalls;
local_push(0);
lex_state = EXPR_END; /* force for args */
@@ -2871,4 +2912,5 @@ primary : literal
fixpos($$, $2);
local_pop();
+ parser->vcalls = $<node>1;
in_single--;
/*%
@@ -4317,11 +4359,4 @@ static int parser_here_document _((struc
# define whole_match_p(e,l,i) parser_whole_match_p(parser,e,l,i)

-#ifdef RIPPER
-/* FIXME */
-# define local_cnt(x) 3
-# define local_id(x) 1
-# define dyna_in_block() 1
-#endif /* RIPPER */
-
#ifndef RIPPER
# define set_yylval_str(x) yylval.node = NEW_STR(x)
@@ -7105,5 +7140,5 @@ gettable_gen(parser, id)
/* method call without arguments */
dyna_check(id);
- return NEW_VCALL(id);
+ return parser->vcalls = NEW_NODE(NODE_VCALL,0,id,parser->vcalls);
}
else if (is_global_id(id)) {
@@ -7915,4 +7950,6 @@ new_super(a)
}

+#define NEW_VCALL_LIST(n) NEW_NODE(NODE_VCALL,0,0,n)
+
static void
local_push_gen(parser, top)
@@ -7932,4 +7969,5 @@ local_push_gen(parser, top)
local->dyna_vars = ruby_dyna_vars;
lvtbl = local;
+ parser->vcalls = NEW_VCALL_LIST(parser->vcalls);
if (!top) {
/* preserve reference for GC, but link should be cut. */
@@ -7944,4 +7982,5 @@ local_pop_gen(parser)
{
struct local_vars *local = lvtbl->prev;
+ NODE *vcalls = parser->vcalls;

if (lvtbl->tbl) {
@@ -7952,4 +7991,10 @@ local_pop_gen(parser)
xfree(lvtbl->dnames);
}
+ while (vcalls) {
+ NODE *next = vcalls->nd_next;
+ vcalls->nd_next = 0;
+ vcalls = next;
+ }
+ parser->vcalls = 0;
ruby_dyna_vars = lvtbl->dyna_vars;
xfree(lvtbl);
@@ -8155,4 +8200,61 @@ dyna_init_gen(parser, node, pre)
}

+static void
+merge_vcalls(parser)
+ struct parser_params *parser;
+{
+ NODE **prev, *n;
+
+ for (n = *(prev = &parser->vcalls); n != 0; n = n->nd_next) {
+ if (n->nd_mid == 0) {
+ *prev = n->nd_next;
+ n->nd_next = parser->vcalls;
+ parser->vcalls = n;
+ break;
+ }
+ }
+}
+
+static void
+replace_vcall(parser, vcnt)
+ struct parser_params *parser;
+ int vcnt;
+{
+ NODE **prev, *n;
+ ID id, *names;
+ int i, ncnt;
+ enum node_type type;
+
+ if (dyna_in_block()) {
+ if (!(names = lvtbl->dnames)) return;
+ ncnt = lvtbl->dname_size;
+ type = NODE_DVAR;
+ }
+ else {
+ if (!(names = lvtbl->tbl)) return;
+ names++;
+ ncnt = lvtbl->cnt;
+ if (vcnt < 2) vcnt = 2;
+ type = NODE_LVAR;
+ }
+
+ if (vcnt >= ncnt) return;
+ prev = &parser->vcalls;
+ while ((n = *prev) != 0 && (id = n->nd_mid) != 0) {
+ for (i = vcnt; i < ncnt; ++i) {
+ if (id == names[i]) {
+ *prev = n->nd_next;
+ nd_set_type(n, type);
+ n->nd_mid = 0;
+ n->nd_vid = id;
+ n->nd_cnt = (type == NODE_LVAR) ? i : 0;
+ goto skip;
+ }
+ }
+ prev = &n->nd_next;
+ skip:;
+ }
+}
+
void
rb_gc_mark_parser()
@@ -8554,4 +8656,5 @@ parser_initialize(parser)
parser->parser_eval_tree_begin = 0;
parser->parser_eval_tree = 0;
+ parser->vcalls = 0;
#else
parser->parser_ruby_sourcefile = Qnil;


--
Nobu Nakada


Dave Burt

6/13/2005 12:18:00 PM

0

"Gavin Kistner" <gavin@refinery.com> wrote...
> If I write code like this:
> self.foo.bar if self.foo
> then ruby runs the #foo method twice. (Javascript is the same way.)
>
> If #foo is a lengthy process which I know has no side-effects, this
> is wasteful. But, I need to ensure that #foo didn't return nil. I
> have (at least) three choices:
>
> if ( tmp=foo )
> puts tmp.bar
> end
>
> ( tmp=foo ) && ( puts tmp.bar )
>
> puts tmp.bar if ( tmp=foo )
>
> ..except that the last doesn't work, if 'tmp' has not been
> previously seen as a local variable. Unfortunately, that's the form
> that I really think looks best.

The following method was mentioned a while back, and is a solution to this
problem.

class NilClass
def method_missing() nil end
end

Cheers,
Dave