Please do not ask me "why do you want to do this?" I'm just exploring the nooks and crannies of Ruby, with no specific goals in mind.
Some Ruby classes can be instantiated with just parentheses rather than explicitly invoking the #new
method. For example,
irb> require 'pathname'
irb> p=Pathname('/etc/xdg/autostart')
=> #<Pathname:/etc/xdg/autostart>
irb> n=Pathname.new('/etc/xdg/autostart')
=> #<Pathname:/etc/xdg/autostart>
irb> p==n
=> true
And some use this for casting/coercion, as in Integer(4.5)
. And there are probably other uses I haven't encountered yet, or can't remember.
However, I haven't found a way to do this for custom classes:
irb> class Foo ; def initialize(*args) ; @args=args ; end ; end
=> :initialize
irb> f1=Foo.new(1,2,3)
=> #<Foo:0x00007f330a29f4e8 @args=[1, 2, 3]>
irb> fp=Foo(1,2,3)
(irb):7:in `<main>': undefined method `Foo' for main:Object (NoMethodError)
Did you mean? for
The only thing I can figure out is that both Pathname
and Integer
(for example) use C code, and somehow wnd up being Kernel methods:
irb> Object.method(:Pathname)
=> #<Method: #<Class:Object>(Kernel)#Pathname(_)>
irb> Object.method(:Integer)
=> #<Method: #<Class:Object>(Kernel)#Integer(*)>
irb> Object.method(:Foo)
(irb):10:in `method': undefined method `Foo' for class `#<Class:Object>' (NameError)
Did you mean? for
So this appears to be something provided by the C/.so
extension code.
However, can it be done simply with native Ruby code without resorting to a kludge like the following?
irb> Object.define_method(:Foo) { |*args| Foo.new(*args) }
=> :Foo
irb> Foo(1,2,3)
=> #<Foo:0x00007f330a3b9018 @args=[1, 2, 3]>
Those global methods are so-called module functions defined in Kernel
. You can define your own global function this way: (using the Ruby 3 shorthand syntax for defining methods)
module Kernel
def Foo(...) = Foo.new(...)
module_function :Foo
end
The above defines a method Kernel#Foo
which forwards all arguments to Foo.new
. Since Object
includes Kernel
, this allows to create a Foo
instances from within objects via:
Foo(1, 2, 3) #=> #<Foo:0x000000010633e120 @args=[1, 2, 3]>
In addition, module_function
makes the method available as a class method:
Kernel.Foo(1, 2, 3) #=> #<Foo:0x0000000108b00398 @args=[1, 2, 3]>
it also marks the instance method as being private so you can't call it with any other explicit receiver:
:hello.Foo(1, 2, 3)
# NoMethodError: private method `Foo' called for an instance of Symbol
This is also how Integer
, Pathname
etc. are integrated.
Note that the built-in global methods which correspond to a class are intended primarily for converting values to that class, e.g. Integer("123") #=> 123
. They don't just pass along the arguments. (Integer
doesn't even respond to new
)