Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Proc#* and Method#* for Proc and Method composition #935

Closed
wants to merge 3 commits into from

Conversation

mudge
Copy link

@mudge mudge commented Jun 14, 2015

c.f. https://bugs.ruby-lang.org/issues/6284

Allow Procs and Methods to be composed together with *.

f = proc { |x| x * 2 }
g = proc { |x, y| x + y }
h = f * g

h.call(1, 2) #=> 6

Support composition with any object that has a call method, e.g.

class Foo
  def call(x, y)
    x + y
  end
end

f = proc { |x| x * 2 }
g = f * Foo.new

g.call(1, 2) #=> 6

This implementation should be largely equivalent to the following Ruby (excepting that the lambda? property is preserved):

class Proc
  def *(g)
    proc { |*args, &blk| call(g.call(*args, &blk)) }
  end
end

proc.c Outdated
VALUE f, g, fargs;
f = RARRAY_AREF(args, 0);
g = RARRAY_AREF(args, 1);
fargs = rb_ary_new3(1, rb_proc_call_with_block(g, argc, argv, passed_proc));
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we could change this to send call to g with the given arguments (e.g. via rb_funcall), this would work with any object with a call method (including other Procs, Methods and regular Ruby classes).

Alternatively, we could ensure g is a Proc by calling to_proc on it if it is not already a Proc (not entirely dissimilar to using the existing StringValue macro).

As discussed on the issue tracker, this would mean the difference between the following Ruby pseudocode:

# Where g must only implement call
class Proc
  def *(g)
    proc { |*args, &blk| call(g.call(*args, &blk)) }
  end
end

vs

# Where g must only implement to_proc
class Proc
  def *(g)
    proc { |*args, &blk| call(g.to_proc.call(*args, &blk)) }
  end
end

@tsujigiri
Copy link

I, for one, would love to see this merged! :)

@marshall-lee
Copy link

I like the idea of functional composition but I don't like the implementation here.

First of all, I don't like naming it *. Any special reason why use a star symbol? For example dry-pipeline gem implements a pipeline operator >>. Of course, both operators have their numeric analogues (multiplication and bitwise right shift accordingly) and it's confusing but >> is used less often so it's a better option IMO.

And the second problem that it does not deal well with custom callable objects. I mean:

module Increment
  def self.call(x)
    x + 1
  end
end

multiply_two = proc { |x| x * 2 }

(multiply_two * Increment).call(1) # => 4
(Increment * multiply_two).call(1) # => NoMethodError

So, I like the concept of mixin with composition operator more like it's done in dry-pipeline gem. Something llike:

module Increment
  extend PipelineOperator
  def self.call(x)
    x + 1
  end
end

mudge and others added 3 commits October 11, 2016 22:05
* proc.c (proc_compose): Implement Proc#* for Proc composition, enabling
  composition of Procs and Methods. [Feature ruby#6284]

* test/ruby/test_proc.rb: Add test cases for Proc composition.
* proc.c (rb_method_compose): Implement Method#* for Method composition,
  which delegates to Proc#*.

* test/ruby/test_method.rb: Add test cases for Method composition.
* proc.c (proc_compose): support any object with a call method rather
  than supporting only procs. [Feature ruby#6284]

* proc.c (compose): use the function call on the given object rather
  than rb_proc_call_with_block in order to support any object.

* test/ruby/test_proc.rb: Add test cases for composing Procs with
  callable objects.

* test/ruby/test_method.rb: Add test cases for composing Methods with
  callable objects.
@mooreniemi
Copy link

FWIW I am pro *, and used it in my C extension. I'd argue it has some mindshare from: https://www.youtube.com/watch?v=seVSlKazsNk

@k0kubun k0kubun changed the base branch from trunk to master August 15, 2019 17:59
@k0kubun
Copy link
Member

k0kubun commented Aug 17, 2019

It seems to have a conflict now. Could you rebase this from master?

@mudge
Copy link
Author

mudge commented Aug 17, 2019

Hi @k0kubun,

I believe this was merged by @nobu back in November 2018 in the following commits and is part of Ruby 2.6:

Please feel free to close the PR.

@k0kubun
Copy link
Member

k0kubun commented Aug 17, 2019

Oh, I see. Thank you 👍

@k0kubun k0kubun closed this Aug 17, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
5 participants