I call Ducktype Violation on #to_proc! :)


#1

Trying to be just ducky…

class X
def to_proc
self
end
def call
“x”
end
end

x = X.new

def y( &b )
b.call
end

y(&x)

TypeError: X#to_proc should return Proc
from (irb):14

Why isn’t it enough that it x responds to #call?

T.


#2

removed_email_address@domain.invalid wrote:

Why isn’t it enough that it x responds to #call?

Personally, I don’t mind that objects given as blocks must define a
#to_proc method; what I don’t like is that the return value must be a
Proc. It should just check if the return value defined a #call method.

The reason I think the #to_proc method should be there is that the
semantics of #call heavily depends on the context in which it’s in:

class PhoneNumber
def call; end
end

#to_proc is by convention only defined on objects that in some way think
of themselves as procs – #call can be defined on a lot of objects, for
a variety of reasons.

That may not be completely “ducky”, but I think it’s common sense.

Daniel


#3

Right. It’s not the #to_proc I’m worried about. I completely agree with
your assessment and that is exactly what I’m thinking too.

T.


#4

removed_email_address@domain.invalid wrote:

Right. It’s not the #to_proc I’m worried about. I completely agree with
your assessment and that is exactly what I’m thinking too.

Good to know :slight_smile:

If some of you want a use case, take this (wrote it a few weeks ago):

class CachedProc
def initialize(&proc)
@proc = proc
@cache = Hash.new{|cache, args| cache[args] = @proc.call(*args)}
end

 def call(*args)
   @cache[args]
 end

 alias_method :[], :call

 def to_proc
   # must return a Proc...
   @proc
 end

 def method_missing(name, *args, &block)
   @proc.send(name, *args, &block)
 end

end

def cached_proc(&proc)
CachedProc.new(&proc)
end

This is fine and all, but doesn’t work with iterators and such:

proc = cached_proc{|obj| expensive_operation(obj)}
[2, 5, 7, 4, 2, 8, 2].collect(&proc)

Here, the caching doesn’t work, because the CachedProc is converted to a
Proc.

Daniel


#5

Daniel S. wrote:

alias_method :[], :call

Here, the caching doesn’t work, because the CachedProc is converted to a
Proc.

def to_proc
proc {|*args| self.call(*args) }
end

Cheers,
Dave


#6

def to_proc
proc {|*args| self.call(*args) }
end

Yea, and I ended up with

def to_proc
proc { |dummy| self }
end

But obviously the restriction is forcing some ugly code on us here.

T.


#7

Dave B. wrote:

def to_proc
proc {|*args| self.call(*args) }
end

Yes, that works (thanks!), but I think it’s against Ruby spirit to
require a certain class for a relatively high-level thing like this. I
can see why there’s not really any way around a string ultimately being
a String object, through #to_str or #to_s, but all that’s really
required by the return value of #to_proc is an object that responds to
#call.

Daniel


#8

2006/5/21, Ross B. removed_email_address@domain.invalid:

I guess it’s probably the way it is because call isn’t involved when a
method uses yield…?

That would be my guess also. Trans, please note that there is an
additional, implicit conversion step if you hand a block to a method
and use it via &b. Also, a proc handed over via &b still can be
invoked via yield so there are some (optimized) things going on behind
the scenes.

Regards

robert


#9

On Sun, 2006-05-21 at 18:22 +0900, Daniel S. wrote:

#call.
I guess it’s probably the way it is because call isn’t involved when a
method uses yield…?

l = lambda { |a| p a }

=> #Proc:0xb7e9c0fc@:1(irb)

def l.call(*args); nil; end

=> nil

l.call(1)

=> nil

[1,2].each(&l)
1
2

=> [1, 2]


#10

Dave B. wrote:

#call.

You can actually use certain non-Proc objects:

irb> def say_hello_to() yield “hello” end
=> nil
irb> say_hello_to &method(:puts)
hello

But is that because of #to_proc the the method object, or is Ruby
treating method objects different?

Getting off-topic, if I may revisit your cached_proc again – you can
easily do without the class entirely.

def cached_proc &block
cache = Hash.new {|h, k| h[k] = block.call(*args) }
proc {|*args| cache[args] }
end

Yeah, that simplifies things quite a bit, thanks mate!

Daniel


#11

Daniel S. wrote:

#call.
You can actually use certain non-Proc objects:

irb> def say_hello_to() yield “hello” end
=> nil
irb> say_hello_to &method(:puts)
hello
=> nil

And you can also use a subclass of Proc.

class CachedProc < Proc

… and your example would work, I think.

Getting off-topic, if I may revisit your cached_proc again – you can
easily do without the class entirely.

def cached_proc &block
cache = Hash.new {|h, k| h[k] = block.call(*args) }
proc {|*args| cache[args] }
end

Cheers,
Dave


#12

Dave B. wrote:

def cached_proc &block
cache = Hash.new {|h, k| h[k] = block.call(*args) }
proc {|*args| cache[args] }
end

I think it should be like this though:

def cached_proc(&block)
cache = Hash.new{|hsh, args| hsh[args] = block.call(*args)}
proc{|*args| cache[args]}
end

Daniel


#13

On May 20, 2006, at 12:10 PM, removed_email_address@domain.invalid wrote:

Why isn’t it enough that it x responds to #call?

There’s a convention where to_XXX requires the receiver to return an
X instance while to_X doesn’t.

See [ruby-talk:96567].


Eric H. - removed_email_address@domain.invalid - http://blog.segment7.net
This implementation is HODEL-HASH-9600 compliant

http://trackmap.robotcoop.com


#14

On May 21, 2006, at 2:22 AM, Daniel S. wrote:

that responds to #call.
#to_ary requires you to return an Array.

#to_str requires you to return a String.

In this spirit #to_proc requires you to return a Proc.


Eric H. - removed_email_address@domain.invalid - http://blog.segment7.net
This implementation is HODEL-HASH-9600 compliant

http://trackmap.robotcoop.com


#15

Fair enough. But then where’'s my #to_p method :wink:


#16

Fair enough. But then where’'s my #to_p method :wink:


#17

On May 22, 2006, at 8:53 PM, removed_email_address@domain.invalid wrote:

Fair enough. But then where’'s my #to_p method :wink:

Huh? What are you responding to?

There’s no #to_p because there’s no use case for it.


Eric H. - removed_email_address@domain.invalid - http://blog.segment7.net
This implementation is HODEL-HASH-9600 compliant

http://trackmap.robotcoop.com


#18

removed_email_address@domain.invalid wrote:

P.S. Barring proof that there is no real usecase for it, I’ll make the
Hash#to_proc method available in the upcoming version of Facets.

Something like this, perhaps? :stuck_out_tongue:

class Hash
def to_proc
proc{|obj| each{|key, value| obj.send("#{key}=", value)}}
end
end

(I know I’m annoying…)


#19

Something like this, perhaps? :stuck_out_tongue:

class Hash
def to_proc
proc{|obj| each{|key, value| obj.send("#{key}=", value)}}
end
end

Yep. Exaclty.


#20

There were two use cases given in this thread. Whether there was a way
to do the same thing otherwise doesn not neccessarily deligitimize the
potential use.

To be more specific in my case I have a method:

class AClass
initialize( &params )
params.call(self)
end
end

Configuration of the class needs to be settable either via a block:

AClass.new { |s|
s.conf = “whatever”
}

But also from an external hash-like object.

hl = HashLike.new( { :conf => ‘whatever’ } )
AClass.new( &hl )

But this later won’t work b/c of the Proc restriction. And, just as I
started this thread, I see no good reason that it should restrict.
Someone mentioned speed at one point, but that’s not Ruby’s modus
operandi --functionality is. A simple check for a “to_p” is reasonable
if the object isn’t a strict Proc.

Now you say there is no usecase. Lets look at my alternatives here. One
is to change the interface to accept both a hash-like object and a proc
–oh the joy:

initialize( paramsh={}, &params )
  raise "bugger" if paramsh and params
  if params
  params.call(self)
  else
    # this would have been the HashLike#call method
    paramsh.each { |k,v| self.send("#{k}=",v) }
  end
end

Okay, so something like that can work but it isn’t very OOP or DRY and
more importantly my use case is for something I want end-programmers to
be able to easily use to create there own “task interfaces” --this mess
just doesn’t cut it.

So here’s what I had to do to make it work for my usecase. Keeping the
original simple definition I added a method to the HashLike class such
that:

hl = HashLike.new( { :conf => ‘whatever’ } )
AClass.new( &(hl.to_proc) )

Ain’t it ironic. I had to take a hash, convert it to a proc, just so I
can turn around and convert it back to a set of attribute parameters.

Okay. So you say there’s no usecase. You’re younger than I, Eric, so
your mind is surely more supple. Maybe you are right. Maybe there’s a
better way to do this. Have any idea?

T.

P.S. Barring proof that there is no real usecase for it, I’ll make the
Hash#to_proc method available in the upcoming version of Facets.