Forum: Ruby I call Ducktype Violation on #to_proc! :)

Announcement (2017-05-07): www.ruby-forum.com is now read-only since I unfortunately do not have the time to support and maintain the forum any more. Please see rubyonrails.org/community and ruby-lang.org/en/community for other Rails- und Ruby-related community platforms.
45196398e9685000d195ec626d477f0e?d=identicon&s=25 unknown (Guest)
on 2006-05-20 21:13
(Received via mailing list)
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.
5da4c52f43677f395aff5bde775593c2?d=identicon&s=25 Daniel Schierbeck (dasch)
on 2006-05-20 23:18
(Received via mailing list)
transfire@gmail.com 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
45196398e9685000d195ec626d477f0e?d=identicon&s=25 unknown (Guest)
on 2006-05-21 01:02
(Received via mailing list)
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.
5da4c52f43677f395aff5bde775593c2?d=identicon&s=25 Daniel Schierbeck (dasch)
on 2006-05-21 01:24
(Received via mailing list)
transfire@gmail.com 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 :)

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
0b561a629b87f0bbf71b45ee5a48febb?d=identicon&s=25 Dave Burt (Guest)
on 2006-05-21 01:57
(Received via mailing list)
Daniel Schierbeck 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
45196398e9685000d195ec626d477f0e?d=identicon&s=25 unknown (Guest)
on 2006-05-21 05:23
(Received via mailing list)
> 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.
5da4c52f43677f395aff5bde775593c2?d=identicon&s=25 Daniel Schierbeck (dasch)
on 2006-05-21 11:25
(Received via mailing list)
Dave Burt 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
A9b6a93b860020caf9d2d1d58c32478f?d=identicon&s=25 Ross Bamford (Guest)
on 2006-05-21 13:23
(Received via mailing list)
On Sun, 2006-05-21 at 18:22 +0900, Daniel Schierbeck 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@(irb):1>

def l.call(*args); nil; end
# => nil

l.call(1)
# => nil

[1,2].each(&l)
1
2
# => [1, 2]
E0d864d9677f3c1482a20152b7cac0e2?d=identicon&s=25 Robert Klemme (Guest)
on 2006-05-21 14:30
(Received via mailing list)
2006/5/21, Ross Bamford <rossrt@roscopeco.co.uk>:
> 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
0b561a629b87f0bbf71b45ee5a48febb?d=identicon&s=25 Dave Burt (Guest)
on 2006-05-21 17:40
(Received via mailing list)
Daniel Schierbeck 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
5da4c52f43677f395aff5bde775593c2?d=identicon&s=25 Daniel Schierbeck (dasch)
on 2006-05-21 20:07
(Received via mailing list)
Dave Burt 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
5da4c52f43677f395aff5bde775593c2?d=identicon&s=25 Daniel Schierbeck (dasch)
on 2006-05-21 20:16
(Received via mailing list)
Dave Burt 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
58479f76374a3ba3c69b9804163f39f4?d=identicon&s=25 Eric Hodel (Guest)
on 2006-05-22 22:52
(Received via mailing list)
On May 20, 2006, at 12:10 PM, transfire@gmail.com 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 Hodel - drbrain@segment7.net - http://blog.segment7.net
This implementation is HODEL-HASH-9600 compliant

http://trackmap.robotcoop.com
58479f76374a3ba3c69b9804163f39f4?d=identicon&s=25 Eric Hodel (Guest)
on 2006-05-22 22:56
(Received via mailing list)
On May 21, 2006, at 2:22 AM, Daniel Schierbeck 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 Hodel - drbrain@segment7.net - http://blog.segment7.net
This implementation is HODEL-HASH-9600 compliant

http://trackmap.robotcoop.com
45196398e9685000d195ec626d477f0e?d=identicon&s=25 unknown (Guest)
on 2006-05-23 05:53
(Received via mailing list)
Fair enough. But then where''s my #to_p method ;)
45196398e9685000d195ec626d477f0e?d=identicon&s=25 unknown (Guest)
on 2006-05-23 05:56
(Received via mailing list)
Fair enough. But then where''s my #to_p method ;)
58479f76374a3ba3c69b9804163f39f4?d=identicon&s=25 Eric Hodel (Guest)
on 2006-05-23 08:23
(Received via mailing list)
On May 22, 2006, at 8:53 PM, transfire@gmail.com wrote:

> Fair enough. But then where''s my #to_p method ;)

Huh?  What are you responding to?

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

--
Eric Hodel - drbrain@segment7.net - http://blog.segment7.net
This implementation is HODEL-HASH-9600 compliant

http://trackmap.robotcoop.com
45196398e9685000d195ec626d477f0e?d=identicon&s=25 unknown (Guest)
on 2006-05-23 15:25
(Received via mailing list)
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.
5da4c52f43677f395aff5bde775593c2?d=identicon&s=25 Daniel Schierbeck (dasch)
on 2006-05-23 15:47
(Received via mailing list)
transfire@gmail.com 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? :P

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



(I know I'm annoying...)
45196398e9685000d195ec626d477f0e?d=identicon&s=25 unknown (Guest)
on 2006-05-23 16:43
(Received via mailing list)
> Something like this, perhaps? :P
>
>    class Hash
>      def to_proc
>        proc{|obj| each{|key, value| obj.send("#{key}=", value)}}
>      end
>    end

Yep. Exaclty.
This topic is locked and can not be replied to.