I thought spaces didn't matter around operators

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

1-1
=> 0
1 -1
=> 0
1 - 1
=> 0
1- 1
=> 0

However, in some cases, spaces seem to matter:

t = Time.now
=> Tue Apr 13 13:40:24 -0700 2010
Time.now -t
NoMethodError: undefined method `-@’ for Tue Apr 13 13:40:24 -0700
2010:Time
from (irb):6
Time.now - t
=> 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

On Apr 13, 2010, at 7:24 PM, Sarah A. wrote:

=> 0
from (irb):6
Sarah
http://www.ultrasarus.com

Posted via http://www.ruby-forum.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 B.
http://agileconsultingllc.com
[email protected]

[email protected]

Hi –

On Wed, 14 Apr 2010, Rob B. wrote:

1 - 1
irb> Time.now -t
empty) argument list on the call removes this ambiguity.
Here’s another interesting space/non-space case:

def m(*args); “hi”; end
=> nil

m *10
=> “hi”

m * 10
=> “hihihihihihihihihihi”

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

David

On 2010-04-13, Sarah A. [email protected] wrote:

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:

t = Time.now
=> Tue Apr 13 13:40:24 -0700 2010
Time.now -t
NoMethodError: undefined method `-@’ for Tue Apr 13 13:40:24 -0700
2010:Time
from (irb):6
Time.now - t
=> 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

David A. Black wrote:

Here’s another interesting space/non-space case:

def m(*args); “hi”; end
=> nil

m *10
=> “hi”

m * 10
=> “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 :slight_smile:

Sarah

Sarah A. wrote:

However, in some cases, spaces seem to matter:

In many cases spaces matter :slight_smile:

a = -3
=> -3

puts (a+1).abs
2
=> nil

puts(a+1).abs
-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
“Index”
end
end

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

Brian C. wrote:

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

1 + 1
is the same as

1.send(:+, 1)

1 +1
is the same as…?

1 1.send(:+@)
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?

t = 2
=> 2

1 +t
=> 3

t = Time.now
=> Wed Apr 14 12:46:30 -0700 2010

1 +t
TypeError: Time can’t be coerced into Fixnum
from (irb):19:in `+’
from (irb):19

Thanks!
Sarah

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

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

t = Time.now
Time.now() - t # using send(:slight_smile:
(Time.now) - t # same: using send(:slight_smile:
Time.now - t # same: using send(:slight_smile:
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(:slight_smile:

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


Zach M.

Sarah A. wrote:

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

1 + 1
is the same as

1.send(:+, 1)

1 +1
is the same as…?

1 1.send(:+@)
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.

t = Time.now
=> Wed Apr 14 12:46:30 -0700 2010

1 +t
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?

“foo” +“bar”
=> “foobar”

1 +1
=> 2

1 -1
=> 0

Time.now - Time.now
=> 0.0

Time.now -Time.now
NoMethodError: undefined method `-@’ for Wed Apr 14 13:28:15 -0700
2010:Time
from (irb):40

Still curious,
Sarah

On Apr 14, 2010, at 3:47 PM, Sarah A. wrote:

end
is the same as…?
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

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

Posted via http://www.ruby-forum.com/.

Rob B. http://agileconsultingllc.com
[email protected]

Sarah A. wrote:

1 + 1
is the same as

1.send(:+, 1)

Yes.

1 +1
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:

class Integer
def +@
puts “Whee!”
self
end
end
=> nil

+1
=> 1

+(1)
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.

Zach M. wrote:

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.


Zach M.

Sarah A. wrote:

“foo” +“bar”
=> “foobar”

1 +1
=> 2

1 -1
=> 0

Time.now - Time.now
=> 0.0

Time.now -Time.now
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)

Hi –

On Wed, 14 Apr 2010, Sarah A. wrote:

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?

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 :slight_smile: 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

Brian C. wrote:

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

t1 = Time.now
=> Wed Apr 14 15:11:33 -0700 2010

t2 = Time.now
=> Wed Apr 14 15:11:41 -0700 2010

t2 -t1
=> 7.682178

Whew. Thanks everyone!

Sarah