May 19 2010

Immutable Ruby?

Got bored and hacked around.  Found this sort of interesting…
class Object
  def self.new( *args, &blk )
    o = allocate
    o.instance_eval{initialize( *args, &blk )}
    o.freeze
    o
  end
end
Forcing objects to be frozen after initialization?  Almost sounds … functional.
  • Share/Bookmark

Feb 3 2009

Object Paradigm versus Procedural Paradigm

Why can’t we just be friends?

Clojure seems to be the buzz language of the moment, fighting for the esoteric language spotlight with Scala, Arc, Groovy, et al. Poor Erlang, it was sooooo 2008.

Clojure is a bit of a conundrum. Sitting upon the JVM, the very organs that the ‘everything is an object’ Java rests upon, Clojure is not object oriented. Except when it is, because it talks to Java libraries and what-not. While Java left a horrible taste in my mouth, I fully enjoy Ruby’s ‘everything is an object’ approach. Want to create a method that allows me to change the number 2 into a date offset from 4004 B.C.? Simply crack open that Fixnum class and add a new method. Now we can call 2.to_date and it works. Truly object oriented.

But does this always make sense? Not quite. This is the odd balance you can sometimes find in C++ libraries. This struck me while I was playing in Matlab. You see, the original purpose of an object was to encapsulate state and behavior. A dog barks. A matrix, on the other hand, does not add. Matrices are added. How are we supposed to capture this? It isn’t a behavior of the matrix itself, but it does use the state of the matrix. Matrix addition is a more … platonic concept. Nevertheless, how many times have we seen m.dot_product(m2)? So then I began to question: “Does 2 convert itself to date? Does that truly represent a behavior of 2? Or any Fixnum in general?” Not quite. So, in our Java ‘everything is an object’ world, should we have a ‘converter’ class? Does that make any sense? When was the last time you picked up your trusty ‘converter’ and used it? I’ll tell you when: never. Rather, you just did it in your head. Or on paper. Either way, it was just a calculation — there was no behavior that belonged to any object. It just … was.

Yet without objects, we can run into a brick wall. 1+1=2. [1 2]+[2 3]=[3 5]. Humans recognize which definition of + to use, but our compiler won’t. Well, unless we decide to give up dynamic typing and go fully static. Then we can. Otherwise, we get all sort of namespace collisions. Do we use the fixnum + or the matrix +? If we go static, what sort of losses do we take? Cool things like Ruby’s ActiveRecord library all of a sudden doesn’t seem to work. Poor method_missing just won’t work when we don’t know what form the arguments will arrive in.

So what can we do? C++ does it through some ‘exceptional’ cases, allowing you to define operators in the general namespace (or something like that; I don’t really know the inner specifics … I have better things to do than read the standard). So it allows us to do Matrix + Matrix instead of Matrix.add(Matrix). Nice. But what if we have a dynamic language? Well, then we might be in some trouble: we can’t tell which method to use.

Or, better, what if we have a hybrid language? Using meta-data, we can determine the type of arguments, and then map them to the appropriate function. Now if we simply tell the interpreter/compiler which method corresponds to which types of arguments, we are golden. Except for that whole ‘more work at run-time’ thing. Unfortunately, the only way to give the compiler any sort of predictive ability is to make the language static.

So now I can define a class for a dog, and implement a method ‘bark’ that truly defines the behavior of a dog. On the other hand, we can now define an (infix) + operator in the ‘platonic’ namespace that allows us to define the behavior of adding two matrices. That makes a lot more sense logically.

  • Share/Bookmark