Object Oriented vs. Functional Programming
A Little Background
After developing for over ten years in Java, PHP, and\or Rails, I was pretty comfortable with best practices for creating web applications. What I didn’t realize was that those best practices were for Object-Oriented web applications, not necessarily web applications per se.
When I tried to create my first web application in XQuery, I ran into all sorts of problems. I couldn’t figure out how to create table rows in a loop, or append values to a string, or do two statements in one call.
In hindsight, I have realized that what I was trying to do was use an OO approach in a functional programming language. There were fundamental differences between the two approaches, and not until I made the paradigm shift was I able to become proficient.
The phrase “paradigm shift” is used a lot in programming, and I suppose sometimes it is justified. But when I learned Rails and made a “paradigm shift” there, it was nothing compared to the real paradigm shift I had to make with XQuery. It actually helped me understand OO better, since I was able to contrast the two approached.
Object-Oriented Programming
In OO programming, everything is an object with two aspects: data and behavior. Objects have data (fields, members, attributes, etc.) which are either simple date types (int, String, boolean, etc.) or are objects themselves. You can get and set these values any time you can get a handle on the object, assuming the object implements a way for you to be able to do that (either directly or through accessor methods).
For example, an object in Java might look like this:
public class Person {
private String name;
public Person() {
}
public void setName(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
}
In this example, I can set the “name” attribute on a Person object whenever I want. Any other piece of code could, too. A particular instance of Person may or may not have a value for “name.” A particular instance of Person will remain in memory for as long as something keeps a reference to it, or until the application shuts down. I can have one part of my code pass a reference to this object to another part of my code. In fact, typically one part of code would set the “name,” pass the reference to of the object to another part of code which would then get the value of “name” off of that object.
In OO programming, the entire system is basically a huge state machine, where objects are created and attributes set, and then the objects are passed around or referenced by other code. The application keeps track of all the object in memory and program execution passes from one object to another, setting data on objects along the way. And programmers are all too familiar with what happens when program execution is attempted on an object that doesn’t actually exist: the Null Pointer Exception.
Objects are a combination of both data and logic. You could decide to create an object solely to contain data (like a Data Access Object), or you could decide to create an object that has no data, ie. attributes (like a utility class), but they are still objects and you still must define a class for them and instantiate them.
All sorts of best practices have evolved around creating maintainable, stable, and performant applications based on the characteristics of OO programs. However, in functional programs, the characteristics are different. So not only are there different best practices, the best practices in OO may be worst practices in functional programming.
Functional Programming
There are no objects in functional programming that have attributes and behavior (at least not in XQuery). Data and code are totally separate. Functions control program execution, and while they can receive data in, they themselves do not have fields or attributes. Functions operate on data and pass the data around by value. Once a function passes some data to another function, that’s it. The sending function cannot keep a reference to the data; it passed the data. The receiving function cannot even modify the data. It can, however, create a new copy with modifications, and send that to another function or return it back to the calling function.
Some of the advantages of Functional Programming are:
- Nothing can change your data. Once you get it, you’ve got your copy and it’s yours. Nothing can mysteriously change it while you are operating on it. Conversely, a function cannot affect data other than what it received (no side effect).
- It’s threadsafe. There are no concurrency issues since nothing can have a reference to the same data. There’s no way for one function to interfere with the data in another function. And there’s no way for code to have stale references.
- No Null Pointer Exceptions. NPEs happen when a piece of code knows the definition of an object (its class) but makes the mistake of trying to invoke methods on a non-existent instance of that class. In functional programming, there are no object instances of code. Function signatures are checked statically and then called, so there’s no chance of a code instance not existing, once those functions have been imported.
- Expressions can be compounded. This one might take a little effort to grasp at first. Since there are no instances of objects, and since all data is always passed by value from one function to another, then you can have once function call another function and so on without having to worry about instantiating objects or checking for null values. I can pass a string value to substring function to a concatenation function to a parsing function to a date constructor to a date formatter, etc. So my code can be made of compound expressions that elegantly build the logic I need, rather than having to write a new function (or object or method) that does what I want it to do.
It’s well known in the Java world that stateless applications will perform better than stateful ones, everything else being equal. Enterprise Java Beans and web apps are often designed so that they do not need to maintain state information across requests if possible. In functional programming, there’s really no way to maintain state in memory (although you could write it to the database). Since there are no references to objects, there is a built-in performance advantage. Because of this, you can combine and compound several functions and still maintain high performance.
One thing that programmers new to functional programming have to keep in mind is that the only data you have to work with is that which is passed into the function, and the only way you can communicate anything from the function is in the return value. This forces you to think of exactly what your function should do since it can only return one result or sequence of results. You also cannot write functions that simply “do something” without returning a result. That would be violating the principle of “no side effects.” This is a mental shift that the programmer must make: from writing statements that do something, to writing expressions that return something. It might sound simple, but I think there are important fundamental differences in mindset and that’s what is necessary to make the paradigm shift.
Nice article. I like the way you compare and contrast OO vs. FP. What do you think of the concept of “Encapsulation” in FP? Should every XML structure have a function module to query update it? What if I change the XML structure. Would this insulate subscribers from changes in internal representations of my data?
What about interfaces and polymorphism? Can I call a the same draw function on different XML structures?
Not that I have all the answers here mind you. Just wondering what you thought about these topics.
Encapsulation is really protecting state from unwanted changes. In OOP you want to hide the state away and create specific accessors to it to modify it. In FP you can’t change state to begin with so encapsulation doesn’t offer much anymore.
In FP I don’t see much value in writing code whose specific purpose is to access data. Just access the data directly. If you change the XML structure then you change the queries. I don’t think that’s too bad. But the power in the expressiveness in XPath and XQuery is much more valuable than trying to insulate against future schema changes.
For example, I could create function to get me the first name off of a profile document. If the “name” element in the profile schema changes, all I have to do is change the function that accesses, (maybe it changes to “firstname” and “lastname”). The problem is that I’ve lost all expressiveness around the name element. All I can do it retrieve it. I can’t do very complex or elegant queries anymore because I’m not using XPath\XQuery but rather my function. I have reduced access to the name to be simply a get.
Interfaces make sense in OOP because it separates the object definition (the class or interface) from the particular instance of it. Data and logic are usually mixed together intentionally in OOP so that a “Person” object has data (like name, height) and logic (like run, sleep). The value is being able change implementation without changes the calling code. But the caller is still locked into the interface. I think there’s more value in having composable expressions rather than contracted, fixed interfaces. This is what closures and lamdas do, provide a way to assemble the functionality you need by combining functions together as needed on a per-call basis.
> If you change the XML structure then you change the queries. I don’t think that’s too bad.
I think that the answer is somewhat a matter of scale of systems. If you just have a few XQuery files to change you can quickly modify the XPath expressions when the requirements change. Especially if you remember everywhere you access the data.
But I have built systems that have hundreds of path references and when the logic changes it is really nice to only have to change a single XQuery function, not find the path expressions in 100s of files and then retest everything.
For example if you have books that all have ISBN numbers but then you add videos and e-books that have different id systems you can simply modify the “book:get-book-id($book)” function to use book-type logic to get the right id from the XML file. You create a simple interface for all subscribers to book data. This approach has saved me many hours of editing and it allows me to tell my customers that they can put ids anywhere they want and I can make a single change for them in a few minutes.
I do agree that not using data interface modules is fine for small systems that have a short life span and where the developer can quickly remember where the path expressions are.
But I think the concept of using XQuery modules as “interfaces” to wrap XML data and insulate changes from other systems is really central to resiliency and long-term maintainability of reusable XML applications. So the advice I give to most of my customers is to encourage them to provide an XQuery interface module for data collections and focus on the interfaces, not the short term representation in an XML file.
Well you gotta do what makes sense for you. But the more accessor methods you create, the more fixed you may be making your schema. If you use getters and setters, then you have to create a function to get any new elements you create.
But in your example, what do you do if you want to get all ids that start with “sed” and are only on books written in the past 5 years that have multiple authors? If you have a function that returns you the id, you can’t very well use the id in queries.