September 21, 2015 by Daniel P. Clark

Introspection by Design in Ruby

If I have one pet peeve in Ruby it’s knowing state at a certain point in the program’s process.  Sure they make plugins for advanced diagnosis of this.  And when you start out learning to program it’s common practice to use puts everywhere to see what’s going on.  But this causes a lot of back and forth in the development process which consumes your time.  When your project gets huge it’s a real pain to have to figure out where to dig in to inspect.  And when you get into multi-threading then you’re in for a heavier pain to deal with.

I don’t like either the plugin approach or the printing approach.  When designing your code base I believe it’s best to include introspection in it by design.  The way I propose to do it is via messaging.  In the returned object include your messages.  In Rails; ActiveRecord does this.  When you persist a record to the database the returned object has an errors method you can check to see if anything went wrong.

One pain point people will most likely come across in Rails is testing devise/user state within a controller.  Sure there are ways and howtos… but they are more difficult than they need to be.  What I propose will make all the headaches go away.

Return [Object, Messages]

This is probably the simplest way to implement you own introspection.  Have the item you want to return be the first item in a list, the second item in the list will be Hash where you put everything you want, or may want, to know about in to be used at any point later.

def add(a, b)
  begin
    [a+b, {status: :success, error: nil}]
  rescue => e
    [nil, {status: :failure, error: e}]
  end
end

There are pros and cons to any approach you may use.  I like this approach as it delegates the responsibility of how the error is handled to the receiver who can then either do something about it, or ignore it.  The method itself shouldn’t care about anything beyond what it was designed for.  Anything beyond that is a bigger picture item and should be dealt with outside the method, like in a test case, if need be.

Return Object with Message method(s)

class ReturnObject
  def initialize(out = nil, messages = {})
    @out = out
    messages.each do |key, value|
      define_singleton_method(key.to_sym) { value }
    end
  end

  def [](key = nil)
    !!key ? send(key) : @out
  end

  def out
    @out
  end
end

You can have fun implementing your own custom messages here.  This takes the message Hash you create and defines a method for each message that can be called on the ReturnObject.  Even better; defining the Hash like method :[ ] like this allows you to access everything this way.  If you provide a String or Symbol to this it will call a method by that name to return the message… if it doesn’t exist you will get a NoMethodError.  If you leave it empty it will return the original object you wanted to return in the first place.

Rewriting the add method above to use this ReturnObject looks like this:

def add(a, b)
  ReturnObject.new *begin
    [a+b, {status: :success, error: nil}]
  rescue => e
    [nil, {status: :failure, error: e}]
  end
end

Since the begin block will always return an Array of two items we we use the splat operator to splat them into ReturnObject as attributes: ReturnObject.new *begin … end

When used the output will look like this:

a = add(1,1)
# => #<ReturnObject:0x00000001974270 @out=2> 
a.out
# => 2 
a[]
# => 2 
a.status
# => :success 
b = add(1,"1")
# => #<ReturnObject:0x000000016f37f8 @out=nil> 
b.out
# => nil 
b[]
# => nil 
b[:status]
# => :failure 
b.error
# => #<TypeError: String can't be coerced into Fixnum>

Solutions, Solutions, Solutions

With this kind of returned object you have unlimited possibilities for introspection.  Anything you want to look at you can add into the message Hash and it becomes super easy to check and verify in your testing environment.  And with Rails you can add any local state that you need to check, such as current_user, into your messages to verify that things are appearing as you expect them to be.

Thoughts on Access to Object Ancestry

Now another thing I’ve wanted included in Ruby is an ancestry of the call chain.  There are many reasons why this would be bad.  It opens up the possibility of modification of the caller by the callee… think of it in simple terms as possible code cannibalization.  It’s like meta-programming the parents DNA after the child’s been born and leave the possibility for crazy behavior.  But, all the code apocalypse aside, it would be a very powerful tool to wield.  Also keep in mind it’s probably an anti-pattern as true Object Oriented Programming is messaging forward… the Tell Don’t Ask pattern.  Having ancestry makes the system more of a state machine which is asking for trouble.

But even so, “if you wanted to do it”, you can simply have every object in your Ruby program receive a parameter from it’s parent with it’s self.  Better yet have it be a “stack” of self’s from each caller parent.  You can design it with a limit, say an Array with a max length of 5 and use a FILO system.  Push self onto the end of the stack and pass it to the methods called.  This can easily be included in the messaging system via an :ancestors or :callers method lookup.

My want to have access to the caller scope/methods goes back to this S.O. answer I gave: http://stackoverflow.com/a/26190259/1500195 .  A newbie was looking at a way to define a meta-programmed Playlist where it’s instance variable is the name of the playlist and peak into the global scope for the name the variable contained.  It was a fun challenge.

But after taking the time to write my thoughts on it out, it’s definitely something you shouldn’t generally do.  Also the references will prevent garbage collection where it would have happened.

Benefits… Benefits Everywhere!

Messaging is true Object Oriented Programming as it’s inventor Alan Kay intended it to be.  Designing a system to include messaging in the objects and follow through in a Tell Don’t Ask pattern permits a clean system, allows for less type checking (anti-pattern), less code required (methods don’t have to care about success … functional languages, like Erlang, use this as a rule), available introspection, and permits adequate testing. It’s a win, win, win!

Summary

I haven’t practiced true OOP much with messaging as a core design.  But the pain points I experience dealing with introspection in Ruby is driving me towards more functional design patterns and messaging.  I’ve long wanted the benefits that this form of messaging permits.  The design of ReturnObject used above is most likely necessary as; even though you can define singleton methods on String variables, you can’t on Fixnum variables.  So a wrapper object is required.

I hope this post and been very insightful and informative for you!  If you have any ideas on this topic I’d love to hear about it!  Please feel free to comment, share, subscribe to my RSS Feed, and follow me on twitter @6ftdan!

God Bless!
-Daniel P. Clark

Image by island home via the Creative Commons Attribution-NonCommercial 2.0 Generic License

Thinking out loud: Perhaps implementing a secondary method describing which refinement is currently being used (like def using_refinement; “#1 with foobar”; end) may solve the introspection issues detailed in this post: Ruby Refinements – Not quite ready for production

 

#code#design#design pattern#introspection#object oriented programming#oop#pattern#ruby#tda#tell dont ask

Comments

  1. kingguy
    September 22, 2015 - 9:26 pm

    nice post!

  2. Kris Leech
    September 28, 2015 - 4:34 am

    Did you consider ReturnObject as a SimpleDelegator, so it can be used as normal but has additional methods for the messages?

    • Daniel P. Clark
      October 2, 2015 - 4:29 am

      No. I haven’t used delegators in practice. Looking at SimpleDelegator I see the similarity in that it wraps the object. But I believe ReturnObject here is much more elegant as you don’t have to manually define methods, you can just pass a key->value hash. Less code to write leaving a nicer code base.

      Also the concepts change your perspective. The word delegator makes you think of passing off some work to something else. Messages simply keeps things more in perspective as we’re not passing off responsibility, but giving a message in tangent with the object. It’s practically the same thing, but conceptually perceived otherwise.

Leave a Reply

Your email address will not be published / Required fields are marked *