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.
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”.
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
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
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
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
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.
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 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.
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
This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.