Reputation: 321
A friend is trying to convince me of the benefits of clojure and I am doing my best to understand the language. I have looked at a lot of web sites and skimmed through the "Clojure for the Brave and True" book, but I am still missing something.
My background is in object oriented programming and it is second nature to me that a program object represents a real-world object. I have read that clojure is not OO but I can't understand what clojure does instead.
Here is an example (much simplified). I am writing an app for planning income in retirement. It has a class for a Savings Account and a class for a Pension. A Savings Account has a balance and an interest rate. It has a Withdraw method that takes out a specified amount (passed parameter) in a year. The balance reduces by the amount withdrawn and increases by the interest amount each year. You can never withdraw more than the balance.
The Pension class has an annual income and an annual percentage increase. This too has a withdraw method but the amount withdrawn is always the annual income (increased by the percentage each year). i.e. It's calculated differently from the Savings Account.
In my OO program, both of these classes implement a protocol that defines the Withdraw method, so my code can process them in the same way without needed to take into account their differences.
What, in the clojure world, would represent the Savings Account and the Pension. i.e. What structures gather together the data for instances and the methods that operate on that data? In OO, the class defines the data and the methods. The instance is a unique occurence of the data. My code file that defines the class tells me everything about the object that class represents. How does clojure do this, or what does it do instead?
Upvotes: 3
Views: 494
Reputation: 21926
it is second nature to me that a program object represents a real-world object
Most object-oriented programmers I know don't program that way. When I program in OO languages I don't program that way. With the possible exception of gamedev, most objects in an object-oriented program have only the most tenuous of connections to business domain entities, for the same reason that most of my SQL tables tend to have only the most tenuous of connections to business domain entities.
Business people do not care about URIProcurerAbstractFactoryManagers or bridge tables, because they don't have a real-world analog.
I mention the above (and also the bit about SQL tables) because the point is that most of what we create in terms of software artifacts is necessarily but indirectly related to solving the problem. HTTP backends for completely unrelated things, say a bank and a national charity, will look very very similar (to the point where the file/directory/table names might be the best clue as to which one you're looking at). More of what we write is more related to the constraints of the type of problem than the specifics of the problem itself.
Clojure is a different way of modelling problems, in the same way relational tables are a different way of modelling problems.
You can try to treat clojure datastructures the same way you treat objects. You can try to treat relational tables the same way you treat objects. But it isn't going to work very well (c.f. the thorny and troubled history of ORM). You can try to treat all object-oriented program objects as real-world analogs, but that isn't going to get you very far either.
You have to learn a new way of thinking about problems, and I would go so far as to say you aren't going to be able to pick the best ones unless you're at least passingly familiar with most of them (so kudos to you for trying to learn a new one!).
Upvotes: 5
Reputation: 25873
First let me tell you that I totally understand your feelings, I had to go through that same situation. And let me tell you also that learning Clojure (functional programming) was totally worth it (of course this is a personal assessment).
Clojure does have OOP tools, for example you do have defrecord
and defprotocol
. But at its heart it is more functional than object-oriented, and you will only reap its real benefits if you start thinking functionally and immutability.
To write idiomatic Clojure, you need to switch your thinking from objects to functions. In a functional language, everything revolves around functions, not data (objects). Data is represented using trivial data structures, such as maps. You do not bind functions with their data like in OOP. In your case, instead of a class Savings Account
and a class Pension
, you simply need a data structure to represent this data, for example a map:
(def saving-account {:balance 0.0, :interest-rate 0.0})
(def pension {:annual-income 0.0, :annual-increase 0.0})
The Withdraw
method would simply be a function for each case, for example:
(defn withdraw-from-saving-account [sa amount] ( <return updated saving account map> ))
(defn withdraw-from-pension [p] ( <return updated pension map> ))
In Clojure you usually want to keep everything immutable, so the above functions will return a new updated map and do not modify any existent state in existing maps. This gets some time to get used to when coming from a heavy mutable state paradigm like Java's OOP. Idiomatic Clojure (as most functional programming languages) is heavily based on function composition (which implies short single task functions whenever possible) and very limited use of variables. If you have ever used a *nix shell, this is very similar with command piping/chaining.
Note that you can also simplify the above functions by declaring a single Withdraw
multimethod. And if you prefer you can also use datatypes to define the data more strictly and use protocols to implement an interface for these records, which is closer to what you do in Java. The main point you need to understand is that while Java revolves around classes, Clojure revolves around functions and immutable data, and the more pure a function is, the better.
Upvotes: 5
Reputation: 5877
@M0skit0 has a very good answer. I want to call out a specific mention. You asked:
In my OO program, both of these classes implement a protocol that defines the Withdraw method
You can have what your class methods without encapsulation. Look more closely at protocols. If your brain already thinks in OO then you should consider that clojure's approach is to let you use protocols but you don't have to. Thus, the term "A la carte Polymorphism". Look for some only resources by that term and you will find good discussions that many people would find confusing but you might find speaks to your brain.
Upvotes: 0