Introducing the "it" keyword

G

Greg Fodor

A common pattern seen in a lot of ruby code is:

g(m(x)) if/unless/while/until h(m(x))

for example,

puts "User: #{opts[:user]}" if opts[:user]
in this case, g(x) = puts "User: #{x}", h(x) = x, and m(x) =
opts[:user]

or

return v+1 if v +1 < 10
In this case, g(x) = x, h(x) = x < 10, and m(x) = v + 1

This is obviously suboptimal code, as is, because it results in the
evaluation of m(x) twice. I propose a new keyword is added, "it",
which may appear within the statement to the left of the decorator.
So, the previous statements become:

puts "User: #{it}" if opts[:user]

and

return it if |v + 1| < 10

If and only if "it" is seen on the left hand side, the ruby
interpreter should store the expression result on the conditional into
a temporary storage and evaluate that as "it". It falls out of scope
after the statement. The use of pipes can designate a subexpression to
use for "it" instead (I don't have my heart set on pipes, but you get
the idea.)

This keyword allows better DRY in ruby for this g(m(x)) op h(m(x))
pattern and also provides a nice optimization since your average lazy
programmer will usually evaluate m(x) twice instead of putting it into
temporary local storage themselves. By promoting it to a keyword, you
also prevent the problems seen here: http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/166828

Thoughts?
 
D

dblack

Hi --

A common pattern seen in a lot of ruby code is:

g(m(x)) if/unless/while/until h(m(x))

for example,

puts "User: #{opts[:user]}" if opts[:user]
in this case, g(x) = puts "User: #{x}", h(x) = x, and m(x) =
opts[:user]

or

return v+1 if v +1 < 10
In this case, g(x) = x, h(x) = x < 10, and m(x) = v + 1

This is obviously suboptimal code, as is, because it results in the
evaluation of m(x) twice. I propose a new keyword is added, "it",
which may appear within the statement to the left of the decorator.
So, the previous statements become:

puts "User: #{it}" if opts[:user]

and

return it if |v + 1| < 10

If and only if "it" is seen on the left hand side, the ruby
interpreter should store the expression result on the conditional into
a temporary storage and evaluate that as "it". It falls out of scope
after the statement. The use of pipes can designate a subexpression to
use for "it" instead (I don't have my heart set on pipes, but you get
the idea.)

This keyword allows better DRY in ruby for this g(m(x)) op h(m(x))
pattern and also provides a nice optimization since your average lazy
programmer will usually evaluate m(x) twice instead of putting it into
temporary local storage themselves. By promoting it to a keyword, you
also prevent the problems seen here: http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/166828

Thoughts?

I think something like 99% of all suggestions for changes to Ruby
involve new punctuation -- which means that only a very tiny number of
them can be accepted before Ruby turns into complete line noise. So
I'd not advocate the pipes version. Maybe you could do:

return v + 1 if its < 10

which of course is missing an apostrophe, speaking of punctuation :)
But something like that, maybe.


David

--
Q. What is THE Ruby book for Rails developers?
A. RUBY FOR RAILS by David A. Black (http://www.manning.com/black)
(See what readers are saying! http://www.rubypal.com/r4rrevs.pdf)
Q. Where can I get Ruby/Rails on-site training, consulting, coaching?
A. Ruby Power and Light, LLC (http://www.rubypal.com)
 
B

Bill Guindon

Hi --

A common pattern seen in a lot of ruby code is:

g(m(x)) if/unless/while/until h(m(x))

for example,

puts "User: #{opts[:user]}" if opts[:user]
in this case, g(x) = puts "User: #{x}", h(x) = x, and m(x) =
opts[:user]

or

return v+1 if v +1 < 10
In this case, g(x) = x, h(x) = x < 10, and m(x) = v + 1

This is obviously suboptimal code, as is, because it results in the
evaluation of m(x) twice. I propose a new keyword is added, "it",
which may appear within the statement to the left of the decorator.
So, the previous statements become:

puts "User: #{it}" if opts[:user]

and

return it if |v + 1| < 10

If and only if "it" is seen on the left hand side, the ruby
interpreter should store the expression result on the conditional into
a temporary storage and evaluate that as "it". It falls out of scope
after the statement. The use of pipes can designate a subexpression to
use for "it" instead (I don't have my heart set on pipes, but you get
the idea.)

This keyword allows better DRY in ruby for this g(m(x)) op h(m(x))
pattern and also provides a nice optimization since your average lazy
programmer will usually evaluate m(x) twice instead of putting it into
temporary local storage themselves. By promoting it to a keyword, you
also prevent the problems seen here: http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/166828

Thoughts?

I think something like 99% of all suggestions for changes to Ruby
involve new punctuation -- which means that only a very tiny number of
them can be accepted before Ruby turns into complete line noise. So
I'd not advocate the pipes version. Maybe you could do:

return v + 1 if its < 10

which of course is missing an apostrophe, speaking of punctuation :)
But something like that, maybe.

Well, you fixed the thing that was bothering me. In the original
proposal, 'it' appeared too early, leaving me wonder what 'it' was.
It makes sense for 'it' to follow what 'it' is referring to.

You could solve the apostrophe problem with the longer 'it_is'.

Overall, I like the idea. It solves the lazy programmer problem which
leads to double evaluation of the same expression Those who take the
effort to assign it to a temp variable, can now be lazy, and do it
with one line.
 
D

Daniel DeLorme

Greg said:
return it if |v + 1| < 10

No need for it to be such a special built-in construct.
Maybe you could do it like this:

def it(*args)
$it = args unless args.empty?
return *$it
end
puts it if it(v+1) < 10


Daniel
 
G

Greg Fodor

Maybe you could do:
return v + 1 if its < 10

-- snip

return v + 1 if < 10

Unfortunately this seems much more difficult and ambiguous
for a number of reasons. First, it is impossible to know
that our implied "it" should be "v + 1" without making
assumptions about the intent. (Why don't we include the
result of the return expression itself? What if it's more
complex?)

More importantly, semantics of order of evaluation become
confusing. Despite v + 1 being evaluated during the
if conditional, it syntactically appears in the return
statement. My approach preserves all current ruby semantics
and is more intentional, I feel. It basically transforms
this:

tmp = m(x)
h(tmp) if g(tmp)

into just

h(it) if g(it = m(x))

The it keyword is essential for one reason and nice for
a few others. It is essential because of the ruby semantics
for determining identifiers vs. methods, referenced in my
original post.

It is nice because we no longer have to come up with the
name "tmp,"ruby provides a consistent intentional name.
It is also nice because it is less code. It is also nice
because it provides correct scope semantics so it will be
GCed at the optimal time.
No need for it to be such a special built-in construct.

Depends on what you mean by "need." For an optimal solution,
I feel it does. "Optimal" means:

- It only evaluates m(x) once (yours does this)
- It is the most terse representation of intent (subjective)
- It introduces the correct scope (yours does not)
- It releases memory upon GC after the statement (yours doesn't)
- It avoids side effects (yours changes global space)

Its hard to think of a solution to this implemented in ruby
that fits these parameters without introducing a new
language level construct.
 
M

Michael W. Ryder

Greg said:
Unfortunately this seems much more difficult and ambiguous
for a number of reasons. First, it is impossible to know
that our implied "it" should be "v + 1" without making
assumptions about the intent. (Why don't we include the
result of the return expression itself? What if it's more
complex?)

More importantly, semantics of order of evaluation become
confusing. Despite v + 1 being evaluated during the
if conditional, it syntactically appears in the return
statement. My approach preserves all current ruby semantics
and is more intentional, I feel. It basically transforms
this:

tmp = m(x)
h(tmp) if g(tmp)

into just

h(it) if g(it = m(x))

The it keyword is essential for one reason and nice for
a few others. It is essential because of the ruby semantics
for determining identifiers vs. methods, referenced in my
original post.

It is nice because we no longer have to come up with the
name "tmp,"ruby provides a consistent intentional name.
It is also nice because it is less code. It is also nice
because it provides correct scope semantics so it will be
GCed at the optimal time.


Depends on what you mean by "need." For an optimal solution,
I feel it does. "Optimal" means:

- It only evaluates m(x) once (yours does this)
- It is the most terse representation of intent (subjective)
- It introduces the correct scope (yours does not)
- It releases memory upon GC after the statement (yours doesn't)
- It avoids side effects (yours changes global space)

Its hard to think of a solution to this implemented in ruby
that fits these parameters without introducing a new
language level construct.

How about something like: return $1 if (v + 1) < 10 if you wanted to
return v + 1? If you wanted to return v you could use: return $2 if
((v) +1) < 10. Improvements would be using some of the regular
expression flags to control what $1, $2, etc. refer to.
 
G

Greg Fodor

How about something like: return $1 if (v + 1) < 10 if you wanted to
return v + 1? If you wanted to return v you could use: return $2 if
((v) +1) < 10. Improvements would be using some of the regular
expression flags to control what $1, $2, etc. refer to.
I like this better than def it() since it seems more intentional,
but it too results in side effects into global space and prevents
GCing early.

Another problem is that you run into problems mixing the
meaning of parenthesis, since within a given expression in your
example you may not want to set aside storage space for all
parenthesized expressions, despite the fact you may need to enforce
order of operations.

For example:

puts $2 if (x + y) * (a - b) < 10

will copy the result of x + y in RAM even if your parentheses are
there to simply make the add happen first. Having it be something
new in this context like pipes separates concerns and avoids these
technical consequences.

FYI, this would be:

puts it if (x + y) * |(a - b)| < 10

We just get temp storage for a - b and preserve a single meaning
of parentheses.
 
G

Greg Fodor

Actually, this example:

puts $2 if (x + y) * (a - b) < 10

Could avoid storing (x + y) by analyzing the AST
of the puts expression, noting that $1 does not appear.
Recall a similar optimization happens with the proposed it
keyword if it is not used on the left hand side.

So, that point about RAM I made above is partially wrong,
except for the fact that analyzing the AST for the presence
of a keyword for optimization reasons seems less kludgy than
analyzing it for references to global variables $1, $2..
(since they are in the runtime's name space as opposed to the
language syntax itself.)
 
M

Michael W. Ryder

Greg said:
I like this better than def it() since it seems more intentional,
but it too results in side effects into global space and prevents
GCing early.

Another problem is that you run into problems mixing the
meaning of parenthesis, since within a given expression in your
example you may not want to set aside storage space for all
parenthesized expressions, despite the fact you may need to enforce
order of operations.

For example:

puts $2 if (x + y) * (a - b) < 10

will copy the result of x + y in RAM even if your parentheses are
there to simply make the add happen first. Having it be something
new in this context like pipes separates concerns and avoids these
technical consequences.

That was why I had put in the comment about using flags from regular
expressions to control whether something was added to the global
variables. I am still reading about regular expressions and don't
remember the flags in question. This would also prevent extraneous data
being placed in the global variables which was another of your concerns.
FYI, this would be:

puts it if (x + y) * |(a - b)| < 10

My problem, and probably that of a lot of other people, is that it is
almost impossible to scan over a line like that and figure out what is
going on. If I am trying to debug a program I want the code to be as
clear as possible. I can see right away that something is placed on the
screen if the expression evaluates to < 10, but what? Now I have to
stop and try and remember how this is evaluated. Is it the information
in the first set of parentheses or between the pipes or something else?
This break in concentration can be a real killer in finding obscure bugs.
My proposal also suffers from some of this problem but as it is
"inherited" from regular expressions it may be easier to figure out.
 
G

Greg Fodor

This break in concentration can be a real killer in finding obscure bugs.
My proposal also suffers from some of this problem but as it is
"inherited" from regular expressions it may be easier to figure out.
I understand these concerns. The pipes are just sugar, it seems like
it
would be a small learning curve to me, but here are a few other ideas:

puts it if (x + y) * (it = (a - b)) < 10
puts it if (x + y) * ((a - b) as_it) < 10
puts it if (x + y) * (<it>(a - b)) < 10

It's not quite convincing that having an explicit relationship between
it => pipes, once learned, is more difficult to read/parse in your
head than scanning parentheses to figure out which are being used
as storage and which are not. (And if you said $8, which one is
actually
$8 and not $7! :))

When I first learned Ruby, reading:

return x if x < 5

Was really counterintuitive at first cause my brain was used to
thinking
"if I see 'return', it's going to return if I got to that line in the
code." I had to unlearn that assumption, and read the full line
checking
for the trailing condition.

I see that mental leap as being many times more difficult to learn
than
associating an expression wrapped in pipes (or other sugar) with the
'it'
keyword.

Although, I'll grant that if there is a solution that functionally
meets
my list that uses idioms from another domain like regular expressions
it
might be a good contender. Maybe you can explain more about the flags
thing with an example to see how it feels vs. the pipes approach?
 
M

Michael W. Ryder

Greg said:
I understand these concerns. The pipes are just sugar, it seems like
it
would be a small learning curve to me, but here are a few other ideas:

puts it if (x + y) * (it = (a - b)) < 10
puts it if (x + y) * ((a - b) as_it) < 10
puts it if (x + y) * (<it>(a - b)) < 10

It's not quite convincing that having an explicit relationship between
it => pipes, once learned, is more difficult to read/parse in your
head than scanning parentheses to figure out which are being used
as storage and which are not. (And if you said $8, which one is
actually
$8 and not $7! :))

When I first learned Ruby, reading:

return x if x < 5

Was really counterintuitive at first cause my brain was used to
thinking
"if I see 'return', it's going to return if I got to that line in the
code." I had to unlearn that assumption, and read the full line
checking
for the trailing condition.

As I have been programming in Business Basic for over 25 years I am used
to seeing statements like: IF A$="" RETURN so I didn't have any problems
with conditional returns.
I see that mental leap as being many times more difficult to learn
than
associating an expression wrapped in pipes (or other sugar) with the
'it'
keyword.

Although, I'll grant that if there is a solution that functionally
meets
my list that uses idioms from another domain like regular expressions
it
might be a good contender. Maybe you can explain more about the flags
thing with an example to see how it feels vs. the pipes approach?

If you had a statement like : puts $1 if (?:x + y) + (z * 2) < 10, then
the program would print z * 2 if the expression evaluated to less than
10. The ?: flag tells the parser to not store the value of x + y.
 
A

Aureliano Calvo

Well, you can do something very similar without changing ruby:

something like:

puts "User: #{it}" if (it=opts[:user]).

and you can do other things like:

puts "This: #{this} and That: #{that}" if (this=opts[:a]) and (that=opts[:b]).

I think this modification is not worth it. But I might be wrong.

Aureliano.
 
G

Greg Fodor

puts "User: #{it}" if (it=opts[:user]).

AFAIK this doesn't work because it will be assumed to
be a method call. That's the crux of the linked post
above.
 
T

Trans

puts "User: #{it}" if (it=opts[:user]).

AFAIK this doesn't work because it will be assumed to
be a method call. That's the crux of the linked post
above.

I believe this is to be fixed in a future version of Ruby.

While we're at it I have another use for "it" I'd appreciate very
much. An (it)erator reference.
0..3.each{ p it.index }
0
1
2
3

"Blech" to _with_index.

T.
 
G

Greg Fodor

While we're at it I have another use for "it" I'd appreciate very
much. An (it)erator reference.
Wow. Yes! Only issue I see is nested blocks though.
(Which it is "it?" :)) Spose it could through a warning if 'it'
comes into scope twice.

But this would be cool:

my_strings.map { pluralize it }.filter { it =~ /tests/ }.each { p it }

The less silly variable names we have to come up with, the better!
 
C

Charles Oliver Nutter

Greg said:
Its hard to think of a solution to this implemented in ruby
that fits these parameters without introducing a new
language level construct.

I'm not opposed to the idea, but I really, really hate "it". "it"
doesn't mean anything. "it" might as well be "that" or "him" or
"this"...oh wait, perhaps not "this".

How about "her"? We don't have a good ratio of women involved in Ruby
anyway, so we can feel better about ourselves then.

What about a "local global" like we have for other things ($_ and $~ for
example?

return $RESULT if v + 1 < 10

-or-

return $result if v + 1 < 10

Although this raises a question....is it supposed to return the result
of "v + 1" or "v + 1 < 10"?

In general I'd say introducing a keyword is the last thing that should
be considered.

- Charlie
 
G

Greg Fodor

return $result if v + 1 < 10
Although this raises a question....is it supposed to return the result
of "v + 1" or "v + 1 < 10"?
As proposed right now, by default, it will be set to the value of
the expression (v + 1 < 10), unless a subexpression is specified.

I proposed pipes to specify such a subexpression and Michael proposed
inheriting the regex style of ordered paren grouping, which has its
own set of pros and cons.

Personally, I like 'it' :) It reads nicely and makes sense to me.

It really *shouldn't* mean or imply anything about it's value,
because "it" is whatever you're currently most likely to be 'talking
about.' I'd argue that the keyword should only be used for short
blocks or expressions, otherwise you're right, it may get confusing.

For example:

return it if |x| < 5

and

strings.map { pluralize it }

are good but

if |x| < 5
.. lots of code ..
my_var = it * 5
end

and

strings.map do
.. lots of code ..
pluralize it
end

Are less desirable.

"it" seems best to be used for short one or two liners, not
sprinkled throughout long block bodies. This may or not be
something we'd even want the interpreter to enforce.
 
N

Nobuyoshi Nakada

Hi,

At Fri, 25 May 2007 08:20:09 +0900,
Greg Fodor wrote in [ruby-talk:252866]:
Depends on what you mean by "need." For an optimal solution,
I feel it does. "Optimal" means:

- It introduces the correct scope (yours does not)
- It avoids side effects (yours changes global space)

By using $_ instead of $it, these will solve.
- It releases memory upon GC after the statement (yours doesn't)

$_ releases after the method.
 
N

Nobuyoshi Nakada

Hi,

At Fri, 25 May 2007 12:55:09 +0900,
Greg Fodor wrote in [ruby-talk:252899]:
But this would be cool:

my_strings.map { pluralize it }.filter { it =~ /tests/ }.each { p it }

p *my_strings.map:)pluralize).grep(/tests/)
 
M

Michael Fellinger

def it
@it = block_given? ? yield : @it
end

p it if it{ 'bar' }

Hi,

At Fri, 25 May 2007 12:55:09 +0900,
Greg Fodor wrote in [ruby-talk:252899]:
But this would be cool:

my_strings.map { pluralize it }.filter { it =~ /tests/ }.each { p it }

p *my_strings.map:)pluralize).grep(/tests/)
 

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

Staff online

Members online

Forum statistics

Threads
473,767
Messages
2,569,571
Members
45,045
Latest member
DRCM

Latest Threads

Top