---
title: Expressing the problem
teaser: Trade-offs between data and behavior.
tags: web,ruby,good code
author: Mike Burns
published_on: 2012-08-01
---

Object-oriented languages are good at adding new data; functional languages are
good at adding new behavior. Can we find a happy medium?

The answer: Sorta.

First I'll explain what I'm talking about, with examples. At the bottom of this
article I'll talk about the lambda calculus. Oh and before that I'll talk about
something relevant to normal Ruby programming, in case that's something you're
still into.

## The problem

But first let's start even simpler: we are going to represent addition in Ruby.

```ruby
class Literal
  def initialize(n)
    @n = n
  end

  def evaluate
    @n
  end
end

class Addition
  def initialize(a, b)
    @a = a
    @b = b
  end

  def evaluate
    @a.evaluate + @b.evaluate
  end
end
```

No magic going on here. We can evaluate an expression:

    ruby-1.9.2-p290> Addition.new(Literal.new(2), Addition.new(Literal.new(3), Literal.new(5))).evaluate
    => 10

So the claim is that it's easy to add new data. What that means is that we can
add a new class and use it quickly:

```ruby
class Boolean
  def initialize(bool)
    @bool = bool
  end

  def evaluate
    @bool
  end
end

class IfThenElse
  def initialize(b, t, f)
    @b = b
    @t = t
    @f = f
  end

  def evaluate
    if @b.evaluate
      @t.evaluate
    else
      @f.evaluate
    end
  end
end
```

Above we have added Booleans and conditionals to our language. We haven't added
the concept of "less than" or "equal to zero" or anything like that, so for now
we can only hard-code truth and lies.

    ruby-1.9.2-p290> IfThenElse.new(
      Boolean.new(true),
      Addition.new(Literal.new(5),
                   Addition.new(Literal.new(3), Literal.new(2))),
      Literal.new(0)).evaluate
    => 10

Further, the claim is that it's hard to add new behavior. As an example of what
that means, consider adding `to_s` for our little arithmetic language. Here are
some ways to do that:

* Open each class and add a `to_s` method. This can be done either by modifying
  the file directly (very possible if it's in our codebase), or by using Ruby's
  open classes. Ruby's open classes are brittle when dealing with private data,
  so we should consider avoiding that.
* Define subclasses, either via composition or inheritance, that add the desired
  debugging behavior. This would mean re-writing every use of `IfThenElse.new`
  with `DebuggingIfThenElse.new`, as needed.

Both of those options are OK, and will absolutely work perfectly in some
situations. Let's explore another option: abstract factories.

## A solution

The abstract factory pattern is described like this:

> Provide an interface for creating families of related or dependent objects
> without specifying their concrete classes.

We can define (and
[name](https://thoughtbot.com/blog/post/27907942818/name-it)!) our system as an
abstract factory:

```ruby
class AdditionAndNumbers
  def literal(n)
    Literal.new(n)
  end

  def addition(a, b)
    Addition.new(a, b)
  end
end
```

And we can make use of this as before:

    ruby-1.9.2-p290> system = AdditionAndNumbers.new
    ruby-1.9.2-p290> system.addition(system.literal(1), system.literal(3)).evaluate
    => 10

We can add new data types to our system:

```ruby
class BooleansWithAdditionAndNumbers < AdditionAndNumbers
  def boolean(b)
    Boolean.new(b)
  end

  def if_then_else(b, t, f)
    IfThenElse.new(b, t, f)
  end
end
```

And make use of those, too:

    ruby-1.9.2-p290> system = BooleansWithAdditionAndNumbers.new
    ruby-1.9.2-p290> system.if_then_else(
      system.boolean(true),
      system.addition(
        system.literal(5),
        system.addition(
          system.literal(3),
          system.literal(2))),
      system.literal(0)).evaluate
    => 10

And we can add our desired stringification. Here's where the "sorta" in my
introduction shines:

```ruby
class ShowLiteral
  def initialize(n)
    @n = n
  end

  def to_s
    @n.to_s
  end
end

class ShowAddition
  def initialize(a, b)
    @a = a
    @b = b
  end

  def to_s
    "#{@a.to_s} + #{@b.to_s}"
  end
end

class ShowAdditionAndNumbers
  def literal(n)
    ShowLiteral.new(n)
  end

  def addition(a, b)
    ShowAddition.new(a, b)
  end
end
```

Here I've defined a new abstract factory that produces objects that respond to
`to_s` usefully. The code that uses this system does not change, as it still
calls `literal` and `addition` as before, except now it calls `to_s` instead of
`evaluate`.

    ruby-1.9.2-p290> system = ShowAdditionAndNumbers.new
    ruby-1.9.2-p290> system.addition(system.literal(1), system.literal(3)).to_s
    => "1 + 3"

In fact, it doesn't even need to be that way: the `to_s` method could instead
have been called `evaluate`. However, this produces a `String` instead of a
`Fixnum`, so that's weird.

## The complaint

That's a lot of small classes!

Boo hoo. Don't use this pattern unless you need it.

## Another example

Factory Bot has a bunch of strategies for factorying-up some data. Here are
some massive simplifications, for example:

```ruby
class Build
  def initialize(class_name, attributes)
    @class_name = class_name
    @attributes = attributes
  end

  def run
    @class_name.to_s.camelize.constantize.send(:new, @attributes)
  end
end

class Create
  def initialize(class_name, attributes)
    @class_name = class_name
    @attributes = attributes
  end

  def run
    @class_name.to_s.camelize.constantize.send(:create, @attributes)
  end
end
```

New strategies are easy to add, but new behavior is not. Again I'll use the
example of inspecting into a `String`, but this time I'll make use of the
existing `run` method instead of a new `to_s` method. Again, just an example.

We start by making an abstract factory:

```ruby
class Strategy
  def build(class_name, attributes)
    Build.new(class_name, attributes).run
  end

  def create(class_name, attributes)
    Create.new(class_name, attributes).run
  end
end
```

Here we're going to go on a tangent to add an extension point to Factory Bot.
This is because, given the above change, Factory Bot will have some code like:

```ruby
class FactoryBot
  def build(class_name, attributes = {})
    strategy.build(class_name, attributes)
  end

  def create(class_name, attributes = {})
    strategy.create(class_name, attributes)
  end

  def plugin(strategy_name, class_name, attributes = {})
    strategy.send(strategy_name, class_name, attributes)
  end

  private

  def strategy
    Strategy.new
  end
end
```

That private method is brutal. So it instead needs to be:

```ruby
class FactoryBot
  def self.strategy=(s)
    @@strategy = s
  end

  # ...

  private

  def strategy
    if defined?(@@strategy)
      @@strategy
    else
      Strategy.new
    end
  end
end
```

Whew. Tangent over.

This allows us to define `StubbingStrategy`:

```ruby
require 'mocha'

class Stub
  def initialize(class_name, attributes)
    @class_name = class_name
    @attributes = attributes
  end

  def run
    class_object.new.tap do |s|
      @attributes.each do |method, result|
        s.stubs(method).returns(result)
      end
    end
  end

  private

  def class_object
    @class_name.to_s.camelize.constantize
  end
end

class StubbingStrategy < Strategy
  def stub(class_name, attributes)
    Stub.new(class_name, attributes).run
  end
end
```

And then use it:

```ruby
FactoryBot.strategy = StubbingStrategy.new
user_stub = FactoryBot.new.plugin(:stub, :user)
```

## Add behavior

We're back to where we started; great. But, now we can add behavior on a whim:

```ruby
class DebuggingBuild
  def initialize(class_name, attributes)
    @class_name = class_name
    @attributes = attributes
  end

  def run
    "#{@class_name.to_s.camelize}.new(#{@attributes.inspect})"
  end
end

class DebuggingCreate
  def initialize(class_name, attributes)
    @class_name = class_name
    @attributes = attributes
  end

  def run
    "#{@class_name.to_s.camelize}.create(#{@attributes.inspect})"
  end
end

class DebuggingStrategy
  def build(class_name, attributes)
    DebuggingBuild.new(class_name, attributes).run
  end

  def create(class_name, attributes)
    DebuggingCreate.new(class_name, attributes).run
  end
end
```

This is a strategy that causes the normal build and create strategies to produce
strings instead of the normal data. We don't need to change anything else: just
set this as the strategy and use it:

    ruby-1.9.2-p290> FactoryBot.strategy = DebuggingStrategy.new
    ruby-1.9.2-p290> FactoryBot.new.create(:user)
    => "User.create({})"

## Read more

The gory details, including the arithmetic system example, come from this year's
[ECOOP](http://www.ecoop.org/) in a paper titled "[Extensibility for the Masses:
Practical Extensibility with Object
Algebras](http://www.cs.utexas.edu/~wcook/Drafts/2012/ecoop2012.pdf)" (PDF) by
Bruno C.d.S. Oliveira and William R. Cook.

I recommend reading the paper for more cool examples, such as concurrent
computation of the arithmetic system and a DSL for batch processing. They also
relate Church encodings to the visitor pattern, and abstract factories to
F-algebras. Throughout the paper they talk about static type analysis, too.

* * *

**Disclaimer:**

Looking for FactoryGirl? The library was renamed in 2017.
[Project name history can be found here.](https://github.com/thoughtbot/factory_bot/blob/master/NAME.md)
