How to use lambdas instead of commands/service objects

I like my interfaces to be cleaner than my implementations and having to use a method as Dog::Bark.new(my_dog).call instead of my_dog.bark is not straightforward. So I take advantage of closures, as I learned from Structure and Interpretation of Computer Programs (a.k.a. SCIP); this book’s style is very similar to what Robert Martin teaches in chapters 3 and 10 of his book Clean Code. But with the advantage of SCIP being more suitable for scripting languages like Ruby (the Scheme language is closer to Ruby than Java).

So instead of:

def average(x, y) = (x + y)/2

class Sqrt
  def initialize(x) = @x = x
  def call() = sqrt_iter(1.0)

  private
  def good_enough?(guess) = (guess**2 - @x).abs < 0.001
  def improve(guess) = average(guess, (@x / guess))
  def sqrt_iter(guess)
    if good_enough?(guess)
      return guess
    else
      return sqrt_iter(improve(guess))
    end
  end
end

I would write my code as:

def average(x, y) = (x + y)/2

def sqrt(x)
  good_enough = ->(guess) {(guess**2 - x).abs < 0.001}
  improve = ->(guess) {average(guess, (x / guess))}
  sqrt_iter = lambda do |guess|
    if good_enough.call(guess)
      return guess
    else
      return sqrt_iter.call(improve.call(guess))
    end
  end

  sqrt_iter.call(1.0)
end

Service objects also tend to add unnecessary indirections to your code:

class Cart
  def checkout
    Service::Cart::Checkout.new(params).call
  end
end

So, to make your interfaces cleaner and reduce indirections in your code, or if you like a more functional code style like me, I recommend using more lambdas. I have written a Ruby gem to illustrate how you can replace service objects for lambdas: command_chain | RubyGems.org | your community gem host.

I use the algorithm in the gem to write codes like this:

def method_name
  do1 = lambda {|obj| puts "first execution"}
  undo1 = lambda {|obj| puts "third execution"}

  do2 = lambda {|obj| puts "second execution"}

  do3 = lambda {|obj| raise CommandChain::Error.new("error")}
  undo3 = lambda {|obj| puts "will not execute"}

  do4 = lambda {|obj| puts "will not execute"}
  undo4 = lambda {|obj| puts "will not execute"}

  CommandChain::Chain.new(
    [
      [do1, undo1],
      [do2, nil],
      [do3, undo3],
      [do4, undo4],
    ]
  ).execute
end
1 Like