Introducing the "it" keyword

Greg Fodor wrote:

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.

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.

···

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

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/)

···

--
Nobu Nakada

I would say that it would always belong to the current block. If you
need "it" from the parent block it's easy enough to assign a variable
to it.

However, I wasn't intending that 'it' reference the block arguments,
but the iteration itself, with useful methods about the state of
iteration, eg. it.first?, it.last?, it.even?, it.odd?, it.index. Of
course this could also include it.value.

T.

···

On May 24, 11:55 pm, Greg Fodor <gfo...@gmail.com> wrote:

> 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!

> This has been pointed out already in this thread.
This has also been pointed out as *not* working :slight_smile:

You are right I got caught by irb - again. I did not see the post telling so,
sorry for the noise.

sum is previously defined it will not, and if it is, it will
hold sum too long.

That however could be disputed, but is not of interest any more :frowning:

<snip>
Robert

···

On 5/25/07, Greg Fodor <gfodor@gmail.com> wrote:
--
You see things; and you say Why?
But I dream things that never were; and I say Why not?
-- George Bernard Shaw

irb(main):001:0> def it_test(v)
irb(main):002:1> (v+1).instance_eval do
irb(main):003:2* return self if self < 10
irb(main):004:2> end
irb(main):005:1> "nothing"
irb(main):006:1> end

You can extend it and let the method do the work for you

def do_if(var, cond)
  yield var if cond[var]
end

do_if(Person.new, proc{|person| person.name == 'John'}) do |person|
  puts person
end

but IMO that's not more efficient than allocating a temporal variable,
except maybe for the persistence of the variable. Perhaps it has
better uses.

irb(main):001:0> def it_test(v)
irb(main):002:1> (v+1).instance_eval do
irb(main):003:2* return self if self < 10
irb(main):004:2> end
irb(main):005:1> "nothing"
irb(main):006:1> end
=> nil
irb(main):007:0> it_test 0
=> 1
irb(main):008:0> (0..10).map {|i| it_test i}
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, "nothing", "nothing"]

That is something I am surprised about, b/c the intention was to have
an idiom for
return it if h(x) < 10
and
return it_test(h(x))
really is not the same, right?

Cheers
Robert

···

On 5/26/07, Robert Klemme <shortcutter@googlemail.com> wrote:

Kind regards

        robert

--
You see things; and you say Why?
But I dream things that never were; and I say Why not?
-- George Bernard Shaw

The OP brought up an interesting issue, but what made it
interesting is not
the implicit binding but the need for a scoped temporary
value. There are
three important optimization goals to it:

1) avoid holding onto memory any longer than necessary (memory)
2) avoid clumsy use of temporary values (readability)
3) avoid recomputing the same value more than once (speed)

See
Why are local variables bad?
for a discussion of local variables and better ways of doing things
(including the functional let).

Well, that article raises the issue that mutal local variables are
evil. I have sympathy with this statement, though I would like to
add that mutal global variable are evil as well, and your hint of
using functional let (which, BTW, was also the inspiration to my
usage of let blocks) just emphasizes that a bit more functional style
could make the world of programming less evil. But while I certainly
would cheer any new element of Functional Programming in Ruby, certainly
Ruby is not Haskell or Scheme. If we go into *that* direction, maybe we
should
start the discussion from a completely different end? How much
additional
FP would enhance Ruby?

Importantly, an implicit variable name has two problems.
First, it reduces
readability. Second, even if naming the variable explicitly
is optional
rather than required, you get the same sorts of maintenance
problems caused
by the optionally braceless block notation for single
statements in C-derived
syntaxes, e.g.

let Time.now in
  puts "starting at #{$1}"
  let a+b+c in
    puts "sum = #{$1}" if $1 > 3
  end
end

Though I for sure can imagine examples, where implicit variables make it
hard to read, your example is, IMO, very easy to follow: You can clearly
see that the first $1 is bound to the time, and the second one is bound
to the sum. But I am well aware that the issue of how/to what extent
variables should be named, is a controversial one, and I wouldn't enjoy
opening that bag of worms here.

Maybe I'm now bringing too much Perl-spirit to the Ruby discussion, but
IMO if a sufficiently large number of users would find some language
feature
useful (and I mean this in a general sense, since I don't know how many
users the "it"/"let"/... stuff would really appreciate), readibility
issues
should be more on the users side (project management), not on the side
of the
language designers. Just my opinion, and I know well that quite a few
will
oppose me here...

...is as bad an issue as...

for (int i=0; i<5; ++i)
  printf("%d\n", i);
  fprintf(stderr, "%d\n", i);

Basically, if you need to add an outer scope, you have to
renumber all of
the temporaries.

Not if you count them from inside out, as I did it. Of course
you would have to renumber if you add an inner scope, such as
when changing

  let foo() in
    x1($1)
    x2($1)
    x3($1)
    x4($1)
    x5($1)

into

  let foo() in do
    x1($1)
    x2($1)
    let bar() in do
      y0($1)
      x3($2) # <--- renumbering necessary
      y1($1)
    end
    x4($1)
    x5($1)
  end

but as I said, I would use anonymous variables only in a *very* local
way,
not for a longer piece of code. In the example above, I would likely
have
assigned a name foo(), but perhaps used anonymous $1 for bar(). Sure,
code
*is* undergoing changes, and I can imagine cases where I would have to
renumber (or, more likely, replace my anonymous variable by a name later
on).
It's just that I don't feel this as painful, if it happens only here and
then.

Ronald

Implicit variables make me queasy. I don't like Perl's $_, I don't like x86
assembly's numerous ops with implicit source and/or destination registers,
and I don't like a magical "it" appearing out of nowhere.

I couldn't agree more.

I think if we only want to introduce an auxiliary variable as
"abbreviation" for an expression which occurs repeatedly in a block, Ruby
indeed has many ways to do it, as do most other languages I know. The
problem becomes interesting IMO if we are libarated from the requirement
to invent a name for such a variable.

The OP suggested to have a "reserved variable" named 'it' which is kind
of implicitly bound, but this is a special case for a special type of
programming pattern. My alternative suggestion go into a more general
direction, where you can have more than one binding, and where it is the
programmer's choice whether or not to name the variables.

The OP brought up an interesting issue, but what made it interesting is not
the implicit binding but the need for a scoped temporary value. There are
three important optimization goals to it:

1) avoid holding onto memory any longer than necessary (memory)
2) avoid clumsy use of temporary values (readability)
3) avoid recomputing the same value more than once (speed)

3 is also about semantics which might even be more important than speed. There can be a huge semantic difference between evaluating an expression once or twice.

Other than that I totally agree with your analysis. This sums it up very nicely!

See Why are local variables bad?
for a discussion of local variables and better ways of doing things
(including the functional let).

Importantly, an implicit variable name has two problems. First, it reduces
readability. Second, even if naming the variable explicitly is optional
rather than required, you get the same sorts of maintenance problems caused
by the optionally braceless block notation for single statements in C-derived
syntaxes, e.g.

let Time.now in
  puts "starting at #{$1}"
  let a+b+c in
    puts "sum = #{$1}" if $1 > 3
  end
end

..is as bad an issue as...

for (int i=0; i<5; ++i)
  printf("%d\n", i);
  fprintf(stderr, "%d\n", i);

Basically, if you need to add an outer scope, you have to renumber all of
the temporaries. This is unnecessary and unreasonable pain to inflict on
maintainers of the code, even if it's your own code.

Looking at it from a different perspective, consider whether any proposed
change to Ruby moves it closer to language X (which I'm equating to Perl)
or closer to language Y (which I'm equating to Lisp). Moving toward X means
making things easy to express tersely at the expense of overall readability
and maintainability. Moving toward Y means simplifying the expression of
things using existing syntax and functionality. I claim that we should
strive to avoid shortcuts that bring Ruby closer to X and strive to solve
our problems in ways that bring Ruby closer to Y.

I kind of agree although I am not sure whether I'd take Lisp as prototype. I believe it is a narrow path to make a language readable and maintainable. The fact that Lisp's macros blend in with the language syntax is very nice on one hand because it allows to define "new syntax" easily and make certain constructs look familiar. On the other hand the missing syntactic distinction between macro and function invocation makes it hard to understand what's going on at times. Maybe it's just me lacking Lisp practice but I think it's a double edged sword.

Thank you for your analysis!

Kind regards

  robert

···

On 29.05.2007 15:17, Gregory Seidman wrote:

On Tue, May 29, 2007 at 04:55:01PM +0900, Ronald Fischer wrote:

dblack@wobblini.net wrote:

The argument from precedent is tricky, though. I'd say the presence
of $_ and $~ and friends suggests, not that more local globals are
desireable, but that Ruby has reached, and perhaps exceeded, its quota
of them :slight_smile:

No argument from me :slight_smile:

I still think "it" is a terrible keyword to introduce though.

- Charlie

Looking at it from a different perspective, consider whether any proposed
change to Ruby moves it closer to language X (which I'm equating to Perl)
or closer to language Y (which I'm equating to Lisp). Moving toward X means
making things easy to express tersely at the expense of overall readability
and maintainability. Moving toward Y means simplifying the expression of
things using existing syntax and functionality. I claim that we should
strive to avoid shortcuts that bring Ruby closer to X and strive to solve
our problems in ways that bring Ruby closer to Y.

I should just point out that Ruby is not Lisp, and that whilst Lisp has many admirable traits, there are many Ruby programmers who like it that way.

On to the topic in question: the idea of the OP that we should be able to write

  return it if |v + 1| < 10

addresses a very real issue as the fragments

  t = f(n)
  return t if t != x

and

  t = f(n)
  return t unless t == x

pop up regularly in my code. As the whole bundle is a single operation it _should_ be possible to write it in atomic form along the lines of

  return %$ if f(n) != x
  return %$ unless f(n) == x
  puts %$[0], %$[1], %$[2] if f(n) > 0

where %$ is a placeholder for substitution of the value returned by f(n).

I've used %$ in these examples due to the conceptual similarity to substitution in the sprintf method and the use of subscripts adds the possibility of consuming multiple return values, although I'm not suggesting it's a _good_ notation :slight_smile: Whatever the specific token, I think punctuation would be a better choice than a keyword because it focuses the eye on the fact that something special is happening and makes explicit that neither a local variable nor a function call is being evaluated where the substituted value is being used. Under the hood the interpreter could avoid messing with local variables, the result would be generated just once, it could be garbage collected immediately on completion and probably all kinds of cleverness could be applied.

Of course this approach fails to solve other common cases such as

  return f(n), f(m) if f(n,m) == x

Ellie

Eleanor McHugh
Games With Brains

···

On 29 May 2007, at 14:17, Gregory Seidman wrote:
----
raise ArgumentError unless @reality.responds_to? :reason

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?

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

p it if it{ 'bar' }

···

On 5/25/07, Nobuyoshi Nakada <nobu@ruby-lang.org> wrote:

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/)

--
Nobu Nakada

Nobuyoshi Nakada wrote:

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/)

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

works better. =)

···

--
Ola Bini (http://ola-bini.blogspot.com) JvYAML, RbYAML, JRuby and Jatha contributor
System Developer, Karolinska Institutet (http://www.ki.se)
OLogix Consulting (http://www.ologix.com)

"Yields falsehood when quined" yields falsehood when quined.

$_ releases after the method.

Ah. This works today:

return $_ if $_ = opts[:user]

But it's still hard to read :slight_smile:

And don't forget:

$_ = "foo"
puts $_ if ($_ = "bob" && chomp)

will print "foo"! (I feel like I'm back writing Perl again)

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

Ah, yes, my ruby-fu is lacking, but it was a contrived
example :slight_smile: Here's another contrived one.. the gist is
that you may need to use "it" more than just as a passthrough:

my_strings.map { it.substring(it.maxlen) }.filter { it.size > 5 }.each
{ p "#{it}: #{it.size}" }

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

p it if it{ 'bar' }

Neat. Instance variable could leak, but still neat!

And Re: Rspec, well, if the powers that be (not me) like 'it',
then I guess they'll have to move out of the way :slight_smile:

I think something like this would be interesting, but "it" would be
easy to confuse with "if", among other things, and it is still a very
special case.

Oftentimes I find myself doing assignments in conditionals, such as:

if foo
  blahbalh
elsif obj = Foo.find_or_something # assignment intended!
  obj.whatever
end

Going along the lines of a syntax already provided by rescue (block-form):

if something
  # it would be silly to do the m(x) call before here as it might not be needed
  blahblahblah
elsif m(x) => foo
  foo.whatever
end

...and extending to the single case which seems to be an extension of
the general:
puts foo if m(x) => foo

Then you run into issues of where foo should be scoped (as if does not
open up a new binding)

irb(main):001:0> def it_test(v)
irb(main):002:1> (v+1).instance_eval do
irb(main):003:2* return self if self < 10
irb(main):004:2> end
irb(main):005:1> "nothing"
irb(main):006:1> end
=> nil
irb(main):007:0> it_test 0
=> 1
irb(main):008:0> (0..10).map {|i| it_test i}
=> [1, 2, 3, 4, 5, 6, 7, 8, 9, "nothing", "nothing"]

That is something I am surprised about, b/c the intention was to have
an idiom for
return it if h(x) < 10

No, it's "return it if it < 10" with "it" being "v+1" in this case.

and
return it_test(h(x))
really is not the same, right?

It's not the same but I believe you misread the task. it_test is just a method to demonstrate the idiom in lines 2-4.

m(int->int): x -> x+1
h(int->bool): x -> x<10
g(int->int): x -> x

x: v

(see also Greg's original posting)

Kind regards

  robert

···

On 26.05.2007 23:37, Robert Dober wrote:

On 5/26/07, Robert Klemme <shortcutter@googlemail.com> wrote:

Hi all,

That is a very interesting and educating discussion :slight_smile:

Lately, I came up with #dump!. What is #dump, you ask? It is a
solution to my problem with #p :

Let's say that I have the line of code:
  v = matrix*Vector[1, 1, function(a)] + vector.invert
Now, let's say that I think I have a bug in there.

Normally, if I'm not into using a debugger (perhaps because that's in
an inner loop and I want to filter the one buggy run from among 1000),
I am forced to do this:
  f_a = function(a)
  v_i = vector.invert
  m_v = matrix * Vector[1, 1, f_a]
  v = m_v + v_i
  p "debug:", f_a, v_i, m_v, v
and of course, after I'm done, re-structure the code because it's an
ugly way of doing things in a language that doesn't use polish
notation.

Here's my solution:

module Kernel
  def dump!(message = nil)
    print " ::: #{caller[1]} ::: "
    print "#message} ::: " if message
    puts self.inspect # well, actually I use #to_yaml
    return self
  end

and lo!:
  v = matrix*Vector[1, 1, function(a).dump!("debugging that v
bug")].dump! + vector.invert.dump!
  v.dump! # I could put it in the prev line with parens, but that's ugly
no restructuring needed. This can go anywhere. And in the less messy
cases, I don't even need to remove those dump! calls, I can just
redefine dump! to return self silently.

That, IMHO, is "the Ruby Way".

In the same code that made me implement that, I've had many cases that
call for "it", and used the simpler techniques in this thread to
overcome it (placing "(a == stuff) and rest_of_expression" within an
if, using a temp var, etc').

Here's what I'm gonna do now:

module Kernel
  def it!
    $sonoflilit_it = self
  end
  def it
    $sonoflilit_it
  end
end

return it if (heavy_calc(param, param2, param3.moreheavylifting(param4)).it!

or

it.print if matrix.inverse.it!.is_what_i_want
nil.it!

There still is the problem of garbage collection. nil.it! solves it
manually in those critical cases, like it being a large matrix.

An alternative version would be:

module Kernel
  def it!(key = :it_secret_default_key)
    $sonoflilit_its[key] = self
  end
  def it(key = :it_secret_default_key)
    $sonoflilit_its[key]
  end
  def pop_it(key = :it_secret_default_key)
    $sonoflilit_its.delete(key) # notice that it returns it. might be useful
  end
  def clean_its
    $sonoflilit_its.clean
  end
end

for cases like:

if a.calculation.it!(:a).abs > b.calculation2.it!(:b).real_part
  puts it(:a).det
else
  puts func(it(:b))
end
clean_its

While previous examples work without change (though the idiom of
nil.it! isn't recommended for the nil.it(:sym) case, since the hash
will still keep a member there and with time it could become memory
consuming).

This is not a general solution, and might look bad to some, but it
serves me well. Feel free to paste it into your own project.

Aur Saraf

···

On 5/30/07, Charles Oliver Nutter <charles.nutter@sun.com> wrote:

dblack@wobblini.net wrote:
> The argument from precedent is tricky, though. I'd say the presence
> of $_ and $~ and friends suggests, not that more local globals are
> desireable, but that Ruby has reached, and perhaps exceeded, its quota
> of them :slight_smile:

No argument from me :slight_smile:

I still think "it" is a terrible keyword to introduce though.

- Charlie

Eleanor McHugh wrote:

Looking at it from a different perspective, consider whether any proposed
change to Ruby moves it closer to language X (which I'm equating to Perl)
or closer to language Y (which I'm equating to Lisp). Moving toward X means
making things easy to express tersely at the expense of overall readability
and maintainability. Moving toward Y means simplifying the expression of
things using existing syntax and functionality. I claim that we should
strive to avoid shortcuts that bring Ruby closer to X and strive to solve
our problems in ways that bring Ruby closer to Y.

I should just point out that Ruby is not Lisp, and that whilst Lisp has many admirable traits, there are many Ruby programmers who like it that way.

On to the topic in question: the idea of the OP that we should be able to write

    return it if |v + 1| < 10

addresses a very real issue as the fragments

    t = f(n)
    return t if t != x

and

    t = f(n)
    return t unless t == x

pop up regularly in my code. As the whole bundle is a single operation it _should_ be possible to write it in atomic form along the lines of

    return %$ if f(n) != x
    return %$ unless f(n) == x
    puts %$[0], %$[1], %$[2] if f(n) > 0

While I realize the Ruby is not C, I think that some of the style guidelines for C should still apply in Ruby. One of the ones that was very important was to make each statement do only one thing. Your example of setting a temporary variable to an amount on one line and then testing that variable on the second line is much easier to read, and understand, quickly than your statements like 'return %$ unless f(n) == x'. This becomes even more important when the test is even more convoluted such as checking for the position of a string in another string and returning if it is in a specific location.

···

On 29 May 2007, at 14:17, Gregory Seidman wrote:

where %$ is a placeholder for substitution of the value returned by f(n).

I've used %$ in these examples due to the conceptual similarity to substitution in the sprintf method and the use of subscripts adds the possibility of consuming multiple return values, although I'm not suggesting it's a _good_ notation :slight_smile: Whatever the specific token, I think punctuation would be a better choice than a keyword because it focuses the eye on the fact that something special is happening and makes explicit that neither a local variable nor a function call is being evaluated where the substituted value is being used. Under the hood the interpreter could avoid messing with local variables, the result would be generated just once, it could be garbage collected immediately on completion and probably all kinds of cleverness could be applied.

Of course this approach fails to solve other common cases such as

    return f(n), f(m) if f(n,m) == x

Ellie

Eleanor McHugh
Games With Brains
----
raise ArgumentError unless @reality.responds_to? :reason

But maybe things should really by simple

return if x > 42
return unless (x+1)%2 == 0 # not zero? here

could probably made syntactically work (differently than now) and
return the LHS of the expression iff the expression evaluates to true
or false respectively.
Do I like it?
I said no above, but maybe I was wrong :wink:
As a matter of fact I would like it if 90% of the Rubyist liked it as
this would take away the nasty taste of surprising behavior.
OTOH no evolution without surprise, can you imagine how surprised the
dinosaurs were after their extinction!

Cheers
Robert

···

On 5/31/07, Eleanor McHugh <eleanor@games-with-brains.com> wrote:

On 29 May 2007, at 14:17, Gregory Seidman wrote:
> Looking at it from a different perspective, consider whether any
> proposed
> change to Ruby moves it closer to language X (which I'm equating to
> Perl)
> or closer to language Y (which I'm equating to Lisp). Moving toward
> X means
> making things easy to express tersely at the expense of overall
> readability
> and maintainability. Moving toward Y means simplifying the
> expression of
> things using existing syntax and functionality. I claim that we should
> strive to avoid shortcuts that bring Ruby closer to X and strive to
> solve
> our problems in ways that bring Ruby closer to Y.

I should just point out that Ruby is not Lisp, and that whilst Lisp
has many admirable traits, there are many Ruby programmers who like
it that way.

On to the topic in question: the idea of the OP that we should be
able to write

> return it if |v + 1| < 10

addresses a very real issue as the fragments

        t = f(n)
        return t if t != x

and

        t = f(n)
        return t unless t == x

pop up regularly in my code. As the whole bundle is a single
operation it _should_ be possible to write it in atomic form along
the lines of

        return %$ if f(n) != x
        return %$ unless f(n) == x
        puts %$[0], %$[1], %$[2] if f(n) > 0

where %$ is a placeholder for substitution of the value returned by f
(n).

I've used %$ in these examples due to the conceptual similarity to
substitution in the sprintf method and the use of subscripts adds the
possibility of consuming multiple return values, although I'm not
suggesting it's a _good_ notation :slight_smile: Whatever the specific token, I
think punctuation would be a better choice than a keyword because it
focuses the eye on the fact that something special is happening and
makes explicit that neither a local variable nor a function call is
being evaluated where the substituted value is being used. Under the
hood the interpreter could avoid messing with local variables, the
result would be generated just once, it could be garbage collected
immediately on completion and probably all kinds of cleverness could
be applied.

Of course this approach fails to solve other common cases such as

        return f(n), f(m) if f(n,m) == x

Ellie

Eleanor McHugh
Games With Brains
----
raise ArgumentError unless @reality.responds_to? :reason

--
You see things; and you say Why?
But I dream things that never were; and I say Why not?
-- George Bernard Shaw

Greg Fodor wrote:

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.

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.