Friday, March 10, 2006

Abstraction in Software - Its not all OO (Part 3)

Modern day programmers tend to believe that software abstractions are synonymous to the OO paradigm - unless you are programming in a statically typed OO language, you miss out on the abstraction. With all the dynamic languages gaining popularity these days, functional programming paradigms of Closures and Continuations being discussed in various blogs and mailing lists and Martin Fowler bliki ing on DuckInterfaces and CollectionClosureMethod, things look set for the next revolution in the arena of programming languages.


Coming from the background of hard core imperative programming paradigms of C, C++ and Java, I have also been trying to catch up with this new wave of Ruby and Python. It was during this time that I got the pointer of "The Little Ruby, A Lot of Objects" from Roshan's blog (Thanks Roshan, for the enlightening experience !!). The "Little Ruby" book was like a breath of fresh air - the elements of programming style that it embraced to come up with the Model of Computation built around passing messages to objects, points to the hard reality that calls for a change in the way we teach software design/abstraction in schools. All the hallmarks of good abstraction design that I mentioned in my post Abstraction in Software - The Good, Bad and Ugly (Part 1), come out naturally once you are able to formulate your model of computation through ideas like "Classes are objects with a protocol to create other objects".


What data abstraction is to OO, control abstraction is to functional programming. Constructs like first class functions, higher level functions, closures & functional composition foster the elegance of designing beautiful abstractions in a completely non OO world. The programming language horizon is ready to embrace these sublime ideas as evident from the voices of Paul Graham, Martin Fowler and from the enthusiasm generated in the community by languages like Ruby and Python. Paul Graham has been writing about the reincarnation of Lisp in all the many features that have found way in Python - he mentions "The question is not whether Lisp..but rather 'when'".


Ruby Iterators: Catch me if you can !!

Modeling an iterator in an OO language like C++ and Java has been considered ad hoc by many purists and has been attributed to the lack of suitable control structures in the language. The state of the iterator needs to be stored somewhere - it cannot be the collection class itself, since that would prevent defining multiple concurrent iterators on the same collection. C++ provides a friend iterator class along with the collection class itself to solve this problem. Java also provides external helper objects based on its Iterator interface to store the iterator state. Languages like Ruby that support higher order functions and can define local functions which are closed over their free lexical variables offer much stronger and natural iteration capabilities for abstract collections without the need to define an iterator class for each such collection class.


Let us look at an example of Ruby iterator (adopted from Programming Ruby) :


class SongList
    def with_title(title)
        @songs.find {|song| title == song.name }
    end
end

In the above snippet, the method find is an iterator, which nicely encapsulates the block of code to be repeatedly executed over each element of the collection songs. The client does not have to invoke an explicit loop structure (as in C++ or Java) - the implementation of the iteration semantics is completely hidden within the control structure. The collection object songs responds to the message find and invokes the block of code (the Closure) for every element - this is what Brain Marick refers to as "In computation, simple rules combine to allow complex possibilities" in the "Little Ruby" book.


Let us look at another killer example of the beauty of control abstraction offered by Ruby iterators. This is shamelessly copied from Roshan.

def producer
    100.times{|i| yield i if i%5 == 0}
end

def consumer
    sum = 0
    producer {|v| sum = sum + v}
    print sum
end


The beauty of the above snippet is the separation of concerns between the producer and consumer through abstraction - this is only possible because the language offers built-in iteration construct times. The interaction between the producer and the consumer is done through the neat implementation of the yield statement, which allows the function producer to exit on every iteration with the return value i.


Before I end this blog entry, one piece of advice to all the new programmers (the victims of The Perils of Java Schools). The horizons of programming language support for software abstractions is changing - languages like Ruby, Python, Scala and Groovy are offering all the elegance that Lisp had delivered in the 1970s; next time when you find yourself modeling a solution to a computing problem, a lambda or a closure may be a better fit than your complex Java / C++ data abstraction.

No comments: