Aspects of Beauty: Proportion, Integrity, Clarity, and Monkey Patching?

Posted by Rick DeNatale Mon, 20 Aug 2007 18:25:00 GMT

Besides being a master werewolf, Marcel Molina Jr. gives great presentations!

In his keynote presentation on the second day of the Ruby Hoedown, Marcel talked about “What Makes Code Beautiful”, click on the link for the confreaks video of this session.

The talk started with an exploration of the classical Philosophy of Beauty, from Plato to Descartes. Marcel summarized this by proposing that beauty lies in the balance between three aspects which, at times, either strengthen or oppose each other:

Proportion
The property that components have the appropriate(relative) size/weight.
Integrity
I would summarize this as “fitness of purpose.” Marcel’s anti-example was a hammer made out of glass. Although it might be beautifully constructed, and a joy to the eye, it would be unlikely to serve its intended purpose, and thus would fall short on integrity
Clarity
The property of being easily grasped as to meaning and function.

Beauty and Ruby

About 16 minutes into the talk, Marcel started talking about this view of beauty in the context of Ruby code. He gave an example of some really “clever” code to convert strings to an appropriate instance of a Ruby class, for example “true” would me converted to true, “false” to false, and strings representing integer or time values to Integers or Times, respectively.

The code in question, implemented a kind of functional language pattern match against the string. Marcel suggested that he might have been into studying Haskell at the time he wrote this code. He used a generator to produce an enumerable collection of patterns to try, and did some “nice” tricks to allow the result of a pattern match to sometimes be solely the value he wanted, and sometimes to be an array with the value as the second element, to handle the special case where the desired value was the literal false. If it sounds complicated, it is, I’ve placed the code at the end of this article. Some of us in the audience, “smelled” this code right away.

He then critiqued this solution. Although he had originally considered it “beautiful” since it was “elegant” and “sophisticated” he came to smell it too.

A Fresh Design

Here’s how the code ultimately was written:

class CoercibleString < String
  def coerce
    case self
    when 'true':          true
    when 'false':         false
    when /^\d+$/:         Integer(self)
    when datetime_format: Time.parse(self)
    else
      self
    end
  end
end

Once this much simpler design is unveiled the original “sophisticated”, and “elegant” design looks anything but.

Measuring against Proportion, Integrity, and Clarity

Proportion
The original is a total failure, it’s much too long compared to the final code.
Integrity
Again the original loses on this aspect. The use of the generator, particularly the early continuation based implementation, causes very slow performance, and leaks memory. Marcel stated that the simpler version is an order of magnitude faster.
Clarity
Do I really have to explore this?

Marcel had pointed out when describing the original design that one of it’s “cool features” was extensibility. Adding a new coercion just required adding another call to try in the Generator.new block.

In contrast adding a new coercion to the better design just requires adding a when leg to the case statement.

The Questionable Beauty of Making Subclasses of Core Classes

While I loved the talk and agree with 99 and 44/100%, I’m just a bit troubled by the introduction of the CoercibleString class. I think that it falls down on proportion at least.

It seems to me that there’s some missing code here. How do you actually coerce a string. This seems to strongly imply a usage like this:

class PayloadProcessor

  def process
    # code which extracts a string to be coerced

    #coerce the string referenced by the variable value_str
    value = CoercibleString.new(value_str).coerce

    #further processing
  end
end

An alternative, and it seems to me to be a better one, although I’m convinceable otherwise, would be to just make that method part of the class requiring the conversion, either directly, or through a module:

class PayloadProcessor

  def process
    # code which extracts a string to be coerced

    #coerce the string referenced by the variable value_str
    value = coerce(value_str)

    #further processing
  end

  def coerce(str)
    case str
    when 'true':          true
    when 'false':         false
    when /^\d+$/:         Integer(str)
    when datetime_format: Time.parse(str)
    else
      str
    end
  end

end

Now some might argue that the ‘functional’ looking coerce method which takes the string as an argument rather than the receiver seems somehow less ‘object oriented’, but I find this unconvincing.

If CoercibleString is a class we need code to create it from a string, something like:

  class CoercibleString < String
    # Create a new coercible string
    # Note that since the actual value of
    # Ruby strings are not held by an instance variable
    # we need to alter the internal representation
    def initialize(source_str)
      self << source_str
    end
  end

I had a brief conversation with Marcel about whether or not subclassing string really seemed appropriate, but it lasted all of about a minute. There’s a bit of supposition here on my part, so apologies to Marcel if I misunderstood the exchange. He indicated that he would probably advocate defining a method called CoercibleString, in parallel with Kernel#Integer and its ilk.

module Kernel
  def CoercibleString(str)
     CoercibleString.new(str)
  end
end

But this syntactic sugar, just seems to be tilting the balance towards a less proportional design.

Conclusion

Building new classes is often a good idea, but not always. I’m not totally convinced that coerce(str) is more beautiful than CoercibleString.new(str).coerce, or CoercibleString(str).coerce, but my sense of esthetics tilts me that way.

Comments?

A “Smelly” Way to Coerce Strings

Here’s Marcel’s original code:

class CoercibleString < String
  attr_accessor generator

  def coerce
    attempt = nil
    break unless {attempt = coercions.next).nil? while coercions.next?
    attempt.nil? ? self : attempt
  end

  private
    def coercions
      Generator.new do | self.generator |
        try { self == 'true'             }
        try { [self == 'false', false ]  }
        try { Integer(self)              }
        try { Date.parse(self)           }
      end
    end

    def try
        attempt, desired = yield
        generator.yield(desired.nil? ? attempt : desired) if attempt
    rescue ArgumentError
        generator.yield nil
    end
end

Posted in ,  | Tags , ,  | 1 comment | no trackbacks

Comments

  1. Ben said about 4 hours later:

    I thought Marcel’s keynote was extremely thought-provoking, but ultimately misguided – beauty is just too vague a concept to guide our coding practices, really, and I found the attempt to define criteria for it lacking. I do think that the question of what we should aim at when we code is important, though, and have been thinking about it publicly in a series of posts at my blog. Virtue > beauty!

Trackbacks

Use the following link to trackback from your own site:
http://talklikeaduck.denhaven2.com/articles/trackback/456

Comments are disabled