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

On Oct 19, 2007, at 10:20 AM, aa wrote:

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

why not something simple like

def search &filter
@list.select &filter
end

def search_from time
search{|s| s.creation and s.creation >= time}
end

def search_in time_range
search{|s| s.creation and time_range === s.creation}
end

def search_until time
search{|s| s.creation and s.creation <= time}
end

??

a @ http://codeforpeople.com/

On Oct 19, 1:57 pm, “ara.t.howard” [email protected] wrote:

why not something simple like
[good stuff snipped]

Well, because:

  • I haven’t thought about using a Time range
  • it didn’t occur to me that passing the filter to the iterator was
    the best idea to decouple iteration with selection criteria
    Quite neat!

Nevertheless, I’m still interested to know if anybody has ever
composed different blocks together within the same iteration (and
how).

Thanks a lot.
AA

On 10/19/07, aa [email protected] wrote:

Nevertheless, I’m still interested to know if anybody has ever
composed different blocks together within the same iteration (and
how).

Thanks a lot.
AA

Do you mean something like this?

def search_from(start)
search_all proc {|s| s.creation >= start }
end
def search_to(_end)
search_all proc {|s| s.creation <= _end }
end
def search(start, _end)
search_all proc {|s| s.creation >= start }, proc { |s| s.creation <=
_end
}
end
def search_all(*blocks)
@list.select {|s|
blocks.all? { |blk| blk.call(s) }
}
end

Unfortunately, you can’t pass more than one block at a time - you have
to
convert them to procs.

Regards,
Sean

This forum is not affiliated to the Ruby language, Ruby on Rails framework, nor any Ruby applications discussed here.

| Privacy Policy | Terms of Service | Remote Ruby Jobs