Skip to content

Instantly share code, notes, and snippets.

@brixen
Last active August 29, 2015 14:03
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 brixen/94262a1bf592c65e98b3 to your computer and use it in GitHub Desktop.
Save brixen/94262a1bf592c65e98b3 to your computer and use it in GitHub Desktop.
$ ruby -v -e 'p lambda { |a, | a }.([1, 2])'
ruby 2.1.2p95 (2014-05-08 revision 45877) [x86_64-darwin13.0]
[1, 2]
$ ruby -v -e 'def m(a) yield a end; p m([1, 2]) { |a, | a }'
ruby 2.1.2p95 (2014-05-08 revision 45877) [x86_64-darwin13.0]
1
$ ruby -v -e 'def m(a) yield a end; l = lambda { |a, | a }; p l.([1, 2]); p m([1, 2], &l)'
ruby 2.1.2p95 (2014-05-08 revision 45877) [x86_64-darwin13.0]
[1, 2]
[1, 2]
@JoshCheek
Copy link

I think this is just because block literals come in as proc style, not lambda style.

def m(&b)
  b
end

method(:m).to_proc      # => #<Proc: (lambda)>
l = lambda { |a, | a }  # => #<Proc:program.rb:6 (lambda)>

p = Proc.new { |a, | a }  # => #<Proc:program.rb:8>
b = m {}                  # => #<Proc:program.rb:9>

@JoshCheek
Copy link

That make sense? Proc/block-literal style is like assignment style, it's loose, things don't need to match up, which lets you do things like pull key/value out of a hash as you iterate over it.

a, = [1,2]
a # => 1

And lambda/method-param style param style is much stricter, raising argument errors.

define_method :m do |a, |
  a
end

m [1, 2] # => [1, 2]

As another example, behaviour of return statements would also differ between the examples.

@JoshCheek
Copy link

Though, honestly, there's places where the distinction becomes very blurred:

Proc.new { |a, b, c| [a, b, c] }.curry[1][2][3]  # => [1, 2, 3]
Proc.new { |a, b, c| [a, b, c] }[]               # => [nil, nil, nil]

Proc.new { |a, b, c| [a, b, c] }.curry.lambda?  # => false
Proc.new { |a, b, c| [a, b, c] }.curry.arity    # => -1
Proc.new { |a, b, c| [a, b, c] }.curry.call     # => #<Proc:0x007f9d6a89bd68>

TBH, lambdas and procs should probably be two separate classes that inherit from some hypothetical ancestor Callable (Method should inherit from it, too). Things like #curry just don't make sense on proc style. I forgot what I was doing before, but this difference caused a bug in Surrogate.

@JoshCheek
Copy link

Like, how would you even implement curry on a subclass of Proc? Curry itself is going to return an instance of Proc, not your subclass, so you have to override it, which means re-implement curry.

But if you get a proc-style, then how do you decide how many args to take?

p = Proc.new { |a, b, c:| [a, b, c] }  # => #<Proc:0x007fa9b904d5c0@/var/folders/7g/mbft22555w3_2nqs_h1kbglw0000gn/T/seeing_is_believing_temp_dir20140710-82875-2ynq1s/program.rb:44>
p.parameters.map(&:first)              # => [:opt, :opt, :keyreq]
p.arity                                # => -3

Oh right, it's proc-style, they're all optional, so the block should just get immediately invoked...

p = Proc.new { |a, b, c:| [a, b, c] }  # => #<Proc:0x007fa9b904d5c0@/var/folders/7g/mbft22555w3_2nqs_h1kbglw0000gn/T/seeing_is_believing_temp_dir20140710-82875-2ynq1s/program.rb:44>
p.call 1, 2, c:4                       # => [1, 2, 4]
p.curry[1, 2, c:3]                     # => [1, 2, 3]
p.curry[1][2, c:3]                     # => [1, 2, 3]
p.curry[1, 2][c:3] rescue $!           # => #<ArgumentError: missing keyword: c>
p.curry[1][2]      rescue $!           # => #<ArgumentError: missing keyword: c>
p.curry[c:3][1][2] rescue $!           # => #<ArgumentError: missing keyword: c>

O.o

What about methods? Does Curry work well with them? Nope!

blk    = Proc.new { |a, b| [a, b, self] }               # => #<Proc:0x007f8e3a05a5a8@/var/folders/7g/mbft22555w3_2nqs_h1kbglw0000gn/T/seeing_is_believing_temp_dir20140710-83401-1ae9co3/program.rb:57>
target = "abc"                                          # => "abc"
target.define_singleton_method(:no_curry, &blk)         # => :no_curry
target.define_singleton_method(:yes_curry, &blk.curry)  # => :yes_curry
target.no_curry 1, 2                                    # => [1, 2, "abc"]
target.yes_curry 1, 2                                   # => [1, 2, main]

Clearly, curry should be on lambdas only (tbh, I wouldn't mind if it were removed from the language).

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