Thinking in Functional Ruby 6

Posted by yrashk

This article is a result of yesterday’s code review session I have done.

I have found that I don’t like the following code (well, a code like this):

def topmost_matches(str,limit=3)
  matches = []
  str.each(' ') do |word|
    word = word.strip.downcase
    matches << word if Entity.find_by_name(word)
  end
  return matches[0, limit] if matches.size > limit
  matches
end

Here is what we can do with this code to make it better:

Well, first of all, let’s simplify last two lines. Since if even matches.size is less than limit, matches[0,limit] will return the whole matches array. This means that we don’t need any kind of explicit condition:

  matches[0,limit]

Then, I really can’t say that the rest of the code is easy to read and understand. It is not readable, it is rather interpretable. And here we can use some Ruby power to do this stuff meach easier and nicer:

 str.split(/\s+/).         # here we split str by whitespace characters
      collect(&:downcase). # then we downcase each word
      select{|word| Entity.find_by_name(word)}[0,limit] 
                           # ...and finally we select that words
                           # that are found within Entities names

Also, it is pretty easy to add even a more complex functionality to this single functional “query”. For example, we also may want to sort results according Entities ratings, descending. No problem!

 str.split(/\s+/). 
      collect(&:downcase).
      select{|word| Entity.find_by_name(word)}.
      collect{|word| Entity.find_by_name(word)}. # collect respective Entities
      sort_by(&:rating).reverse.                 # sort them by rating, descending
      collect(&:name)[0,limit]                   # collect their names, and return topmost 
                                                 # of them

Of course, the above code is still a heaven for code reviewer, but it is out of this article’s scope.

Update: According to improvements proposed in comments, it could be even that easy:

 str.split(/\s+/). 
      collect(&:downcase).
      collect{|word| Entity.find_by_name(word)}.compact.
      sort_by(&:rating).reverse.               
      collect(&:name).first(limit)
Comments

Leave a response

  1. sergey.kojin@gmail.comJanuary 11, 2007 @ 08:24 AM

    I think in last example, select{|word| Entity.find_by_name(word)} is not need, for be better use compact() collect{|word| Entity.find_by_name(word)}.compact

    (less sql queries)

  2. Yurii RashkovskiiJanuary 11, 2007 @ 08:28 AM

    Sergey,

    Absolutely agreed, this is what I was thinking also in particular when was writing “Of course, the above code is still a heaven for code reviewer, but it is out of this article’s scope.”

    I just wanted to let last example share the same beginning with the first example.

  3. ChrisJanuary 13, 2007 @ 09:58 PM

    array[0,limit] or array.first(limit)

  4. Yurii RashkovskiiJanuary 13, 2007 @ 10:02 PM

    Chris,

    Yea, as I’ve said at the end of article, there is still a room for numerous improvements, and surely your improvement makes sense!

  5. Michael KlishinJanuary 14, 2007 @ 04:22 AM

    Looks sweet. It would be a bit more readable if you align comments by right margin so it won’t fuse with the code.

  6. Yurii RashkovskiiJanuary 14, 2007 @ 04:33 AM

    Michael,

    Yes, you’re right. Looks much better now while having syntax unhighlighted.

Comment