~2 minutes

ActiveSupport: Array Extensions

It seems like such a simple thing, but I’m really enjoying reading the source of Rails. I’m finding all sorts of hidden gems (ouch).

For instance, check out this interesting extension added to Array in “core_ext/array/extract_options.rb”

class Array
  # Extracts options from a set of arguments. Removes and returns the last
  # element in the array if it's a hash, otherwise returns a blank hash.
  #
  #   def options(*args)
  #     args.extract_options!
  #   end
  #
  #   options(1, 2)        # => {}
  #   options(1, 2, a: :b) # => {:a=>:b}
  def extract_options!
    if last.is_a?(Hash) && last.extractable_options?
      pop
    else
      {}
    end
  end
end

I think I’ve rolled my own version of this at least a few times after seeing too many arguments in the signature.

Or consider this clever extension in “core_ext/array/grouping.rb”

  # Divides the array into one or more subarrays based on a delimiting +value+
  # or the result of an optional block.
  #
  #   [1, 2, 3, 4, 5].split(3)              # => [[1, 2], [4, 5]]
  #   (1..10).to_a.split { |i| i % 3 == 0 } # => [[1, 2], [4, 5], [7, 8], [10]]
  def split(value = nil, &block)
    inject([[]]) do |results, element|
      if block && block.call(element) || value == element
        results << []
      else
        results.last << element
      end

      results
    end
  end

Having just gone through “Learn You a Haskell for Great Good”, I’ve been wanting to see some Ruby examples in the wild of something which should be familiar to the functional and/or lispy crowds. Since Enumerable#inject is not used as much as it should be, let’s review it’s usage.

Inject (also called reduce) takes an enumerable (a thing which meets a couple requirements but basically means you can iterate over) and applies an operation to all of it’s elements and accumulates a resulting value. Simple case:

(1..5).inject(0,:+) # => 0 + 1 + 2 + 3 + 4 + 5 = 15

You can also leave off the accumulator if the first element of your enum is an acceptable starting accumulator, so the above could be

(1..5).reduce(:+) # => 1 + 2 + 3 + 4 + 5 = 15

In addition, it can take a block instead of a binary operation:

(1..5).reduce {|results,x| results + x } # => 15

Now that we’ve reviewed that, we can see the cleverness in Array#split. It has a starting accumulator of [[]] and instead of a simple binary operation :+ it takes a block and applies it to each element in enum. That’s where the logic happens, if the block returns a truthy value, we start a new empty array in the accumulator, otherwise we push the element onto the last array in the accumulator.

That’s it for now!

Tagged with: programming

My first novel is coming soon-ish!

Check out Singular