OO for Blasphemers

There seems to be a quite a bit of talk these days about programming in an Object Oriented vs a Functional style, and while that divide is real (especially as specific implementations go), one must be aware of the similarities between the two in order to properly evaluate each of its own merits. While it’s easy to dismiss some of the loftier ideals of the Functional paradigm as heady and academic, one must not forget that ideas such as:

  • Garbage Collection
  • Type Inference
  • Higher Order Functions (functions that consume and/or return other functions, Enummerable methods are a great example)
  • List Comprehensions
  • Aspect Oriented Programming (think Rails before/after methods)
  • and much more were once rooted in Functional Academia, which turned out to be quite useful, in practice.

However, enough of that. Let’s delve into an exercise which hopes to illuminate just how similar the two can be.

Given a basic Person class, as one would typically write it in Ruby, you’d likely end up with something akin to this:

class Person
          def initialize(name, age)
            @name = name
            @age = age
          end

          def set_name(new_name)
            @name = new_name
          end

          def get_name
            @name
          end

          def get_age
            @age
          end
        end

(Pardon the explicit getters and setters, but for illustrative purposes please play along)

What we have here will allow us to create objects, which are classically defined as:

    "An object has state, behavior, and identity; the structure and
    behavior of similar objects are defined in their common class; the
    terms instance and object are interchangeable".

This enlightens us to some less obvious facts about Objects:

1.) My language doesn’t necessarily need to provide them 2.) An object essentially wraps up method dispatch and state

And perhaps you’re familiar with Closures, which are essentially stateful Function Objects (think Procs & Blocks in Ruby) which have access to their lexical environment. In other words, they “close over” free variables.

Aha!

From here, one could have the thought that Objects, in their most basic form, are essentially hashes of stateful functions!

This hit me hard when I first came to understand it, perhaps we and our Functional bretheren aren’t so different after all?

So, that’s fine and great and good theoretically, but how might this actually look. In (working!) Ruby code, no less.

Person = ->(name=nil, age=nil) do
          methods = {
              get_name: -> { name },
              set_name: ->(new_name) { name = new_name },
              get_age: -> { age }
          }

          ->(meth, *args) { methods[meth].call(*args) }
        end

        Person.call("Robert", 24)
         => #<Proc:0x0000010109b008@(irb):98 (lambda)>

So what just happened here? Our Person function/constructor has returned a function which represents an instance of Person.

For familiarities sake, a little syntactic sugar:

def Person.new(name, age)
          call(name, age)
        end

        robert = Person.new("Robert", 24)
         => #<Proc:0x0000010109b008@(irb):98 (lambda)>

        robert.call(:get_name)
         => "Robert"

        robert.call(:set_name, "Robert Pitts")
         => "Robert Pitts"

        robert.call(:get_name)
         => "Robert Pitts"

Oh, and as of earlier this, week, that age isn’t quite correct anymore, so let’s add a had_birthday function to our methods hash.

methods = {
              get_name: -> { name },
              set_name: ->(new_name) { name = new_name },
              get_age: -> { age },
              had_birthday: -> { age += 1 }
        }

        robert.call(:had_birthday)
         => 25

        robert.call(:get_age)
         => 25

As you can see, our state is being preserved correctly, much like a standard Ruby Object would behave.

There are some downfalls…

robert.call(:gracefully_handle_errors)
         => NoMethodError: undefined method `call' for nil:NilClass

So perhaps we should have at least some primitive error handling as well.

no_method_error = ->(meth) do
          raise NoMethodError, "undefined method `#{meth}' for person"
        end

        ->(meth, *args) do
          if methods[meth]
            methods[meth].call(*args)
          else
            no_method_error.call(meth)
          end
        end

        robert.call(:gracefully_handle_errors)
         => NoMethodError: undefined method `gracefully_handle_errors' for person

One could always expand upon things from here, doing things such as using #method_missing for more familiar invocation, etc.

Not bad!

While we still don’t have inheritance, or a more formal separation of Classes vs Instances, I’m sure you’re beginning to see that in matters of Objects vs Functions vs Data, there may be more in common than otherwise. Also, had you not already realized, Ruby owes quite a bit more to Functional Programming than is immediately obvious, and all this without delving very far into the similarities at all.

Cheers!

Full Example Code:

Person = ->(name=nil, age=nil) do
      methods = {
          get_name: -> { name },
          set_name: ->(new_name) { name = new_name },
          get_age: -> { age },
          had_birthday: -> { age += 1 }
      }

      no_method_error = ->(meth) do
        raise NoMethodError, "undefined method `#{meth}' for person"
      end

      ->(meth, *args) do
        if methods[meth]
          methods[meth].call(*args)
        else
          no_method_error.call(meth)
        end
      end
    end

    def Person.new(name, age)
      call(name, age)
    end


blog comments powered by Disqus