Factory function like Array() for your own classes

Hi,

I was quite surprised today to find out that you can define both a
class and a function with the same name and still be able to reference
them both separately.

This makes it possible to define factory functions that work like the
built-in Array() and String() functions, e.g. so that Thing(1,2,3) is
the same as Thing.new(1,2,3).

(I know that Array(4) is different from Array.new(4) but I'm talking
about what it looks like here.)

Here's an example:

  class Thing
    factory # defaults to name of class
    # ...
  end

  t = Thing(:name => "glove", :description => "white")
  t = Thing :name => "glove", :description => "white"
  t = Thing() { |x|
    x.name = "Alice"
  }
  t = Thing :name => "White Rabbit" do |x|
    x.description = "flustered"
  end

Here's the implementation (it really is very simple):

  def factory(name = self.to_s)
    Kernel::eval "
      def #{name}(*args, &block)
        #{name}.new(*args, &block)
      end
    "
  end

Note that I use eval here rather than define_method because you can't
pass blocks to blocks (yet).

With this function, you can define factory functions for any class, e.g.

  factory :SomeOtherClass
  soc = SomeOtherClass(*args, &block)

Here's a script to test interaction with blocks:

  if __FILE__ == $0
    class Thing
      attr_accessor :attrs
      factory
      def initialize(attrs = {}, &block)
        @attrs = attrs
        block.call(self) if block_given?
      end
    ends

    t = Thing()
    p t

    t = Thing :name => "glove", :description => "white"
    p t

    p Thing() { |x|
      x.attrs[:name] = "Alice"
    }

    wr = Thing :name => "White Rabbit" do |obj|
      obj.attrs[:description] = "flustered"
    end
    p wr

    # and just to show that the class is still there
    t = Thing.new(:name => "Carpenter")

    p Module.const_get(:Thing)
    p method(:Thing)
  end

I think this is a very natural idiom which doesn't seem to have any
unwelcome side-effects. I'm curious what the general opinion is. It
seems a cleaner solution than,say, aliasing [] to new (which I used to
do a lot).

So... what do you think?

Regards,

Sean

not a bad idea

   harp:~ > cat a.rb
   Kernel::methods.grep(%r/[A-Z]/).each do |meth|
     obj = send meth, 42
     puts "#{ meth }(42) => #{ obj.inspect } - #{ obj.class }"
   end

   harp:~ > ruby a.rb
   Integer(42) => 42 - Fixnum
   Float(42) => 42.0 - Float
   String(42) => "42" - String
   Array(42) => [42] - Array

:wink:

-a

···

On Sat, 15 Oct 2005, Sean O'Halpin wrote:

I think this is a very natural idiom which doesn't seem to have any
unwelcome side-effects. I'm curious what the general opinion is. It seems a
cleaner solution than,say, aliasing to new (which I used to do a lot).

So... what do you think?

--

email :: ara [dot] t [dot] howard [at] noaa [dot] gov
phone :: 303.497.6469
anything that contradicts experience and logic should be abandoned.
-- h.h. the 14th dalai lama

===============================================================================

Sean O'Halpin:

I was quite surprised today to find out that you can define both a
class and a function with the same name and still be able to reference
them both separately.

This makes it possible to define factory functions that work like the
built-in Array() and String() functions, e.g. so that Thing(1,2,3) is
the same as Thing.new(1,2,3).
...
So... what do you think?

In Ruby, so we're told, methods begin with a lower-case letter. Now,
actually, you can have all kinds of funky names:

class Object
define_method("!@#%$") { 43 }
end

=> #<Proc:0x02b46bd0@(irb):16>

send("!@#%$")

43

I think we don't often see these is because methods _should_ begin in
lower-case.

That said, as Ara has pointed out, there are 4 such methods in the core, and
at least one more in the standard library (Rational()). But these methods
are all cast methods - you feed them something that isn't an Array, Integer,
Float, String, Rational, and it gives you either an object of the correct
type or an error.

I suppose that part of your Thing example could be seen as a cast from a
hash to a Thing, but taking a block is a bit of a stretch. Why not just use
..new? Why does new need an alias?

Cheers,
Dave

Sean O'Halpin wrote:

Hi,

I was quite surprised today to find out that you can define both a
class and a function with the same name and still be able to reference
them both separately.

This makes it possible to define factory functions that work like the
built-in Array() and String() functions, e.g. so that Thing(1,2,3) is
the same as Thing.new(1,2,3).

(I know that Array(4) is different from Array.new(4) but I'm talking
about what it looks like here.)

Here's an example:

  class Thing
    factory # defaults to name of class
    # ...
  end

  t = Thing(:name => "glove", :description => "white")
  t = Thing :name => "glove", :description => "white"
  t = Thing() { |x|
    x.name = "Alice"
  }
  t = Thing :name => "White Rabbit" do |x|
    x.description = "flustered"
  end

Here's the implementation (it really is very simple):

  def factory(name = self.to_s)
    Kernel::eval "
      def #{name}(*args, &block)
        #{name}.new(*args, &block)
      end
    "
  end

Note that I use eval here rather than define_method because you can't
pass blocks to blocks (yet).

With this function, you can define factory functions for any class, e.g.

  factory :SomeOtherClass
  soc = SomeOtherClass(*args, &block)

Here's a script to test interaction with blocks:

  if __FILE__ == $0
    class Thing
      attr_accessor :attrs
      factory
      def initialize(attrs = {}, &block)
        @attrs = attrs
        block.call(self) if block_given?
      end
    ends

    t = Thing()
    p t

    t = Thing :name => "glove", :description => "white"
    p t

    p Thing() { |x|
      x.attrs[:name] = "Alice"
    }

    wr = Thing :name => "White Rabbit" do |obj|
      obj.attrs[:description] = "flustered"
    end
    p wr

    # and just to show that the class is still there
    t = Thing.new(:name => "Carpenter")

    p Module.const_get(:Thing)
    p method(:Thing)
  end

I think this is a very natural idiom which doesn't seem to have any
unwelcome side-effects. I'm curious what the general opinion is. It
seems a cleaner solution than,say, aliasing to new (which I used to
do a lot).

So... what do you think?

Yep. Yep. Yep. I've sugegsted it before. But some people don't like it,
insisting that the ".new" gives some sort of additional clarity. Don't
know, but like your (our) way better --even wish it were built-in.

T.

Hi,

At Sat, 15 Oct 2005 11:00:44 +0900,
Sean O'Halpin wrote in [ruby-talk:160664]:

Note that I use eval here rather than define_method because you can't
pass blocks to blocks (yet).

  class Class
    def factory(name = self.to_s)
      names = name.split(/::/)
      name = names.pop
      if names.empty?
        mklass = klass = Object
      else
        klass = names.inject(Object) {|c, n| c.const_get(n)}
        mklass = class << klass; self; end
      end
      meth = klass.const_get(name).method(:new)
      mklass.class_eval {define_method(name, &meth)}
    end
  end

···

--
Nobu Nakada

A language-comparison-aside...

In Lua, every table (the core object unit; basically a Hash with some extra goodies) can be accessed directly OR called as a function, if the table's metatable has a __call property that points to a function. For example:

Person = {
   -- constructor
   new=function( self, inName, inAge )
     local theInstance = { name = inName or "John", age = inAge or 0 }
     -- make the instance inherit from the 'class'
     setmetatable( theInstance, self._instanceMetaTable )
     return theInstance
   end,

   -- methods and properties common to all instances
   prototype = {
     species = 'Human',
     greet = function( self )
       print( "Hello, my name is " .. self.name )
     end
   }
}

Person._instanceMetaTable = {
   __index = Person.prototype,
   __tostring = function( self )
     return string.format( '<Person name="%s" age="%d">', self.name, self.age )
   end
}

-- Here's the magic that makes the Person table callable as a function
setmetatable( Person, {
   __call = function( self, ... )
     return self:new( unpack( arg ) )
   end
} )

local gk = Person:new( 'Gavin', 32 )
local lk = Person( 'Lisa', 31 )

print( gk, lk )
--> <Person name="Gavin" age="32"> <Person name="Lisa" age="31">

Person.prototype.greet( gk )
--> Hello, my name is Gavin

gk:greet( )
--> Hello, my name is Gavin

lk:greet( )
--> Hello, my name is Lisa

-- Showing that Person isn't a function, just an object with properties
print( Person.prototype )
--> table: 0x303a40

···

On Oct 14, 2005, at 8:00 PM, Sean O'Halpin wrote:

This makes it possible to define factory functions that work like the
built-in Array() and String() functions, e.g. so that Thing(1,2,3) is
the same as Thing.new(1,2,3).

[snip]

Why not just use ..new? Why does new need an alias?

It doesn't ~need~ an alias. It's just quite handy sometimes when you
are creating lots of nested objects to avoid having to type new all
the time. For example, in a report DSL, you could use something like:

  r = Report(
    :header => Header( :title => "My report" ),
    :body => Body( :rows => Query(:sql => "SELECT * FROM Supplier"))
    )

instead of

  r = Report.new(
    :header => Header.new( :title => "My report" ),
    :body => Body.new( :rows => Query.new(:sql => "SELECT * FROM Supplier"))
    )

Not a big gain, but in my opinion, the .new in the second example is
just adding noise. It's a matter of taste I guess.

Regards,

Sean

···

On 10/15/05, Dave Burt <dave@burt.id.au> wrote:

Trans:

Yep. Yep. Yep. I've sugegsted it before. But some people don't like it,
insisting that the ".new" gives some sort of additional clarity. Don't
know, but like your (our) way better --even wish it were built-in.

Like this?

def method_missing(sym, *args, &block)
  const = Object.const_get(sym) rescue nil
  if const.kind_of?(Module)
    const.new(*args, &block)
  else
    raise NoMethodError("undefined method '#{sym}' for #{self.inspect}")
    # OK, it is cool.
  end
end

Cheers,
Dave

Ah! I see the light! :slight_smile:

Thanks,

Sean

···

On 10/16/05, nobu.nokada@softhome.net <nobu.nokada@softhome.net> wrote:

     meth = klass.const_get(name).method(:new)
     mklass.class_eval {define_method(name, &meth)}

i'm with you - i do it all the time in my own code :wink:

-a

···

On Sat, 15 Oct 2005, Sean O'Halpin wrote:

On 10/15/05, Dave Burt <dave@burt.id.au> wrote:

[snip]

Why not just use ..new? Why does new need an alias?

It doesn't ~need~ an alias. It's just quite handy sometimes when you
are creating lots of nested objects to avoid having to type new all
the time. For example, in a report DSL, you could use something like:

r = Report(
   :header => Header( :title => "My report" ),
   :body => Body( :rows => Query(:sql => "SELECT * FROM Supplier"))
   )

instead of

r = Report.new(
   :header => Header.new( :title => "My report" ),
   :body => Body.new( :rows => Query.new(:sql => "SELECT * FROM Supplier"))
   )

Not a big gain, but in my opinion, the .new in the second example is
just adding noise. It's a matter of taste I guess.

--

email :: ara [dot] t [dot] howard [at] noaa [dot] gov
phone :: 303.497.6469
anything that contradicts experience and logic should be abandoned.
-- h.h. the 14th dalai lama

===============================================================================

I'd prefer

def method_missing(s, *a, &b)
  Object.const_get(s).new(*a,&b)
end

Ruby will throw exceptions for any cases that don't work anyway.

Kind regards

    robert

···

Dave Burt <dave@burt.id.au> wrote:

Trans:

Yep. Yep. Yep. I've sugegsted it before. But some people don't like
it, insisting that the ".new" gives some sort of additional clarity.
Don't know, but like your (our) way better --even wish it were
built-in.

Like this?

def method_missing(sym, *args, &block)
const = Object.const_get(sym) rescue nil
if const.kind_of?(Module)
   const.new(*args, &block)
else
   raise NoMethodError("undefined method '#{sym}' for
   #{self.inspect}") # OK, it is cool.
end
end

Trans:

Yep. Yep. Yep. I've sugegsted it before. But some people don't like
it, insisting that the ".new" gives some sort of additional clarity.
Don't know, but like your (our) way better --even wish it were
built-in.

Like this?

def method_missing(sym, *args, &block)
const = Object.const_get(sym) rescue nil
if const.kind_of?(Module)
   const.new(*args, &block)
else
   raise NoMethodError("undefined method '#{sym}' for
   #{self.inspect}") # OK, it is cool.
end
end

I'd prefer

def method_missing(s, *a, &b)
Object.const_get(s).new(*a,&b)
end

Ruby will throw exceptions for any cases that don't work anyway.

Kind regards

   robert

PS: Note also the alternative approach of Array:

class Foo
class <<self
alias : :new
end

=> nil

Foo

=> #<Foo:0x1018b850>

Kind regards

    robert

···

Robert Klemme <bob.news@gmx.net> wrote:

Dave Burt <dave@burt.id.au> wrote:

Indeed - I mentioned it above ("aliasing : to new") and have been
using it for years (since I saw it in matju's X11 lib). I've always
liked it myself but have recently discovered that others find it a bit
strange. Also, since I've been using Rails, I've started to use : as
an alias for :find so I can do things like this:

  person = Person[:where => "name like 'A%'"]

This seems a more natural fit - i.e. thinking of Person as an extent /
container that you get things out of fits better with the normal use
of : as an fetch operator.

I was wondering if the Object() syntax might be a better solution as
an alias for new. It has the precedence of Array(), Integer(), etc.

As Dave says, method names should be lowercase so it wouldn't affect
anyone who's been following the rules!

I wasn't aware that Trans had already suggested this - can anyone
point me to the discussion?

Cheers,

Sean

···

On 10/15/05, Robert Klemme <bob.news@gmx.net> wrote:

PS: Note also the alternative approach of Array:

I wasn't aware that Trans had already suggested this - can anyone
point me to the discussion?

http://blade.nagaokaut.ac.jp/cgi-bin/scat.rb/ruby/ruby-talk/114265

Probably has been thought of before too. And btw I wrote a system to
automate it fora all classes --but b/c it just adds additional overhead
for a little bit nicer syntax I don't find it very useful.

T.