From d138b2e91c5752d332b4e07017c82e3c0396836b Mon Sep 17 00:00:00 2001 From: Paul Mucur Date: Sun, 14 Jun 2015 17:20:53 +0100 Subject: [PATCH 1/3] proc.c: Implement Proc#* for Proc composition * proc.c (proc_compose): Implement Proc#* for Proc composition, enabling composition of Procs and Methods. [Feature #6284] * test/ruby/test_proc.rb: Add test cases for Proc composition. --- proc.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++ test/ruby/test_proc.rb | 51 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 105 insertions(+) diff --git a/proc.c b/proc.c index 6d153c1..c73fcf1 100644 --- a/proc.c +++ b/proc.c @@ -2818,6 +2818,59 @@ rb_method_curry(int argc, const VALUE *argv, VALUE self) return proc_curry(argc, argv, proc); } +static VALUE +compose(VALUE dummy, VALUE args, int argc, VALUE *argv, VALUE passed_proc) +{ + 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)); + + return rb_proc_call(f, fargs); +} + + /* + * call-seq: + * prc * g -> a_proc + * + * Returns a proc that is the composition of this proc and the given proc g. + * The returned proc takes a variable number of arguments, calls g with them + * then calls this proc with the result. + * + * f = proc {|x| x * 2 } + * g = proc {|x, y| x + y } + * h = f * g + * p h.call(1, 2) #=> 6 + */ +static VALUE +proc_compose(VALUE self, VALUE g) +{ + VALUE proc, args; + rb_proc_t *procp; + int is_lambda; + + if (!rb_obj_is_method(g) && !rb_obj_is_proc(g)) { + rb_raise(rb_eTypeError, + "wrong argument type %s (expected Proc/Method)", + rb_obj_classname(g)); + } + + if (rb_obj_is_method(g)) { + g = method_to_proc(g); + } + + args = rb_ary_new3(2, self, g); + + GetProcPtr(self, procp); + is_lambda = procp->is_lambda; + + proc = rb_proc_new(compose, args); + GetProcPtr(proc, procp); + procp->is_lambda = is_lambda; + + return proc; +} + /* * Document-class: LocalJumpError * @@ -2906,6 +2959,7 @@ Init_Proc(void) rb_define_method(rb_cProc, "lambda?", rb_proc_lambda_p, 0); rb_define_method(rb_cProc, "binding", proc_binding, 0); rb_define_method(rb_cProc, "curry", proc_curry, -1); + rb_define_method(rb_cProc, "*", proc_compose, 1); rb_define_method(rb_cProc, "source_location", rb_proc_location, 0); rb_define_method(rb_cProc, "parameters", rb_proc_parameters, 0); diff --git a/test/ruby/test_proc.rb b/test/ruby/test_proc.rb index b67773b..a6e9bcc 100644 --- a/test/ruby/test_proc.rb +++ b/test/ruby/test_proc.rb @@ -1341,4 +1341,55 @@ def g e.each {} EOS end + + def test_compose + f = proc{|x| x * 2} + g = proc{|x| x + 1} + h = f * g + + assert_equal(6, h.call(2)) + end + + def test_compose_with_multiple_args + f = proc{|x| x * 2} + g = proc{|x, y| x + y} + h = f * g + + assert_equal(6, h.call(1, 2)) + end + + def test_compose_with_block + f = proc{|x| x * 2} + g = proc{|&blk| blk.call(1) } + h = f * g + + assert_equal(8, h.call { |x| x + 3 }) + end + + def test_compose_with_lambda + f = lambda{|x| x * 2} + g = lambda{|x| x} + h = f * g + + assert(h.lambda?) + end + + def test_compose_with_method + f = proc{|x| x * 2} + c = Class.new { + def g(x) x + 1 end + } + g = c.new.method(:g) + h = f * g + + assert_equal(6, h.call(2)) + end + + def test_compose_with_nonproc_or_method + f = proc{|x| x * 2} + + assert_raise(TypeError) { + f * 5 + } + end end -- 2.6.4