I thought spaces didn't matter around operators

S

Sarah Allen

I had understood that operators, like minus (-), had special "syntactic
sugar" that allowed me to include or omit spaces around them like this:
=> 0

However, in some cases, spaces seem to matter:
NoMethodError: undefined method `-@' for Tue Apr 13 13:40:24 -0700
2010:Time
from (irb):6=> 6.895787

Can someone explain what "undefined method `-@'" refers to?

Here's my version of Ruby if it matters:
$ ruby -v
ruby 1.8.7 (2008-08-11 patchlevel 72) [universal-darwin10.0]

Thanks,
Sarah
http://www.ultrasarus.com
 
R

Rob Biedenharn

I had understood that operators, like minus (-), had special
"syntactic
sugar" that allowed me to include or omit spaces around them like
this:
=> 0

However, in some cases, spaces seem to matter:
NoMethodError: undefined method `-@' for Tue Apr 13 13:40:24 -0700
2010:Time
from (irb):6=> 6.895787

Can someone explain what "undefined method `-@'" refers to?

Here's my version of Ruby if it matters:
$ ruby -v
ruby 1.8.7 (2008-08-11 patchlevel 72) [universal-darwin10.0]

Thanks,
Sarah
http://www.ultrasarus.com


irb> t=Time.now
=> Tue Apr 13 19:27:43 -0400 2010
irb> Time.now() -t
=> 5.824652
irb> Time.now -t
NoMethodError: undefined method `-@' for Tue Apr 13 19:27:43 -0400
2010:Time
from (irb):5
from :0
irb> -t
NoMethodError: undefined method `-@' for Tue Apr 13 19:27:43 -0400
2010:Time
from (irb):6
from :0

The flexible syntax also lets you omit parentheses and in this case,
the "-t" is parsed as an argument to the Time.now method. Putting the
explicit (and empty) argument list on the call removes this ambiguity.

The '-@' method is the unary minus method. The error message is
because there is no method that negates a Time instance.

-Rob

Rob Biedenharn
http://agileconsultingllc.com
(e-mail address removed)
http://gaslightsoftware.com
(e-mail address removed)
 
S

Seebs

I had understood that operators, like minus (-), had special "syntactic
sugar" that allowed me to include or omit spaces around them like this:

Often, yes.
However, in some cases, spaces seem to matter:
NoMethodError: undefined method `-@' for Tue Apr 13 13:40:24 -0700
2010:Time
from (irb):6
=> 6.895787
Hmm!

Can someone explain what "undefined method `-@'" refers to?

Probably "unary -".

As in:
x = 3
puts (-x) -3

But!

puts -x -3
puts - x

NoMethodError: undefined method `-' for nil:NilClass

Basically, Ruby is interpreting "-x" as a unary minus on x, rather
than as part of a larger expression. It turns out to be ambiguous
in this case, because Ruby can't tell whether you mean
(Time.now()) - (t)
or
(Time.now(-1 * t))

Only unary - isn't necessarily "-1 * t", it's just "whatever you get
calling -@ on t".

-s
 
D

David A. Black

Hi --

irb> t=Time.now
=> Tue Apr 13 19:27:43 -0400 2010
irb> Time.now() -t
=> 5.824652
irb> Time.now -t
NoMethodError: undefined method `-@' for Tue Apr 13 19:27:43 -0400 2010:Time
from (irb):5
from :0
irb> -t
NoMethodError: undefined method `-@' for Tue Apr 13 19:27:43 -0400 2010:Time
from (irb):6
from :0

The flexible syntax also lets you omit parentheses and in this case, the "-t"
is parsed as an argument to the Time.now method. Putting the explicit (and
empty) argument list on the call removes this ambiguity.

Here's another interesting space/non-space case:
=> "hihihihihihihihihihi"

In m *10 the * is the unary *, while in m * 10 it's the infix
operator/method *.


David
 
S

Sarah Allen

David said:
Here's another interesting space/non-space case:

=> "hihihihihihihihihihi"

In m *10 the * is the unary *, while in m * 10 it's the infix
operator/method *.


David

And what, may I ask, it the unary * operator? Is it what I've heard
Rubyists call the "splat" operator that you use in var args? How and
why would you override it?

Thanks so much for indulging my pursuit of arcane Ruby knowledge :)

Sarah
 
B

Brian Candler

Sarah said:
However, in some cases, spaces seem to matter:

In *many* cases spaces matter :)
2
=> nil-2
NoMethodError: undefined method `abs' for nil:NilClass
from (irb):3
from :0

If unsure:
- put parentheses around method arguments
- put spaces around operators
Can someone explain what "undefined method `-@'" refers to?

-@ is the unary minus method, +@ is the unary plus method.

class Foo
def -@
"Minused"
end
def +@
"Plussed"
end
end

f = Foo.new
puts(-f)
puts(+f)

Aside: as a symbol, the method name is :-@. This lets you write smiley
programs:

puts f.send:)-@)

As well as most operators, there are some other syntactic constructs
which map to method calls. e.g.

class Foo
def [](*args)
"Index"
end
end

puts f[]
puts f.send:)[])
 
S

Sarah Allen

Brian said:
-@ is the unary minus method, +@ is the unary plus method.

class Foo
def -@
"Minused"
end
def +@
"Plussed"
end
end

This is a very helpful example.

But, I still can't fully answer my original question
SyntaxError: compile error
(irb):8: syntax error, unexpected tINTEGER, expecting $end
1 1.send:)+@)
^
from (irb):8

nope.

So, in what way is 1 +2 not like 1 +t (where t is a Time object). I know
2 is a FixNum, but why is that significant? Specifically, how is the
expression evaluated to make 1 +1 work?
TypeError: Time can't be coerced into Fixnum
from (irb):19:in `+'
from (irb):19


Thanks!
Sarah
 
R

Rob Biedenharn

This is a very helpful example.

But, I still can't fully answer my original question

SyntaxError: compile error
(irb):8: syntax error, unexpected tINTEGER, expecting $end
1 1.send:)+@)
^
from (irb):8

nope.

So, in what way is 1 +2 not like 1 +t (where t is a Time object). I
know
2 is a FixNum, but why is that significant? Specifically, how is the
expression evaluated to make 1 +1 work?

From your original,
Time.now -t
now is a method call
1 -t
1 is not a method call
TypeError: Time can't be coerced into Fixnum
from (irb):19:in `+'
from (irb):19

There's no coerce method for a Time instance.

Try this:

t = Time.now
1 + t

Then do:

class Time
def coerce(other)
case other
when Integer
return other, self.to_i
else
super
end
end
end

And retry:

1 + t

This is a different issue than the parser trying to apply :+@

-Rob
Thanks!
Sarah

Rob Biedenharn http://agileconsultingllc.com
(e-mail address removed)
 
Z

Zach Moazeni

Sarah, here's my take on it (though these are assumptions)

1 - 1 # using send:)-)
1 -1 # using send:)-)
(1) -1 # also using send:)-)
-1 # using send:)-@)
+1 # using send:)+@)

t = Time.now
Time.now() - t # using send:)-)
(Time.now) - t # same: using send:)-)
Time.now - t # same: using send:)-)
Time.now -t # executed as (Time.now) -t or t.send:)-@) which doesn't
exist

# Something interesting
1.to_i -1 # uses send:)-@) but wants to pass that as an argument to
Fixnum#to_i
(1.to_i) -1 # uses send:)-)

Which makes me think that:

Time.now -t # is trying to do:
Time.now(-t) # which first needs to evaluate -t first (... which is
t.send:)-@) ...)


So in conclusion, I don't think Fixnum is being treated any differently.
It's just using a different method
 
S

Sarah Allen

Sarah said:
But, I still can't fully answer my original question

SyntaxError: compile error
(irb):8: syntax error, unexpected tINTEGER, expecting $end
1 1.send:)+@)
^
from (irb):8

nope.

I still don't quite get the answer to the above question, but the rest
of my post was incorrect.
TypeError: Time can't be coerced into Fixnum
from (irb):19:in `+'
from (irb):19

This fails because 1 + Time.now doesn't work either. Fixnum's + method
requires a Fixnum parameter.

Someone on suggested that the unary operator is only used if there isn't
an object that precedes the operator. However, if that were true,
Time.now -t would work. A special case for String and Fixnum?
NoMethodError: undefined method `-@' for Wed Apr 14 13:28:15 -0700
2010:Time
from (irb):40

Still curious,
Sarah
 
Z

Zach Moazeni

Zach said:
Time.now -t # executed as (Time.now) -t or t.send:)-@) which doesn't
exist
Time.now -t # is trying to do:
Time.now(-t) # which first needs to evaluate -t first (... which is
t.send:)-@) ...)

I should proofread more. The more I look at it, I think the second is
right.
 
B

Brian Candler

Sarah said:
Yes.

is the same as...?

Same again, as you have shown.
So, in what way is 1 +2 not like 1 +t (where t is a Time object).

Well, it must be either
(1) + (t)
or
(1) (+t)

The Ruby parser is picking the second, even though it doesn't make sense
at a higher level (you can't have two adjacent expressions without an
intervening operator)

That doesn't explain why 1 +2 is treated differently though.

Actually, there is an optimisation that numeric literals have any unary
prefix interpreted by the parser. That is, +1 is the value +1, not 1
with a unary plus applied.

You can demonstrate it thus:
Whee!
=> 1
(BTW, I don't recommend redefining core class operators like this. If
you redefine Fixnum#+, and don't preserve its existing semantics, nasty
things will happen)

The first case doesn't invoke the unary plus method, it's just absorbed
into the literal number.

So you'd think even more that 1 +1 would be taken as two adjacent
integers, like 1 1 or 1 +t. Clearly, it isn't.

Welcome to Ruby. It's like your granny - it might have some warts, but
it's an old friend.
 
B

Brian Candler

Sarah said:
NoMethodError: undefined method `-@' for Wed Apr 14 13:28:15 -0700
2010:Time
from (irb):40

Ah I see now, this is just what's called "poetry mode" - method calls
without parentheses around the argument list

Time.now - Time.now => Time.now() - Time.now()

Time.now -Time.now => Time.now(-Time.now())

Similarly,

Time.now - 1 => Time.now() - 1
Time.now -1 => Time.now(-1)
 
S

Sarah Allen

Brian said:
Ah I see now, this is just what's called "poetry mode" - method calls
without parentheses around the argument list

Time.now - Time.now => Time.now() - Time.now()

Time.now -Time.now => Time.now(-Time.now())

Similarly,

Time.now - 1 => Time.now() - 1
Time.now -1 => Time.now(-1)

Indeed. Now I understand.

minus (-) is interpreted as a binary object when preceded by object.
When preceded by a method name and parentheses are omitted, it is
interpreted as a unary operator acting on the object that follows it.

There is nothing special about Fixnum or String, here is an example with
Time:

$ irb=> 7.682178

Whew. Thanks everyone!

Sarah
 
D

David A. Black

Hi --

And what, may I ask, it the unary * operator? Is it what I've heard
Rubyists call the "splat" operator that you use in var args? How and
why would you override it?

It is indeed what people call the "splat" operator, though I've never
found that name very expressive. I think of it as the unar[r]ay
operator (that's unary unarray :) It does an "unarray" operation in
the sense that it can turn an array into a bare list, in a method
call.

You can't define it directly (I believe that's true in 1.9 as well as
earlier versions), but you can affect what it does via the #to_a
method:

o = Object.new
def o.to_a; [1,2,3]; end
a = *o
p a # [1,2,3]

As for the why: I don't think you'd often define #to_a just to get *
behavior. It's more of an extra thing you get in cases where you need
#to_a anyway.


David
 

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,744
Messages
2,569,482
Members
44,900
Latest member
Nell636132

Latest Threads

Top