Project

General

Profile

Actions

Feature #4910

closed

Classes as factories

Added by rklemme (Robert Klemme) over 13 years ago. Updated almost 7 years ago.

Status:
Rejected
Target version:
-
[ruby-core:37244]

Description

I suggest to add these two to class Class:

class Class
  alias call new

  def to_proc(*args)
    lambda {|*a| new(*args)}
  end
end

Then we can use class instances where blocks are needed and can easily use them as factory instances using the general contract of #call (see example attached).


Files

pro.rb (836 Bytes) pro.rb Example file with old and new variants rklemme (Robert Klemme), 06/20/2011 08:50 PM

Related issues 1 (0 open1 closed)

Related to Ruby master - Feature #14498: Class#to_procRejectedActions

Updated by Eregon (Benoit Daloze) over 13 years ago

Hello,

Robert Klemme wrote:

I suggest to add these two to class Class:

class Class
  alias call new

  def to_proc(*args)
    lambda {|*a| new(*args)}
  end
end

Did you want to mean:

def to_proc
  lambda { |*args| new(*args) } # or maybe lambda { |args| new(*args) }
end

?

#to_proc is called with no arguments (Symbol.instance_method(:to_proc).arity # => 0).

Then we can use class instances where blocks are needed and can easily use them as factory instances using the general contract of #call (see example attached).

I don't really see the advantage of defining #call, you could use #new instead at line 16.
If you want more flexibility, I believe it is fine to use a block.

But I like Class#to_proc, and it is indeed some kind of factory helper:

Pos = Struct.new :x,:y
[[1,2],[3,4]].map(&Pos) # => [#<struct Pos x=1, y=2>, #<struct Pos x=3, y=4>]
# instead of
[[1,2],[3,4]].map { |x,y| Pos.new(x,y) }

# note neither #to_proc defined as "lambda { |*args| new(*args) }" nor map(&Pos.method(:new)) would work:
# ([#<struct Pos x=[1, 2], y=nil>,...])

The obvious limitation being the lack of flexibility for common arguments (e.g.: y always the same). You would then have to use an explicit block.

I do not know if it is worth to add it for this specific case, but it can be nice.

I am also unsure if we need factories in Ruby (certainly not like in statically typed languages).

Updated by rklemme (Robert Klemme) over 13 years ago

Benoit Daloze wrote:

Hello,

Robert Klemme wrote:

I suggest to add these two to class Class:

class Class
  alias call new

  def to_proc(*args)
    lambda {|*a| new(*args)}
  end
end

Did you want to mean:

def to_proc
  lambda { |*args| new(*args) } # or maybe lambda { |args| new(*args) }
end

?

#to_proc is called with no arguments (Symbol.instance_method(:to_proc).arity # => 0).

No, it was meant exactly as stated. Advantage is that you can provide parameters to #new if needed while mapping the parameterless call of to_proc easily to the parameterless call of Class#new.

Then we can use class instances where blocks are needed and can easily use them as factory instances using the general contract of #call (see example attached).

I don't really see the advantage of defining #call, you could use #new instead at line 16.
If you want more flexibility, I believe it is fine to use a block.

That's the exact point: by aliasing #new to #call we can pass in a lambda OR a class instance. The most general contract would then be '#call'able (i.e. an anonymous callback function) and as a shortcut we can pass in a class instance.

But I like Class#to_proc, and it is indeed some kind of factory helper:

Pos = Struct.new :x,:y
[[1,2],[3,4]].map(&Pos) # => [#<struct Pos x=1, y=2>, #<struct Pos x=3, y=4>]
# instead of
[[1,2],[3,4]].map { |x,y| Pos.new(x,y) }

# note neither #to_proc defined as "lambda { |*args| new(*args) }" nor map(&Pos.method(:new)) would work:
# ([#<struct Pos x=[1, 2], y=nil>,...])

The obvious limitation being the lack of flexibility for common arguments (e.g.: y always the same). You would then have to use an explicit block.

I do not know if it is worth to add it for this specific case, but it can be nice.

I had considered that case as well and felt it might not be as common as the case where we try to provide arguments. I do not have any statistics though and I hope for others shedding some more light what they deem more useful.

A variant would be

class Class
  def to_proc(*args)
    if args.empty?
      lambda {|*a| new(*a)}
    else
      lambda {|*a| new(*args)}
    end
  end
end

In other words: if arguments are passed to to_proc use them as sole method arguments for #new; if not, use whatever is passed to the proc (which would support your mapping example).

We could probably make things even more complex by appending *a to *args and truncating the list with the arity of #new at the time of invocation of the block (or, more efficient, time of call of to_proc).

I am also unsure if we need factories in Ruby (certainly not like in statically typed languages).

Any class in Ruby is a factory object already with method #new being the factory method.

Updated by headius (Charles Nutter) over 13 years ago

I'm not sure I agree with adding to_proc to Class instances, since it seems questionable that #new is what you'd always want to be called. Dodging that debate for now, there is another way to get the result you seek:

class Foo
  def initialize(i)
    @i = i
  end
end

(1..50).map(&Foo.method(:new))

This is both more explicit and less magic. If there were syntax added to get method objects (without calling #method) it would be even cleaner.

Updated by rklemme (Robert Klemme) over 13 years ago

Charles Nutter wrote:

I'm not sure I agree with adding to_proc to Class instances, since it seems questionable that #new is what you'd always want to be called.

Hmm, but what else? I think it is a reasonable default.

Dodging that debate for now, there is another way to get the result you seek:

class Foo
  def initialize(i)
    @i = i
  end
end

(1..50).map(&Foo.method(:new))

This is both more explicit and less magic. If there were syntax added to get method objects (without calling #method) it would be even cleaner.

That's true. Though in absence of that syntax I prefer (1..50).map {|i| Foo.new i} over your solution as it is equally explicit and even less magic - could even be shorter to type. :-) Actually only (1..50).map(&Foo) would be an alternative I would consider.

Cheers

Updated by mame (Yusuke Endoh) over 12 years ago

  • Status changed from Open to Assigned
  • Assignee set to matz (Yukihiro Matsumoto)

Updated by mame (Yusuke Endoh) about 12 years ago

  • Target version set to 2.6
Actions #7

Updated by naruse (Yui NARUSE) almost 7 years ago

  • Target version deleted (2.6)
Actions #8

Updated by nobu (Nobuyoshi Nakada) almost 7 years ago

  • Description updated (diff)
Actions #9

Updated by matz (Yukihiro Matsumoto) almost 7 years ago

Updated by matz (Yukihiro Matsumoto) almost 7 years ago

  • Status changed from Assigned to Rejected

It can lead to unreadable code.

Matz.

Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0Like0Like0Like0Like0