While I was watching Solving Problems the Clojure Way, I got an impression that there’s less friction between imperative, object-oriented and functional code than we might think. Please hear me out – and I’d love to hear if this strikes the minds of other Clojurians.
- Imperative: do stuff
- Object-oriented: organize your StuffDoers so that you can run one StuffDoers without using the main function (dependency injection) (Shout-out to Integrant’s
integrant.core/init-keyability to init a single component)
- Functional: Decouple your functionality from your state. This gives you strong guarantees about the behavior of code you use – when you’re passed in data, that data can’t change, and you don’t have to re-check whether it has changed all the time, or send messages because it has changed.
Imperative, OO and FP lie on a continuum of optimization. If you’ve got a small problem, it’s simplest to write a small, imperative script that does the job. When the responsibility of that script grows, you might suffer from too much coupling in your code. Adding a new command line parameter wasn’t simple, because you didn’t separate between modules. OO can help you separate into modules, where the modules are the objects.
That can get you very far, at least if you’re disciplined. But what about reloading your code? Not simple when everything carries state. Does some object copy over some stuff from other objects on instantiation? How do you update a part of your system? You might have to write a lot of manual functionality to enable that. FP to the rescue. FP nudges you towards being very explicit about change. In Haskell, that’s enforced in the type system. In Clojure, that’s strongly encouraged by the community guidelines and the standard library, which enables you to write code that’s explicit about change with ease (“simple made easy”).
Where does that leave us?
(a) Imperative, OO and FP is on a scale of tradeoffs towards gradually more decoupled code. Refactoring "towards the right" lets you build more general pieces of functionality. (b) It's important not to forget the lessons of imperative and object-oriented code when writing in a functional paradigm. Imperative: make sure your script /does what it should/. Consider the CLI. Consider how the functionality composes with other systems. Object-oriented: don't forget dependency injection. Don't forget that when you /do have to touch state/, being explicit abouts its creation is good. Take your state as a parameter where possible.
When I first touched Clojure, I really liked it, but couldn’t really point out why. There was something about the integration of pragmatic code and “tight” code. This was just after I had spent a lot of time on Haskell. In hindsight, I think I was annoyed by “not being able to integrate imperative and object-oriented lessons” in Haskell. The purity was great and felt great, but I felt dirty each time I touched IO (or the state monad, or unsafePerformIO and friends).
Some specific questions:
- Do you have similar expericens coming from OO?
- Do you have similar experiences from FP?
- Feel like there’s something to add to the discussion?