Skip to content

Instantly share code, notes, and snippets.

@benhamill
Created February 2, 2013 04:29
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save benhamill/4696115 to your computer and use it in GitHub Desktop.
Save benhamill/4696115 to your computer and use it in GitHub Desktop.
How I Want Ruby's `Object#method` To Work

How I Want Ruby's Object#method To Work

The simple case, is this guy:

> m = SecureRandom.method(:hex)
=> #<Method: SecureRandom.hex>
> m.call
=> "5928aab178404945fb560a249b67a000"
> m.call
=> "d5ba69ac2e9dc8632aee7bf3212f6d67"

But, what I want, is a fully curried function. Something like this:

> m = SecureRandom.method(:hex, 6)
=> #<Method: SecureRandom.hex>
> m.call
=> "bd131d38de3c"
> m.call
=> "4e5a20936be0"

But Object#method doesn't take more than one argument. So, to get the effect I want, I have to do something like this:

> m = -> { SecureRandom.hex(6) }
=> #<Proc:0x000000034144b8@(pry):7 (lambda)>
> m.call
=> "bd131d38de3c"
> m.call
=> "4e5a20936be0"

It's not a huge thing, just a little sugar, but still.

@benhamill
Copy link
Author

I guess to be really nice to use with, like, partial argument-list support, you'd have to do some math around arity between the method and the object returned so that the call would only expect as many args as the method had left, or... whatever.

@jcsalterego
Copy link

class Object
  alias_method :original_method, :method
  def method(*args)
    Proc.new do |*pargs|
      all_args = args[1..-1] + pargs
      original_method(args[0]).call(*all_args)
    end
  end
end

I wouldn't actually use it in production, nor do I know if the closures are actually properly preserved.

Normal usage:

>> m1 = SecureRandom.method(:hex)
=> #<Proc:0x0000000105044b00@test.rb:5>
>> m1.call
=> "5532eba830603b73958479d42f42f7ce"
>> m1.call(10)
=> "601660c3bfaf317ef43a"

Turbo button usage:

>> m2 = SecureRandom.method(:hex, 10)
=> #<Proc:0x0000000105044b00@test.rb:5>
>> m2.call
=> "6fdee86fbcc3d2ecde5b"

Extra args FTL:

>> m2.call(12)
ArgumentError: wrong number of arguments (2 for 1)
    from test.rb:7:in `hex'
    from test.rb:7:in `call'
    from test.rb:7:in `method'
    from test.rb:17:in `call'
    from test.rb:17
    from :0

@benhamill
Copy link
Author

So @avdi pointed out Proc#curry. And I tried it out, but it's a bit weird about arity:

> p = SecureRandom.method(:hex).to_proc
=> #<Proc:0x000000030b2590 (lambda)>
> p.airty
=> -1
> p1 = p.curry
=> #<Proc:0x0000000312f400 (lambda)>
> p1.arity
=> -1
> p1.call
=> "de22fd26a6f63516f9a1c32147cadc44"
> p1.call(5)
=> "1f5e2b31f6"
> p2 = p.curry[5]
=> "ed76826b41"
> # Oh. That's not a Proc...
> p2.call
NoMethodError: undefined method 'call' for "ed76826b41":String
> p3 = p.curry(1)[5]
=> "6c08b6f144"
> # That's not one, either...
> p4 = p.curry(2)[5]
=> #<Proc:0x000000031e4fd0 (lambda)>
> # OK. Closerrrrrr...
> p4.call
=> #<Proc:0x000000031f8cd8 (lambda)>
> # Uh... no.
> p4.call(0)
ArgumentError: wrong number of arguments (2 for 1)
from /home/ben/.rbenv/versions/1.9.3-p286/lib/ruby/1.9.1/securerandom.rb:139:in 'hex'
> # Shit.

Basically, as soon as the arity of the curried function is met, it evaluates it down, so you can't fill the args all up and then wait to call it... without explicitly making a lambda.

@jcsalterego
Copy link

Name my method Object#cmethod and call it a day :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment