Ruby, iOS, and Other Development

A place to share useful code snippets, ideas, and techniques

All code in posted articles shall be considered public domain unless otherwise noted.
Comments remain the property of their authors.

2006-06-15

Enumerable#bucket_by (and #uniq_by and #uniq_by!)

I just learned about Enumerable#partition and immediately had the desire to partition into more than two parts. The #partition method is certainly ideal for implementing a quicksort, but for general bucketing there is a need for something less boolean:

module Enumerable
  def bucket_by
    hash = Hash.new { |h,k| h[k] = [] }
    each { |v| hash[yield(v)] << v }
    hash.default = nil
    hash
  end
end

The bucket_by method takes a block and returns a hash of arrays of objects keyed by the block return values for each element. Simple and convenient. And hey, while we're using blocks for stand-in values and _by suffixes:

module Enumerable
  def uniq_by
    seen = {}
    select { |v|
      key = yield(v)
      (seen[key]) ? nil : (seen[key] = true)
    }
  end
end
class Array
  def select!
    reject! { |v| not yield(v) }
  end
  def uniq_by!
    seen = {}
    select! { |v|
      key = yield(v)
      (seen[key]) ? nil : (seen[key] = true)
    }
  end
end

Enjoy!

Update! See this post with a slightly cleaner implementation.

Labels:

2006-06-11

Class#extract_instance_method and Object#extract_singleton_method

This is sort of kinky, but sometimes it's just what you need. Particularly when extending (rather than including) modules, it is sometimes desirable to preserve, but remove, an existing method. To that end I present:

class Module
  def extract_instance_method(sym)
    if instance_methods(false).include? sym.to_s
      method = instance_method(sym)
      remove_method(sym)
      method
    else
      nil
    end
  end
end

class Object
  def extract_singleton_method(sym)
    metaclass = class << self; self; end
    if method = metaclass.extract_instance_method(sym)
      method.bind(self).to_proc
    else
      nil
    end
  end
end
Enjoy!

Labels: ,

2006-06-09

Mixing in Class Methods

It is pretty common to have some conceptual block of functionality that is appropriate for a module, except it involves a combination of instance methods and class methods. The issue of singleton methods on a module not being added as singleton methods on a class when mixing in the module has come up repeatedly on the mailing list. While it is a compelling argument that wanting to mix in class methods as well as instance methods is the common case, having that as the only behavior prevents some desirable flexibility (particularly involving DSLs).

Instead, consider the following idiom (Updated! thanks, Sas):

module M
 module ClassMethods; end
 def self.included(klass)
   klass.extend(ClassMethods)
 end

 def foo
   puts "Calling class bar() method from foo()"
   self.class.bar
 end

 module ClassMethods
   def bar
     puts "Called #{self}::bar()"
   end
 end
end

We can now define methods intended to be mixed into the including class in the M::ClassMethods module. This is a naïve solution, however, and does not handle the situation in which a module is included in another module. Unfortunately, the simplicity of a ClassMethods module can't deal with including one module in another since the same ClassMethods module would be shared across both modules. In addition, we might like to provide some code to be called when our module is included, but the Module#included is what we use to make the class method mixin work. To make this easier, let's push the functionality into the Module class (which is the superclass of all modules just as Class is the superclass of all classes):

class Module
 private

 module MixinClassMethods
   def included_by_module(klass)
     #check to see if klass is already set up
     if not klass.instance_variables.include? '@class_method_module'
       klass.send(:mixin_class_methods)
     end
     klass_method_module =
       klass.instance_variable_get('@class_method_module')
     klass_method_module.send(:include, @class_method_module)
   end

   def included(klass)
     @extra_include_block.call(klass) if @extra_include_block
     case klass
       when Class
         klass.extend(@class_method_module)
       when Module
         #more work to include in a module
         included_by_module(klass)
     end
   end

   def define_class_methods(&block)
     @class_method_module.module_eval &block
   end

 end

 def mixin_class_methods(&block)
   #ensure the existence of the ClassMethods module
   if not (Module === (@class_method_module ||= Module.new))
     fail "@class_method_module is not a module!"
   end
   @extra_include_block = block
   extend MixinClassMethods
 end

end
It is now possible to do the following:
require 'mixin_class_methods'

module M
 mixin_class_methods { |klass|
   puts "Module M has been included by #{klass}"
 }

 def foo
   puts "Calling class bar() method from foo()"
   self.class.bar
 end

 define_class_methods {
   def bar
     puts "Called #{self}::bar()"
   end
 }
end

module N
 include M
end

class Baz
 include N
end

Baz.bar
Baz.new.foo
This will produce the following output:
Module M has been included by N
Called Baz::bar()
Calling class bar() method from foo()
Called Baz::bar()
Enjoy!

Labels: ,

2006-06-07

Ungreedy Regular Expressions in Ruby

I was recently working on a script to condense or pretty-print CSS. Condensing is actually pretty easy, but pretty-printing involved preserving comments while sorting style directives within rules. (For those who aren't familiar with CSS, its comments are delimited by /* and */ just like in C.) Matching comments, particularly multiline comments, is pretty easy as long as you can make your regular expressions ungreedy. The naïve, greedy regex /\/\*.*\*\//m (note the m option at the end, which sets the multiline option for the Regexp) will not stop at just one comment but will match everything from the beginning of the first comment to the end of the last comment, including all the uncommented code in between. This is clearly wrong, and the problem is that * (and +) is greedy (i.e. matches as much text as it can).

If greedy matching is the problem, how do we make it ungreedy? It turns out that Ruby takes a page from Perl regular expressions and whereas * (and +) is the greedy version, *? (and +?) is the ungreedy version. Thus our problem regex becomes /\/\*.*?\*\//m and works as desired.

This may not be quite as significant as previous posts, but it's really handy to know when you need it.

Labels: ,