Wait ProcArrays, you mean BlockArrays, well no. Ruby doesn't like it when you try
to call a method on a block (eg. my_method {|f| ... }.something(1)
) see EOF.
so instead the first thing must be a Proc, then you can chain blocks on to
that as it is OK to call methods on a Proc. So here is an example:
def call_them(procs)
procs.call
end
# Note I used the `->` proc style because it is nicer, `proc` would still work.
call_them ->{ "First" }.and { "Second" }
#=> ["First", "Second"]
So, above I passed two blocks/procs (proc with a block really) to a method and called them. The results of calling each proc were then returned in an Array. But we can do more interesting things than just call the Procs in the ProcArray. We can pass them arguments as well:
def call_them(procs)
procs.call(['dogs'], ['like', 'cats'])
end
call_them ->(noun){ puts noun }.and {|verb, noun| puts "#{verb} #{noun}" }
#=> "dogs"
#=> "like cats"
See that I passed the list of arguments for each Proc as an Array, allowing me to pass two arguments to the last Proc/block.
ProcArray responds to #size
and I've also monkey patched Proc to as well, so it
doesn't matter if a single Proc or a massive ProcArray is passed you can check it's
#size
.
def two_to_four_procs(procs)
if procs.size < 2
raise '#two_to_four_procs expects at least two procs'
elsif procs.size > 4
raise '#two_to_four_procs expects at most four procs'
else
# do something
end
end
Remember ProcArray is a subclass of Array so you can use normal array methods as expected.
procs = ->(i){ i * 2 }.and {|i| i * 3 }.and {|i| i * 4 }
procs.inject(2) {|a,e| e.call(a) }
#=> 48 # ((2 * 2) * 3) * 4
procs.map {|i| i.call(2) }
#=> [4, 6, 8]
# OR
procs.call([2], [2], [2])
#=> [4, 6, 8]
# OR
procs.call *([2] * 3)
#=> [4, 6, 8]
procs.find_all {|i| i.call(1) % 2 == 0 }
#=> [->(i){ i * 2 }, ->(i){ i * 4 }]
procs.each do |prc|
puts prc.call(5)
end
#=> 10
#=> 15
#=> 20
two, three, four = procs
two[50]
#=> 100
three[100]
#=> 300
four[500]
#=> 2000
If you attempt
def m(&block)
block.call
end
m { puts 'First' }.and { puts 'Second' }
# NoMethodError: undefined method `and' for nil:NilClass
# from (irb):11
# from /Users/Josh/.rbenv/versions/1.9.2-p290/bin/irb:12:in `<main>'
It raises a NoMethodError because for some reason it tries to call #and on the return value of the calling the first block. This is I think due to ruby on seeing a block parsing it in a special way as (kind of) shown below.
require 'ripper'
Ripper.sexp "method {}.and {}"
#=> [:program,
[[:method_add_block,
[:call,
[:method_add_block,
[:method_add_arg, [:fcall, [:@ident, "method", [1, 0]]], []],
[:brace_block, nil, [[:void_stmt]]]],
:".",
[:@ident, "and", [1, 10]]],
[:brace_block, nil, [[:void_stmt]]]]]]
Ripper.sexp "method ->{}.and {}"
#=> [:program,
[[:command,
[:@ident, "method", [1, 0]],
[:args_add_block,
[[:method_add_block,
[:call,
[:lambda, [:params, nil, nil, nil, nil, nil], [[:void_stmt]]],
:".",
[:@ident, "and", [1, 13]]],
[:brace_block, nil, [[:void_stmt]]]]],
false]]]]
Prefer to avoid reopening Proc. Like the chaining-based approach; however, it doesn't allow for routing the call to a particular Proc based on the supplied input.