Method_missing and send

I'm in the process of writing a logging package for a project. The actual
logging methods can be specified at runtime and are implemented with a class
method_missing call.

That works fine until I call the warn method using 'send'. I would have
expected the object to respond identically to both the direct invocation and
the indirect invocation via 'send'.

Is this a bug in ruby or have I missed something?

class B
  def self.method_missing(sym, *args)
    puts "%s: %s"%[sym, args[1]]
  end
end

B.log(self, "There was a young man called McKnight,")
B.send(:log, self, "Who could travel faster than light.")
B.warn(self, "One day at about noon, he went to the moon")
B.send(:warn, self, "And arrived back the previous night.")

bash-2.05b$ ruby -v
ruby 1.8.1 (2004-01-27) [i386-mswin32]
bash-2.05b$
bash-2.05b$ tmp.rb
log: There was a young man called McKnight,
log: Who could travel faster than light.
warn: One day at about noon, he went to the moon
./tmp.rb:71:in `warn': wrong number of arguments(2 for 1) (ArgumentError)
from ./tmp.rb:71:in `send'
from ./tmp.rb:71
bash-2.05b$

Neil.

I'm in the process of writing a logging package for a project. The actual
logging methods can be specified at runtime and are implemented with a class
method_missing call.

That works fine until I call the warn method using 'send'. I would have
expected the object to respond identically to both the direct invocation and
the indirect invocation via 'send'.

Is this a bug in ruby or have I missed something?

well, you have to consider that warn is already a method in ruby;

warn 'foo'

foo
=> nil

OTOH it works for me:

def warn(*args)
p args
end

=> nil

warn 't' , 't', 'y'

["t", "t", "y"]
=> nil

self.send :warn , :t,:t,:t,:t

[:t, :t, :t, :t]
=> nil

so I believe you did something wrong. But I wonder: why don'tu you use
the logger module (that comes with ruby 1.8) or log4r ?

···

il Mon, 14 Jun 2004 20:13:42 +0900, "Neil Mc Laughlin" <nml@fjserv.net> ha scritto::

Is this a bug in ruby or have I missed something?

#warn is a private method of Kernel

When you call it, like a public method (i.e. B.warn), ruby don't find it
and call #method_missing

With #send ruby is able to find any methods (even private) and call it.

For example

svg% cat b.rb
#!/usr/bin/ruby
class A
   class << self
      def method_missing(*args)
         puts "method_missing : #{args}"
      end

      private
      def tt(*args)
         puts "tt : #{args}"
      end
   end
end

A.tt(1, 2)
A.send(:tt, 3, 4)
svg%

svg% b.rb
method_missing : tt12
tt : 34
svg%

Guy Decoux

Hi,

As a side note, you may want to consider a solution
using blocks:

def trace( domain = :debug, &block )
  if $domains[domain] then
    p block.call()
  end
end

trace{ "debug"}
trace( :info){ "Hello"}
trace( :warn){ "fast" }

Doing so is rather efficient when the said domain is disabled.
As a result you can keep traces in production code.

You can even:

def off( domain )
  if domain == :trace then
    alias trace noop
  end
  $domains[domain] = false
end

def noop( *a ); end

...

off( :trace) # turns all tracing off

Yours,

JeanHuguesRobert

···

At 20:13 14/06/2004 +0900, you wrote:

I'm in the process of writing a logging package for a project. The actual
logging methods can be specified at runtime and are implemented with a class
method_missing call.

That works fine until I call the warn method using 'send'. I would have
expected the object to respond identically to both the direct invocation and
the indirect invocation via 'send'.

Is this a bug in ruby or have I missed something?

class B
def self.method_missing(sym, *args)
   puts "%s: %s"%[sym, args[1]]
end
end

B.log(self, "There was a young man called McKnight,")
B.send(:log, self, "Who could travel faster than light.")
B.warn(self, "One day at about noon, he went to the moon")
B.send(:warn, self, "And arrived back the previous night.")

bash-2.05b$ ruby -v
ruby 1.8.1 (2004-01-27) [i386-mswin32]
bash-2.05b$
bash-2.05b$ tmp.rb
log: There was a young man called McKnight,
log: Who could travel faster than light.
warn: One day at about noon, he went to the moon
../tmp.rb:71:in `warn': wrong number of arguments(2 for 1) (ArgumentError)
from ./tmp.rb:71:in `send'
from ./tmp.rb:71
bash-2.05b$

Neil.

-------------------------------------------------------------------------
Web: @jhr is virteal, virtually real
Phone: +33 (0) 4 92 27 74 17

Thanks for that. I see that send does indeed call the private method.

I'm still a bit puzzled as to why 'obj.send(:msg)' should behave differently
from 'obj.msg'. My impression was that they are different ways of expressing
the same concept - sending a message to an object. That's why I expected the
same result from either call.

Does anyone else think this odd or even in violation of the principle of
least surprise?

Regards, Neil

···

----- Original Message -----
From: "ts" <decoux@moulon.inra.fr>
To: "ruby-talk ML" <ruby-talk@ruby-lang.org>
Cc: <ruby-talk@ruby-lang.org>
Sent: Monday, June 14, 2004 12:25 PM
Subject: Re: method_missing and send

> Is this a bug in ruby or have I missed something?

#warn is a private method of Kernel

When you call it, like a public method (i.e. B.warn), ruby don't find it
and call #method_missing

With #send ruby is able to find any methods (even private) and call it.

For example

svg% cat b.rb
#!/usr/bin/ruby
class A
   class << self
      def method_missing(*args)
         puts "method_missing : #{args}"
      end

      private
      def tt(*args)
         puts "tt : #{args}"
      end
   end
end

A.tt(1, 2)
A.send(:tt, 3, 4)
svg%

svg% b.rb
method_missing : tt12
tt : 34
svg%

Guy Decoux

Jean-Hugues ROBERT wrote:

As a side note, you may want to consider a solution
using blocks:

def trace( domain = :debug, &block )
  if $domains[domain] then
    p block.call()
  end
end

Nice. Small point: it's more efficient to use yield than to instantiate a Proc:

def trace( domain = :debug)
   if $domains[domain] then
     p yield
   end
end

Even if the trace is disabled, the &block construct will instantiate a Proc.

I'm still a bit puzzled as to why 'obj.send(:msg)' should behave differently
from 'obj.msg'. My impression was that they are different ways of expressing
the same concept - sending a message to an object. That's why I expected the
same result from either call.

Does anyone else think this odd or even in violation of the principle of
least surprise?

I too found it odd, but very convenient. By using obj#send, you also get to sidestep the encapsulation support. So it's possible to call protected and private methods this way. That might be considered in violation of POLS, but it's pretty darn useful in those edge cases where you really do want to, knowingly, sidestep encapsulation.

···

--
David Heinemeier Hansson,
http://www.instiki.org/ -- A No-Step-Three Wiki in Ruby
http://www.basecamphq.com/ -- Web-based Project Management
http://www.loudthinking.com/ -- Broadcasting Brain
http://www.nextangle.com/ -- Development & Consulting Services

IMO instance_eval is better for that case.

Paul

···

On Wed, Jun 16, 2004 at 04:48:57AM +0900, David Heinemeier Hansson wrote:

I too found it odd, but very convenient. By using obj#send, you also
get to sidestep the encapsulation support. So it's possible to call
protected and private methods this way. That might be considered in
violation of POLS, but it's pretty darn useful in those edge cases
where you really do want to, knowingly, sidestep encapsulation.