Introducing the "it" keyword

S

SonOfLilit

Hi all,

That is a very interesting and educating discussion :)

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
 
R

Robert Dober

I concur. From what I understand, this _will_ eventually work:
and furthermore, which slipped away from me at first sight,

return @it if ((@it = 42)%2).zero?

works already, of course it is nonsense to use an instance variable
for this save in the main context.

Cheers
Robert
 
S

SonOfLilit

Hi all,

That is a very interesting and educating discussion :)

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

No argument from me :)

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

- Charlie

I take this back, unless someone can come up with a solution ot the
following problem:

def a(x)
return it if x.abs.it! != 1
0
end

z = Complex[1, 1]
p it if a(z.conj.it!) # prints 1.414..., not Complex[1, -1]


Aur Saraf
 
G

Greg Fodor

z = Complex[1, 1]
p it if a(z.conj.it!) # prints 1.414..., not Complex[1, -1]
If there's anything our functional programming friends can
teach us, it's that side effects will always come back to
bite you. :)

I like your syntax, though. Perhaps there is some wizardry
that could be done that would get the semantics of let()
but the syntax of it!() ?
 
S

SonOfLilit

z = Complex[1, 1]
p it if a(z.conj.it!) # prints 1.414..., not Complex[1, -1]
If there's anything our functional programming friends can
teach us, it's that side effects will always come back to
bite you. :)

I like your syntax, though. Perhaps there is some wizardry
that could be done that would get the semantics of let()
but the syntax of it!() ?

I'm afraid only compiler-side wizardry can help here.

Ruby just isn't fit to accept this syntax :p

Is there a way in ruby to write a method that returns the caller's Binding? i.e.

def yourbinding
#do magic stuff
return binding
end
a = 6
yourbinding.eval("a = 8")
a #=> 8

?

Maybe using evil.rb things? The method must not be called
Kernel#binding, btw... that would be totally cheating.

Aur
 
E

Eleanor McHugh

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 :) 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
 
M

Michael W. Ryder

Eleanor said:
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.
 
C

Chad Perrin

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.

I'm afraid I must disagree. The only thing I find "unreadable" about
that is the complete lack of obvious association between %$ and the
source of the value it contains/labels. The composition of the
expression itself, such that the condition test is on the same line as
the rest of the action, produces absolutely zero difficulty for me.

I get the impression that you don't like the predicate conditional form
at all, then, and would rather it was stricken from the language. Yes?
 
R

Robert Dober

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 :) 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
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 ;)
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
 
M

Michael W. Ryder

Chad said:
I'm afraid I must disagree. The only thing I find "unreadable" about
that is the complete lack of obvious association between %$ and the
source of the value it contains/labels. The composition of the
expression itself, such that the condition test is on the same line as
the rest of the action, produces absolutely zero difficulty for me.

The problem is when trying to maintain the code. Suppose you have to
change the test and it changes what is referenced by %$x or $1 or
whatever is used. Now you have to try and determine what you need to
use for the return to get the correct value. I find that harder than
having one line determine the test value and a separate line determining
what is done with that value.
As I am still maintaining code I wrote over 20 years ago I find that
clear code is far better than clever. In the past when working with
other peoples programs I often found that I had to remove large chunks
of the code and replace it because the old code was not clear or
maintainable.

I get the impression that you don't like the predicate conditional form
at all, then, and would rather it was stricken from the language. Yes?

I never said I wanted something stricken from the language. Much like
any language I use I don't use all of the parts as there are usually
more than one way to get the same results. When programming in C I
didn't use all the obscure tricks to make one line programs but that
didn't mean that I wanted those tricks removed from the language. In
Ruby I have found that there are usually several ways to accomplish the
same task and I choose to use those I can maintain, not those that will
cause endless hours of time to figure out and maintain.
 
C

Chad Perrin

The problem is when trying to maintain the code. Suppose you have to
change the test and it changes what is referenced by %$x or $1 or
whatever is used. Now you have to try and determine what you need to
use for the return to get the correct value. I find that harder than
having one line determine the test value and a separate line determining
what is done with that value.

That's why the fact there's no explicit, obvious connection in the
syntax between %$ and whatever it links to is a problem for readability.
 
J

Jacob Burkhart

Would a let or with construct be fast enough and memory efficient
enough for those concerned about such things?

def with(args*)
yield args
end

with( v + 1 ) do |it|
return it if it < 10
end

If this isn't fast enough or memory-efficient enough, then I would
suggest we find a way to make the interpreter recognize or optimize
such cases.

Perhaps "with" could be provided by the language by default, and then
we could support this line as syntactic sugar:

return it if with(v+1) < 10

or maybe:
return it if with_it(v+1) < 10

The interpreter would have to recognize and transform this into the
equivalent with ... do ... end

Or maybe we can go really crazy with the syntax, and support any name
for the 'tmp' variable (and any number of temp variables) with the
knowledge that if we use this syntax to assign them, the interpreter
will know to transform and garbage collect appropriately.

return it if with:it(v+1) < 10

return a if with:a(v+1) < 10

return a,b if ( with:a(v+1) + with:b(x*y) ) < 10
 
J

Jacob Burkhart

consider the clarity of reading these:

with(v+1) do |a|
with(x+2) do |b|
return a if a+b < 10
end
end

vs.

with:a(v+1) do
with:b(x+2) do
return a if a+b < 10
end
end

with:var could be usefull in other scenrios as well, yes?

We could say that with:a(v+1) is just an abbreviation for with( :a => v+1 )
and with is just a method that has the magical ability to bind
variables in the scope of the block it's given. (so in this case bind
'a' with the value of 'v + 1')

am I crazy?
 
B

Brian Candler

consider the clarity of reading these:

with(v+1) do |a|
with(x+2) do |b|
return a if a+b < 10
end
end

vs.

with:a(v+1) do
with:b(x+2) do
return a if a+b < 10
end
end

How about also comparing it with:

(1)

with(v+1, x+2) { |a, b|
return a if a+b < 10
}

(2)

a = v+1 # or: a, b = v+1, x+2
b = x+2 #
return a if a+b < 10

(3)

local {
a = v+1 # or: a, b = v+1, x+2
b = x+2 #
return a if a+b < 10
}

All these can be implemented without any language changes.

Now, if you could assign default parameters in blocks, you'd have another
option:

(4)

let { |a=v+1, b=x+2|
return a if a+b < 10
}

Having default values for block arguments would be a boon for other reasons,
including being able to rationalise some of the differences between the
various flavours of blocks, procs and methods.

The main difficultly seems to be the ambiguity of '|' as an operator or the
end of a parameter list.

Regards,

Brian.
 
M

Mariusz Pękala

--qtZFehHsKgwS5rPz
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable

consider the clarity of reading these:
=20
with(v+1) do |a|
with(x+2) do |b|
return a if a+b < 10
end
end
=20
vs.
=20
with:a(v+1) do
with:b(x+2) do
return a if a+b < 10
end
end
with:var could be usefull in other scenrios as well, yes?


Maybe that's just my twisted brain, but I find the first example more
readable :)

--=20
No virus found in this outgoing message.
Checked by 'grep -i virus $MESSAGE'
Trust me.

--qtZFehHsKgwS5rPz
Content-Type: application/pgp-signature
Content-Disposition: inline

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.7-ecc0.1.6 (GNU/Linux)

iD8DBQFGXsvgsnU0scoWZKARAj6cAJ4+1qRRZSAMIIHw31g7ySi+hpQT3ACgn+Nx
vh+SJHR9MjP0Qszdix7F0Bo=
=m6K9
-----END PGP SIGNATURE-----

--qtZFehHsKgwS5rPz--
 
C

Chad Perrin

Maybe that's just my twisted brain, but I find the first example more
readable :)

It's not just yours -- mine, too. If you wanted to locate the parameter
label in the same place as the expression, I'd say do something more
like this:

let a = v + 1 in
let b = x + 2 in
return a if a + b < 10
end
end

Of course, that might require more language-mangling, so overall I'd be
more inclined to use the first "with" example, which just looks like a
block. Perhaps something like this could be done (where I don't presume
to know what to call the method):

(v + 1).foo do |a|
(x + 2).foo do |b|
return a if a + b < 10
end
end

With that syntax, you'd have a more traditional-looking lead-in to your
block.

Of course, I might find the with:a(v+1) syntax more readable if it were
used in a language where that sort of thing was normal. It just looks
un-Rubylike enough to me that it throws me off in a Ruby context.
 
C

Chad Perrin

(1)

with(v+1, x+2) { |a, b|
return a if a+b < 10
}

I think I like this one the most.

(2)

a = v+1 # or: a, b = v+1, x+2
b = x+2 #
return a if a+b < 10

(3)

local {
a = v+1 # or: a, b = v+1, x+2
b = x+2 #
return a if a+b < 10
}

All these can be implemented without any language changes.

. . and I like that about all three of them.

Now, if you could assign default parameters in blocks, you'd have another
option:

(4)

let { |a=v+1, b=x+2|
return a if a+b < 10
}

I understand what you're trying to accomplish here, and I find it an
appealing idea as well -- but I think that diverges enough from standard
Ruby syntax style that it would make things less readable. If you
really want to use a let statement like that in Ruby, I think I'd prefer
something more like creating a new block construct using let..in..end:

let a=v+1, b=x+2 in
return a if a + b < 10
end

Having default values for block arguments would be a boon for other reasons,
including being able to rationalise some of the differences between the
various flavours of blocks, procs and methods.

The main difficultly seems to be the ambiguity of '|' as an operator or the
end of a parameter list.

Notice that a "let block" would avoid that problem nicely. The one line
version might look something like this:

let a=v+1, b=x+2 { return a if a + b < 10 }

I also think it has a cleaner look in general than trying to cram the
assignments between pipes at the beginning of the block. The way
parameters are specified in do..end blocks works great for that, but
once you start performing explicit assignments I don't think it works as
well.

Of course . . . all that aside, I'm not sure I want Ruby to be an OCaml
with dynamic typing, either. I think connecting with() to a standard
block works quite well enough, and doesn't threaten to turn Ruby into a
different language entirely.
 
J

Jacob Burkhart

Of course, I might find the with:a(v+1) syntax more readable if it were
used in a language where that sort of thing was normal. It just looks
un-Rubylike enough to me that it throws me off in a Ruby context.

The point of enabling something like with:a(v+1){ ... } in addition to
with(v+1){ |a| ...} is simply a proposal in answer to the original
proposition about 'it':
return it if |v+1| < 10

something like:

return it if with:it(v+1) < 10

return a if with:a(v+1) < 10

return a,b if ( with:a(v+1) + with:b(x*y) ) < 10


Where this:

return it if with:it(v+1) < 10

Would be translated by the interpreter into exactly the same thing as this:

with:it(v+1) do
return it if it < 10
end

which is exactly the same thing as this:

with(v+1) do |it|
return it if it < 10
end
 
B

Brian Candler

The point of enabling something like with:a(v+1){ ... } in addition to
with(v+1){ |a| ...} is simply a proposal in answer to the original
proposition about 'it':
return it if |v+1| < 10

something like:

return it if with:it(v+1) < 10

return a if with:a(v+1) < 10

return a,b if ( with:a(v+1) + with:b(x*y) ) < 10

But if you're going to label them anyway, then why not write

return a,b if ((a = v+1) + (b = x*y)) < 10

?
 
M

Mariusz Pękala

--BRE3mIcgqKzpedwo
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable

=20
But if you're going to label them anyway, then why not write
=20
return a,b if ((a =3D v+1) + (b =3D x*y)) < 10

The whole discussion started because this code does not work in current
ruby parser rules.

In the part 'return a,b' "a" and "b" are treated as method calls, but
in 'if ((a =3D v+1) + (b =3D x*y)) < 10' they are treated as local variable=
s.

If you try to execute this line (use irb) you will get error
'NameError: undefined local variable or method `a' for main:Object'

--=20
No virus found in this outgoing message.
Checked by 'grep -i virus $MESSAGE'
Trust me.

--BRE3mIcgqKzpedwo
Content-Type: application/pgp-signature
Content-Disposition: inline

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.7-ecc0.1.6 (GNU/Linux)

iD8DBQFGYDUusnU0scoWZKARAsClAKC7uSyBClAEkz8ipj7qSHDtJWMtwwCgqaV9
dYe2rSG0eSMA7Q3N5rpeQts=
=SMxC
-----END PGP SIGNATURE-----

--BRE3mIcgqKzpedwo--
 

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

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,020
Latest member
GenesisGai

Latest Threads

Top