From: aa Date: 2007-10-20T01:20:04+09:00 Subject: block composition? Hi, I'm implementing a plain simple search functionality over an array of objects. One of the attributes that those objects expose is a creation date (a Time object). The search functionality should work in 3 ways: - starting from a date - up to a date - within a certain range In the first implementation I had the same code repeated in different search methods: def search_from(start) @list.select {|s| s.creation >= start} end def search_to(_end) @list.select {|s| s.creation <= _end} end def search(start, _end) @list.select {|s| s.creation >= start && s.creation <= _end} end So I wanted to remove the duplication and I thought about defining search_from and search_to in terms of search. I tried using some sort of block composition by means of lambda functions, but I didn't really like it: def search_from(start) search(start, nil) end def search_to(_end) search(nil, _end) end def search(start, _end) upper_limit = if start.nil? lambda {|s| true} else lambda {|s| s.creation >= start} end lower_limit = if _end.nil? lambda {|s| true} else lambda {|s| s.creation <= _end} end @list.select {|s| upper_limit.call(s) && lower_limit.call(s)} end In the end I've decided to override <=> on Time so that if you compare with nil it returns 0 but: - it's really ugly - as a side effect Time.mktime(2007) == nil evaluates to true (while of course Time.mktime(2007).nil? is false) - your code may break in unexpected ways Does anybody know a better approach towards block composition? Thanks! AA PS: here is the current code with the ugly, horrible, too bad Time.<=> override. class Time alias_method :comp, "<=>".intern def <=>(other) if other.nil? 0 else comp(other) end end end class Stuff attr_accessor :name attr_accessor :creation def initialize(name, creation) @name = name @creation = creation end end class Repository def initialize(list) @list = list end def search_from(start) search(start, nil) end def search_to(_end) search(nil, _end) end def search(start, _end) @list.select {|s| s.creation >= start && s.creation <= _end} end end require 'test/unit' class TestRepository < Test::Unit::TestCase def setup @jan = Stuff.new("jan", Time.mktime(2007, 1)) @feb = Stuff.new("feb", Time.mktime(2007, 2)) @rep = Repository.new([@jan, @feb]) end def test_search_returns_values_starting_from_creation_date assert_equal([], @rep.search_from(Time.mktime(2008))) assert_equal([@feb], @rep.search_from(Time.mktime(2007, 1, 15))) assert_equal([@jan, @feb], @rep.search_from(Time.mktime(2006, 12))) end def test_search_returns_values_up_to_creation_date assert_equal([], @rep.search_to(Time.mktime(2006))) assert_equal([@jan], @rep.search_to(Time.mktime(2007, 1, 15))) assert_equal([@jan, @feb], @rep.search_to(Time.mktime(2008))) end def test_search_returns_values_within_a_range_of_creation_dates assert_equal([], @rep.search(Time.mktime(2005), Time.mktime(2006))) assert_equal([@jan], @rep.search(Time.mktime(2006), Time.mktime(2007, 1, 15))) assert_equal([@jan, @feb], @rep.search(Time.mktime(2006), Time.mktime(2008))) end end