Forum: Ruby I thought spaces didn't matter around operators

20ee0d23c969c7740d3c936a4675bb23?d=identicon&s=25 Sarah Allen (ultrasaurus)
on 2010-04-14 01:24
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
Ef3aa7f7e577ea8cd620462724ddf73b?d=identicon&s=25 Rob Biedenharn (Guest)
on 2010-04-14 01:32
(Received via mailing list)
On Apr 13, 2010, at 7:24 PM, Sarah Allen 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 Biedenharn
http://agileconsultingllc.com
  Rob@AgileConsultingLLC.com
http://gaslightsoftware.com
  Rob@GaslightSoftware.com
F53b05cdbdf561cfe141f69b421244f3?d=identicon&s=25 David A. Black (Guest)
on 2010-04-14 01:45
(Received via mailing list)
Hi --

On Wed, 14 Apr 2010, Rob Biedenharn 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
2c672f42613d05ca8ba8d834800b1113?d=identicon&s=25 Seebs (Guest)
on 2010-04-14 01:50
(Received via mailing list)
On 2010-04-13, Sarah Allen <sarah@ultrasaurus.com> 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
20ee0d23c969c7740d3c936a4675bb23?d=identicon&s=25 Sarah Allen (ultrasaurus)
on 2010-04-14 06:01
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 :)

Sarah
753dcb78b3a3651127665da4bed3c782?d=identicon&s=25 Brian Candler (candlerb)
on 2010-04-14 11:00
Sarah Allen wrote:
> However, in some cases, spaces seem to matter:

In *many* cases spaces matter :-)

>> 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 [](*args)
    "Index"
  end
end

puts f[]
puts f.send(:[])
20ee0d23c969c7740d3c936a4675bb23?d=identicon&s=25 Sarah Allen (ultrasaurus)
on 2010-04-14 21:47
Brian Candler 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
Ef3aa7f7e577ea8cd620462724ddf73b?d=identicon&s=25 Rob Biedenharn (Guest)
on 2010-04-14 22:21
(Received via mailing list)
On Apr 14, 2010, at 3:47 PM, Sarah Allen 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 Biedenharn    http://agileconsultingllc.com
Rob@AgileConsultingLLC.com
D46a89672353a9c5258e187c8095ea40?d=identicon&s=25 Zach Moazeni (zmoazeni)
on 2010-04-14 22:29
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


--
Zach Moazeni
http://simplechatter.com
20ee0d23c969c7740d3c936a4675bb23?d=identicon&s=25 Sarah Allen (ultrasaurus)
on 2010-04-14 22:29
Sarah Allen 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
D46a89672353a9c5258e187c8095ea40?d=identicon&s=25 Zach Moazeni (zmoazeni)
on 2010-04-14 22:32
Zach Moazeni 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 Moazeni
http://simplechatter.com
753dcb78b3a3651127665da4bed3c782?d=identicon&s=25 Brian Candler (candlerb)
on 2010-04-14 22:33
Sarah Allen 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.
753dcb78b3a3651127665da4bed3c782?d=identicon&s=25 Brian Candler (candlerb)
on 2010-04-14 22:38
Sarah Allen 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)
20ee0d23c969c7740d3c936a4675bb23?d=identicon&s=25 Sarah Allen (ultrasaurus)
on 2010-04-15 00:14
Brian Candler 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
F53b05cdbdf561cfe141f69b421244f3?d=identicon&s=25 David A. Black (Guest)
on 2010-04-15 01:52
(Received via mailing list)
Hi --

On Wed, 14 Apr 2010, Sarah Allen 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 :-) 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
Please log in before posting. Registration is free and takes only a minute.
Existing account

NEW: Do you have a Google/GoogleMail, Yahoo or Facebook account? No registration required!
Log in with Google account | Log in with Yahoo account | Log in with Facebook account
No account? Register here.