Only if the object exists

G

Gavin Kistner

--Apple-Mail-1-611104768
Content-Transfer-Encoding: 7bit
Content-Type: text/plain;
charset=US-ASCII;
delsp=yes;
format=flowed

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


--Apple-Mail-1-611104768--
 
D

Douglas Livingstone

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

I'd probably go for:

result =3D foo
result.bar unless result.nil?

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

t =3D foo and puts t.bar

but you still have the temp var.

Douglas
 
G

Gavin Kistner

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

Robert Klemme

Gavin Kistner said:
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
 
M

Mark Hubbart

=20
Why change the parser if there are ways to do this that don't require suc= h
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 =3D foo
t.bar
end

-- and:

t.bar if t =3D 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 =3D foo
and t.bar", which doesn't read as clearly to me.

cheers,
Mark
 
G

Gavin Kistner

--Apple-Mail-4-699972693
Content-Transfer-Encoding: quoted-printable
Content-Type: text/plain;
charset=ISO-8859-1;
delsp=yes;
format=flowed

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

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

tmp.bar if tmp=3Dfoo

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

if tmp=3Dfoo
tmp.bar
end

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

tmp =3D x if false

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

def foo
10.times{ i =3D i ? i+1 : 1 }
puts i =3D i ? i+1 : 1
10.times{ i =3D i ? i+1 : 1 }
puts i =3D 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 =20=

not the same throughout the same block. If this is on purpose and/or =20
really cool and useful in situations I haven't considered, then I'll =20
accept "tmp.bar if tmp=3Dfoo" not working as a necessary casualty. But =20=

it seems to me that the variable variable scope may be due to an =20
implementation flaw in the pre-processing of code, not a conscious =20
design decision.

I'm not even proposing the 'massive' undertaking of scanning the =20
entire method for "tmp\s*=3D" outside of any blocks to predetermine if =20=

tmp should be local (though IMO that should be done). I'm simply =20
suggesting that the parser conceptually invert the order of single-=20
line "yyyy if/unless xxxx" to process xxxx first.



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

(foo || DUMMY).bar
Interesting!


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

tmp =3D foo and tmp.bar

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

--Apple-Mail-4-699972693--
 
D

Douglas Livingstone

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

{ i =3D 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 =3D 0
10.times{ i =3D i ? i+1 : 1 }
puts i =3D i ? i+1 : 1
10.times{ i =3D i ? i+1 : 1 }
puts i =3D i ? i+1 : 1
end

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

Duglas
 
D

Douglas Livingstone

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:
=20
if t =3D foo
t.bar
end
=20
-- and:
=20
t.bar if t =3D foo
=20

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
=3D> nil
irb(main):006:0> foo =3D Foo.new
=3D> #<Foo:0x2be07f0>
irb(main):007:0> bar.name if bar =3D foo
NameError: undefined local variable or method `bar' for main:Object
from (irb):7
irb(main):008:0> bar =3D nil
=3D> nil
irb(main):009:0> bar.name if bar =3D foo
=3D> "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
 
R

Robert Klemme

Douglas said:
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
 
N

nobuyoshi nakada

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) {
+ *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;
 
D

Dave Burt

Gavin Kistner said:
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
 

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,755
Messages
2,569,536
Members
45,009
Latest member
GidgetGamb

Latest Threads

Top