aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authornobu <nobu@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2018-11-22 05:51:40 +0000
committernobu <nobu@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2018-11-22 05:51:40 +0000
commita43e967b8d277fee5cf18329b26bc5c31a3e0363 (patch)
treea73019bcd581bd65ce6b2e8e7941374c1da143ba
parent655257273af98a1cce848393c73a7c3012724c59 (diff)
downloadruby-a43e967b8d277fee5cf18329b26bc5c31a3e0363.tar.gz
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. From: Paul Mucur <mudge@mudge.name> git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@65911 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
-rw-r--r--proc.c54
-rw-r--r--test/ruby/test_proc.rb51
2 files changed, 105 insertions, 0 deletions
diff --git a/proc.c b/proc.c
index bf5921e9ce..efff796499 100644
--- a/proc.c
+++ b/proc.c
@@ -3046,6 +3046,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 <i>g</i>.
+ * The returned proc takes a variable number of arguments, calls <i>g</i> 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
*
@@ -3142,6 +3195,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 1f713f3dbc..7ea4556e8a 100644
--- a/test/ruby/test_proc.rb
+++ b/test/ruby/test_proc.rb
@@ -1416,4 +1416,55 @@ class TestProc < Test::Unit::TestCase
def test_proc_without_block_for_symbol
assert_equal('1', method_for_test_proc_without_block_for_symbol(&:to_s).call(1), '[Bug #14782]')
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_predicate(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