Def Method
Software Development & Consulting
backdrop.png

Def Method Blog

The thoughts and ponderings of the team @defmethodinc

Declarative Programming in Ruby

 

By Randall Reed

I’ve been programming in Ruby for almost two years now, and sometimes I forget how hard it was for me to wrap my head around the Ruby approach to solving problems. Finally, Tyler Mcginnis has given me the vocabulary to describe the paradigm shift I experienced — Imperative vs Declarative code.

With imperative code, you’re telling your program how to do something. And with declarative code, you’re telling your program what you want to do.
-Tyler Mcginnis, React Fundamentals

Coming from a C++ background, I was used to imperative programming; those mental pathways were well-trodden. Although Ruby can be used to write imperative code, its expressiveness makes declarative code possible. The ‘each’ method was a revelation to someone who had been writing ‘for’ and ‘while’ loops for years.

Example: Performing an operation on every element of an array

  • Imperative Anti-pattern: Manually keeping track of array index
i = 0
while i < array.length do
puts array[i]
end
  • Declarative alternative: #each
array.each do |element|
puts element
end

However, Ruby doesn’t stop there. If you still find yourself defaulting to using each for every problem and using local variables to keep track of state, consider whether a simpler solution exists. Here are some anti-patterns to watch for, along with iterators that will make your code more declarative.

Anti-patterns in Ruby Iterators

Creating a new array from an existing array

  • Desired return type: Array
  • Imperative Anti-pattern: Manually adding elements to new array
squares = []
array.each do | element |
 squares << element ** 2
end
squares
array.collect { | element | element ** 2 }

Returning a subset of an array satisfying criterion

  • Desired return type: Array
  • Imperative Anti-pattern: Manually adding elements to new array
odds = []
array.each do | element |
 odds << element if element.odd?
end
odds
array.select { | element | element.odd? }

Returning the first element of an array satisfying a certain criterion

  • Desired return type: Object (whatever class the element is)
  • Imperative Anti-pattern: Using a local variable to store element
first_positive = 0
array.each do | element |
 first_positive = element if element > 0
end
first_positive
array.detect { | element | element > 0 }

Determining the number of elements of an array satisfying a certain criterion

  • Desired return type: Integer
  • Imperative Anti-pattern: Manually incrementing a counter
match_count = 0
array.each do | element |
 match_count += 1 if element % 10 == 0
end
match_count
  • Declarative alternative: #count
array.count { | element | element % 10 == 0 }

Determining whether any elements of an array share a certain characteristic

  • Desired return type: Boolean
  • Imperative Anti-pattern: Manually toggling a boolean flag
is_present = false
array.each do | element |
 is_present = true if element < 0
end
is_present
  • Declarative alternative: #any?
array.any? do | element |
 element < 0
end

Determining whether all elements of an array share a certain characteristic

  • Desired return type: Boolean
  • Imperative Anti-pattern: Manually toggling a boolean flag
all_even = true
array.each do | element |
 all_even = all_even && element.even?
end
all_even
array.all? do | element |
 element.even?
end

Conclusion

Once you’ve gotten the hang of declarative programming, imperative programming begins to feel a bit like micro-managing. Learn to let the details go; stop worrying about managing state and put Ruby’s iterators to work for you.

 
Def Method