Project

General

Profile

Feature #4910

Classes as factories

Added by rklemme (Robert Klemme) almost 7 years ago. Updated 3 months ago.

Status:
Rejected
Priority:
Normal
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).

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

Related issues

Related to Ruby trunk - Feature #14498: Class#to_procRejected

History

#1 [ruby-core:37245] Updated by Eregon (Benoit Daloze) almost 7 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 #newinstead 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).

#2 [ruby-core:37248] Updated by rklemme (Robert Klemme) almost 7 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.

#3 [ruby-core:37297] Updated by headius (Charles Nutter) almost 7 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.

#4 [ruby-core:37326] Updated by rklemme (Robert Klemme) almost 7 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

#5 [ruby-core:43638] Updated by mame (Yusuke Endoh) about 6 years ago

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

#6 [ruby-core:49714] Updated by mame (Yusuke Endoh) over 5 years ago

  • Target version set to 2.6

#7 Updated by naruse (Yui NARUSE) 5 months ago

  • Target version deleted (2.6)

#8 Updated by nobu (Nobuyoshi Nakada) 3 months ago

  • Description updated (diff)

#9 Updated by matz (Yukihiro Matsumoto) 3 months ago

#10 [ruby-core:85709] Updated by matz (Yukihiro Matsumoto) 3 months ago

  • Status changed from Assigned to Rejected

It can lead to unreadable code.

Matz.

Also available in: Atom PDF