diff --git a/lib/puppet/functions/reduce.rb b/lib/puppet/functions/reduce.rb index 26b985761..5b54e41c5 100644 --- a/lib/puppet/functions/reduce.rb +++ b/lib/puppet/functions/reduce.rb @@ -1,102 +1,94 @@ # Applies a parameterized block to each element in a sequence of entries from the first # argument (_the enumerable_) and returns the last result of the invocation of the parameterized block. # # This function takes two mandatory arguments: the first should be an Array, Hash, or something of # enumerable type, and the last a parameterized block as produced by the puppet syntax: # # $a.reduce |$memo, $x| { ... } # reduce($a) |$memo, $x| { ... } # # When the first argument is an Array or someting of an enumerable type, the block is called with each entry in turn. # When the first argument is a hash each entry is converted to an array with `[key, value]` before being # fed to the block. An optional 'start memo' value may be supplied as an argument between the array/hash # and mandatory block. # # $a.reduce(start) |$memo, $x| { ... } # reduce($a, start) |$memo, $x| { ... } # # If no 'start memo' is given, the first invocation of the parameterized block will be given the first and second # elements of the enumeration, and if the enumerable has fewer than 2 elements, the first # element is produced as the result of the reduction without invocation of the block. # # On each subsequent invocation, the produced value of the invoked parameterized block is given as the memo in the # next invocation. # # @example Using reduce # # # Reduce an array # $a = [1,2,3] # $a.reduce |$memo, $entry| { $memo + $entry } # #=> 6 # # # Reduce hash values # $a = {a => 1, b => 2, c => 3} # $a.reduce |$memo, $entry| { [sum, $memo[1]+$entry[1]] } # #=> [sum, 6] # # # reverse a string # "abc".reduce |$memo, $char| { "$char$memo" } # #=>"cbe" # # It is possible to provide a starting 'memo' as an argument. # # @example Using reduce with given start 'memo' # # # Reduce an array # $a = [1,2,3] # $a.reduce(4) |$memo, $entry| { $memo + $entry } # #=> 10 # # # Reduce hash values # $a = {a => 1, b => 2, c => 3} # $a.reduce([na, 4]) |$memo, $entry| { [sum, $memo[1]+$entry[1]] } # #=> [sum, 10] # # @example Using reduce with an Integer range # # Integer[1,4].reduce |$memo, $x| { $memo + $x } # #=> 10 # # @since 3.2 for Array and Hash # @since 3.5 for additional enumerable types # @note requires `parser = future`. # Puppet::Functions.create_function(:reduce) do dispatch :reduce_without_memo do param 'Any', :enumerable - required_block_param + required_block_param 'Callable[2,2]', :block end dispatch :reduce_with_memo do param 'Any', :enumerable param 'Any', :memo - required_block_param + required_block_param 'Callable[2,2]', :block end - require 'puppet/util/functions/iterative_support' - include Puppet::Util::Functions::IterativeSupport - def reduce_without_memo(enumerable, pblock) - assert_serving_size(pblock) enum = asserted_enumerable(enumerable) enum.reduce {|memo, x| pblock.call(nil, memo, x) } end def reduce_with_memo(enumerable, given_memo, pblock) - assert_serving_size(pblock) enum = asserted_enumerable(enumerable) enum.reduce(given_memo) {|memo, x| pblock.call(nil, memo, x) } end - # Asserts number of lambda parameters with more specific error message than the generic - # mis-matched arguments message that is produced by the dispatcher's type checking. - # - def assert_serving_size(pblock) - serving_size = pblock.parameter_count - unless serving_size == 2 || pblock.last_captures_rest? && serving_size <= 2 - raise ArgumentError, "reduce(): block must define 2 parameters; memo, value. Block has #{serving_size}; "+ - pblock.parameter_names.join(', ') + def asserted_enumerable(obj) + unless enum = Puppet::Pops::Types::Enumeration.enumerator(obj) + raise ArgumentError, ("#{self.class.name}(): wrong argument type (#{obj.class}; must be something enumerable.") end + enum end + end diff --git a/spec/unit/functions/reduce_spec.rb b/spec/unit/functions/reduce_spec.rb index eb2eacac1..80f4d5159 100644 --- a/spec/unit/functions/reduce_spec.rb +++ b/spec/unit/functions/reduce_spec.rb @@ -1,92 +1,93 @@ require 'puppet' require 'spec_helper' require 'puppet_spec/compiler' require 'matchers/resource' describe 'the reduce method' do include PuppetSpec::Compiler include Matchers::Resource before :all do # enable switching back @saved_parser = Puppet[:parser] # These tests only work with future parser end after :all do # switch back to original Puppet[:parser] = @saved_parser end before :each do node = Puppet::Node.new("floppy", :environment => 'production') @compiler = Puppet::Parser::Compiler.new(node) @scope = Puppet::Parser::Scope.new(@compiler) @topscope = @scope.compiler.topscope @scope.parent = @topscope Puppet[:parser] = 'future' end context "should be callable as" do it 'reduce on an array' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,2,3] $b = $a.reduce |$memo, $x| { $memo + $x } file { "/file_$b": ensure => present } MANIFEST expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present') end it 'reduce on an array with captures rest in lambda' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,2,3] $b = $a.reduce |*$mx| { $mx[0] + $mx[1] } file { "/file_$b": ensure => present } MANIFEST expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present') end it 'reduce on enumerable type' do catalog = compile_to_catalog(<<-MANIFEST) $a = Integer[1,3] $b = $a.reduce |$memo, $x| { $memo + $x } file { "/file_$b": ensure => present } MANIFEST expect(catalog).to have_resource("File[/file_6]").with_parameter(:ensure, 'present') end it 'reduce on an array with start value' do catalog = compile_to_catalog(<<-MANIFEST) $a = [1,2,3] $b = $a.reduce(4) |$memo, $x| { $memo + $x } file { "/file_$b": ensure => present } MANIFEST expect(catalog).to have_resource("File[/file_10]").with_parameter(:ensure, 'present') end it 'reduce on a hash' do catalog = compile_to_catalog(<<-MANIFEST) $a = {a=>1, b=>2, c=>3} $start = [ignored, 4] $b = $a.reduce |$memo, $x| {['sum', $memo[1] + $x[1]] } file { "/file_${$b[0]}_${$b[1]}": ensure => present } MANIFEST expect(catalog).to have_resource("File[/file_sum_6]").with_parameter(:ensure, 'present') end it 'reduce on a hash with start value' do catalog = compile_to_catalog(<<-MANIFEST) $a = {a=>1, b=>2, c=>3} $start = ['ignored', 4] $b = $a.reduce($start) |$memo, $x| { ['sum', $memo[1] + $x[1]] } file { "/file_${$b[0]}_${$b[1]}": ensure => present } MANIFEST expect(catalog).to have_resource("File[/file_sum_10]").with_parameter(:ensure, 'present') end end + end