On functional, procedural and OO

The focus of procedural programming is to break down a programming task into a collection of variables, data structures, and subroutines, whereas in object-oriented programming it is to break down a programming task into objects that expose behavior (methods) and data (members or attributes) using interfaces. The most important distinction is that while procedural programming uses procedures to operate on data structures, object-oriented programming bundles the two together, so an “object”, which is an instance of a class, operates on its “own” data structure.

The principles of modularity and code reuse in practical functional languages are fundamentally the same as in procedural languages, since they both stem from structured programming. So for example:

  • Procedures correspond to functions. Both allow the reuse of the same code in various parts of the programs, and at various points of its execution.
  • By the same token, procedure calls correspond to function application.
  • Functions and their invocations are modularly separated from each other in the same manner, by the use of function arguments, return values and variable scopes.

The main difference between the styles is that functional programming languages remove or at least deemphasize the imperative elements of procedural programming. The feature set of functional languages is therefore designed to support writing programs as much as possible in terms of pure functions:

Many functional languages, however, are in fact impurely functional and offer imperative/procedural constructs that allow the programmer to write programs in procedural style, or in a combination of both styles. It is common for input/output code in functional languages to be written in a procedural style.

All from: Procedural programming - Wikipedia

The interesting bit for me are how in a way, functional programming is an evolution of procedural programming. And in an impure functional language like Clojure, it really is that Clojure is a procedural language with additional features (like closures, first class functions, hof, macros, etc.) and an emphasis on pure functions and immutable data.

And also how I feel people who favour procedural languages such as C, Pascal, Go, Rust?, etc. tend to not like OO, in a similar vein and with same criticism as those who prefer functional programming, mostly that bundling methods and data they operate over instead of having functions (or procedures) operate over one or more data structures is just limiting.

3 Likes

While providing a portable way to reason about code across VMs, Clojure also feels pretty close to home for a C programmer like myself. IMO restraint in creating unnecessary artifacts to hide data access is a common trait for otherwise very different languages like Rust and Clojure.

I actually find it the opposite. The more complex the data structures need to be, the more limiting I find FP. In fact, I’ve yet to see an explanation of why FP is great that didn’t end up giving an OOP solution at the end. I have a few real world projects I ported either from OOP to FP or from FP to OOP and it’s been the same experience either way. FP is great until the data gets complicated and then OOP wins easily. Multi-paradigm languages work best where you can use OOP for the data and FP for the algorithms.

It’s not that most people don’t like OO, they just don’t like Java. A lot of procedural and FP programmers can’t live without OO concepts like polymorphism (including generics) and interfaces (which are just pure base classes).

I was hoping for a comment from you :stuck_out_tongue_closed_eyes:

Actually, I agree with you. But I think it’s just a difference in taxonomy.

OO can be considered many things and so can FP, and so can procedural. So the truth is, I’m not sure what exactly you mean when you say:

I can see that I feel you use a broader definition, since you mentioned that you consider polymorphism an OO concept.

Actually just out of curiosity, do you put polymorphism as an overlapping concept? Which exist in both OO and FP, or exclusively as an OO concept? Thus when an FP language has polymorphic features you consider that to be bringing some OO to the language?

I’ve actually tried to look at the history of polymorphism recently. From what I could find, it seemed like parametric polymorphism (like generics) is an FP concept, kind of the bread and butter of typed FP in fact, and then subtyping polymorphism (inheritance) is a OO concept, kind of the bread and butter of OO, and what wasn’t so clear was ad-hoc polymorphism (Java interfaces, Clojure protocols), for that one it seems it might have kind of developed in tendem between both paradigms but in slightly different ways, with Self bringing up Traits in 1987 and Standard ML having TypeClasses in 1988. Since then, lots of OO and FP langs have had all three, so I see them as overlapping concepts nowadays.

Anyway, that’s a different rabbit hole.

Back on topic, can you be more specific, like maybe provide an example of what you were doing, what kind of data-structure need you needed, what you meant by first tried an all FP approach, and hen what you mean by turned out more like an OO one? I think only in the details can I actually see your point, because understanding where you categorize things as OO or FP versus where I do etc. it’s not very clear.

1 Like

The core tenet of OOP is to combine domain specific data structures with the functions that operate on that specific data. FP tends to preach 1 data structure with 100 functions that operate on it. However, I’ve never seen an FP project that does that. Every talk I’ve seen about FP, or article I’ve read on it, ends up giving an example where they create a problem specific data structure and then give a handful of functions that only work with that structure. That’s an object, just using a language that doesn’t have specific support for it.

The one project I’ve tried with the most languages and paradigms is the 2007 ICFP contest problem, https://save-endo.cs.uu.nl. The first part of it, anyway, which requires a rope like data structure. The easiest way to just finish the project is to find a rope library, or Haskell apparently has some built in data structure that works, but for me the whole point is to try to write a custom version specific to the problem. The OOP languages I’ve used have been much easier to do that with.

I used to run amateur volleyball tournaments and wrote software to help me do that. I came up with some different types of tournaments, which required more and more complex data structures to handle. For example, one of them was a version of blind draw tournaments where players sign up individually and are placed on different teams during the tournament. I originally wrote it using OOP, then got into my “screw OOP” phase and decided to rewrite it using only pure functions and all that fun stuff. It was awesome until I got to the more complex parts. Dealing with data structures that are nested 5+ layers deep just got to where it was a headache, so I switched the data back to objects.

For work, I wrote a video decoding server that was basically a front end to ffmpeg. It needed to keep in memory state for each process, including info about whatever video was being decoded. I originally wrote it using FP, but as it got more complex I switched it to OOP. It was the same thing where it was great until the data got nested too deep, little edge case requirements came in, or we’d need one-off changes for one of our customers. That was the project where I realized the fragile base class problem also applies to FP. If you make changes to a function that gets used all over your code base, there’s no telling how much damage it’ll cause.

1 Like

I think that’s where we often talk past each other, but I feel now this is actually an important aspect.

For me, OO is about a language that has an Object construct of some sort. And with that construct comes features, what can I do and can’t do with it, and how does the Object behave.

Where an Object is a container which combines variables and procedures over the contained variables.

And then object-oriented programming is a style where I model my domain using mostly (maybe even exclusively) this construct.

So when you say: “That’s an object, just using a language that doesn’t have specific support for it.” to me it is not an object, because there is no such construct for me to use, and similarly, for me to be constrained by.

Specifically, I’m not sure if calling a “Car” a data-structure is fair. The data-structure used in OO in my view is actually the Object. The Car is the domain I’m trying to model. And I might want to represent some information about a car, like how much gaz in the tank, how many doors, if windows are open or closed, etc. And I might want to allow the user of my application to modify this information, like change a window from closed to open.

The question becomes, how do I model that with a computer? Where would I put the information about the Car?

I could do it procedurally:

(def percent-full-of-tank 80)
(def door-count 2)
(def left-window-state :open)
(def right-window-state :closed)

(defn close-left-window []
  (def left-window-state :closed))

Or OO (can’t use Clojure here)

public class Car {
  private int percent-full-of-tank = 80;
  private int door-count = 2;
  private WindowState left-window-state = WindowState.open;
  private WindowState right-window-state = WindowState.closed;

  public close-left-window() {
    this.left-window-state = WindowsState.closed;
  }
}

Or FP:

(def car
  {:percent-full-of-tank 80
   :door-count 2)
   :left-window-state :open
   :right-window-state :closed})

(defn close-left-window [car]
  (assoc car :left-window-state :closed))

All three of these model the data for a Car and the operation over that data, in this case, closing the left window, but in my taxonomy, I do not call them all OOP. But I can see how in some other taxonomy, where OO was more a conceptual model it would be, though I almost cannot think of any other way to model this conceptually since it’s pretty much a requirement of the domain to have it modeled this way.

So when it comes to my definition of OO, my criticism of of it is specifically towards that concrete Object construct, I find it is too limiting and not flexible enough as a basic building block.

At this point, since I’m referring to a specific construct, it also helps to talk within the frame of a particular language, though in my experience, all languages I’ve used that try to have an Object construct, I still find it often ends up with one limitation or annoyance or another.

For example, a typical annoyance is that I can’t add a method to an existing Object. Some OO languages do let you do that though. But here’s the kicker in my experience, you always end up encountering a situation where you can’t do X, because the provided Object construct doesn’t have a way to do it. And now you need to wait for the next version of the language to add some new feature that would allow you a way to do it.

Where as I feel with good old data-structures, variables, procedures and functions, I don’t encounter these limits, like they are basic enough as building blocks that they can compose or be used in a way that I can achieve all scenarios I’m faced with. Where Objects I feel that wasn’t the case in my experience. Especially in Java.

1 Like

Exactly. OOP took that pattern, used it as a base and ran with it, adding things like inheritance, encapsulation, etc. You don’t need to use an OO language to use objects, it just makes it easier. People write OOP code in C all the time. The Wayland project is a good example. You have to do the inheritance stuff and whatnot by hand, but it’s possible.

That’s only true for static languages like Java and C++ where everything needs to be known at compile time. All dynamic languages I know of allow it. JavaScript, Ruby, Python, PHP, Common Lisp. They all let you do it. It’s the same with trying to add a function to C at runtime. Can’t do it. The function has to be compiled first.

But that’s all objects are, they’re data structures and functions written in a different way. The paradigm, itself, doesn’t limit anything that those can do.

And there it is. :slight_smile: Like I said earlier, I don’t think people are actually opposed to OO, in fact they use it all the time. They just have PTSD from Java.

1 Like

I agree with all that. I actually do like OO, find it quite interesting. And I also like Java, its just that I prefer Clojure. Kind of like flying, I’m okay flying economy, I can even enjoy it, but I’d rather fly first class if I can :stuck_out_tongue_closed_eyes:

They’re not though. They’re an interface provided to you, the programmer, to use. And that’s my issue. I can implement Objects myself with data-structures and functions, and when I need too, I can give them exactly the features I need for the given problem I’m facing. In my world view, an Object is a different kind of data-structure, like you have Maps, Lists, Sets, and you have Objects. The latter is not always the most appropriate for my task. So why does my language only provide me with Object? Or forces me to have one just so I can use any of the other ones?

That’s a good point. And I think I need to think more about this. Because I have some similar issues with Haskell. There are certain things you can’t do, because the compiler couldn’t type check it, and that bothers me. Why can’t I model things the way I want? That’s fundamentally my most valued quality of any programming language. Can I do everything I want to do with it, and how straightforward is it for me to do so. To date, Clojure is the language that I feel lets me do most of what I want in the simplest most straightforward way.

Yes, and that’s often where my issue with it lies exactly. I don’t like where they ran with it. I would want it to be different, and when I do, the choices the language made become friction to me. Things end up convoluted, overly verbose, or hard to reason about.

P.S.: Apart from that, I do value immutability and find pure functions easier to work with. But that seems orthogonal to what we’re discussing, since you could make use of immutable Objects and get similar benefits, even if I don’t know of any OO language that make that the default.

I don’t know any language where Maps, Lists, and Sets aren’t Objects. It’s language dependent, but Object is usually a universal base class so it can be used as an “anything” type, like void* in C.

If you’re looking for a “do whatever the hell you want” language, ES6 JavaScript is one of the best out there. It’s dynamic to a fault. Any program can be written using only 6 characters, that’s how nuts it is. It not only lets you shoot yourself in the foot, it hands you a bat shit crazy gun that makes it more likely to happen. Since it has a JIT, its performance is on par with languages like Clojure, Go, and Common Lisp.

I’m indifferent to immutability, I find it gets in the way as much as it helps, but pure functions are really nice to work with. I don’t know any OO language that has immutability by default, either. I think most of them have libraries for it now, but I don’t think they get much use. The only one I know that’s gotten any sort of traction is immutable.js because it helps with React’s performance.

Haskell? Go? Rust? Elisp? C? Those are just on the top of my head.

Not a huge fan of JavaScript, though I was a big fan of other ECMA implementations like ActionScript 3.

The issue with JavaScript is that it makes modeling things my way more convoluted then I’d like, it doesn’t support macros (so I can’t extend the semantics of the syntax how I’d like), I don’t like the syntax (personal preference), and it lacks good module support which I find a must have. Also, it’s a bit too unsafe for my liking, I’m not a fan of type coercions, I like a language that does what I say, not try to outsmart me.

Also, I’ve yet to find a JS standard lib I like, Node is alright, but I’d want something better. I also like being able to manage threads myself so I can model concurrency how I want.

Which is great, cause I can use ClojureScript instead, though REPLs are made harder, and I do lots of backend and again Node is… not sure about it yet compared to JVM, so Clojure it is.

That seems paradoxal, a pure function is immutable, at least over its blackbox of input/output, and that’s where immutable data-structures really help, in making it easy and less error prone to implement pure functions.

I meant languages with OO built into them. C doesn’t have Maps, Lists, or Sets, either. If you’re rolling your own objects, you don’t need that universal base class. Languages with OO built in usually have it as a convenience.

As an example, take a function that searches over a collection. Inside the function, you could use an iterator to walk the collection, but the iterator is forced to be immutable so you have to jump through some hoops to update it. I agree that immutability can help with writing pure functions, but it can also get in the way at times. I’ve also never had a problem writing pure functions without immutable data, so I’m fine with or without it.

If we’re going to pretend JavaScript has a standard library, I think we can all agree Glib is C’s standard library :stuck_out_tongue_closed_eyes:

Wouldn’t it mean a language is not an OO language if the core collections weren’t exposed via Objects?

True, and I think this is a place where lots of people struggle, that is the intersection between immutability and performance and execution on a stricly imperative machine.

My feeling is most FP languages have some good solution to hide this conflict from you. Transducers for example I find solve this really elegantly. Lazyness has its performance issues I’ll confess. Immutable bindings like loop/recur and the macros build on it I also find solve this pretty well.

But, ya, local mutation while I’ve definitely had quite a few hard to debug bugs that took me few days to solve in my career I’d say overall is not as much of a crime as breaking purity of functions. That’s also why I’m totally fine with impure FP languages. There’s a time and a place for each, because hardware is imperative.

Yeah, it’s a bit of a stretch calling it a standard library. It has hashmaps, that’s pretty much it. Even arrays aren’t really arrays, they’re hashmaps.

Not necessarily. I consider a language an OO language as long as it has even minimal support for objects, such as Javascript. Here’s why that matters. If we go back to the car example,

(defn close-left-window [car] ... )

What happens if something other than a car is passed as the first parameter? Do all the functions that take a car have to validate the structure? Or do you just deal with the strange bugs that pop up at run time when somebody accidentally passes the wrong thing in?

Compare that with using an actual class,

class Car {
    close_left_window() { ... }
}

The compiler now guarantees that the data passed to that function must be a Car. It’s not possible to pass it anything else. What if the structure of the Car changes? For example, the left-window-state changes to an object instead of a token? How do you guarantee that everywhere the function is called correctly updated the data structure that’s passed in? With classes, it’s all combined in the same place.

Hum… I’m not sure I’m following you. You don’t need Objects for what you’re talking about, unless I misunderstood you. You just need some static type checker and a way to define user level types.

In Haskell for example:

data Car = Car {percent-full-of-tank :: Int
               , door-count :: Int
               , left-window-state :: WindowState
               , right-window-state :: WindowState}

closeLeftWindow :: Car -> Car
closeLeftWindow car = car { left-window-state = Closed }

So we defined a Car datatype, and there’s a function called closeLeftWindow that takes an argument of type Car and returns a new Car with the left-window-state field set to Closed.

But there’s no Objects involved, and the function closeLeftWindow is not contained together with the Car structure, this is only compiler information.

So all we have here is a function and some compiler type declarations and we can statically type check. No need for OO.

1 Like

Yeah, “need” isn’t the word I’d use there. There are a couple benefits I find with classes, though. One is that you get a similar level of type safety to what you showed even with dynamic languages. Obviously, it won’t be 100% the same because the whole point of dynamic languages is you can muck with stuff at run time, but I find what type safety it brings to be more and more beneficial the more complex the code gets.

The other thing, which is what I was referring to with things being combined, has to do with where in the code the functions are defined. With the Haskell example, where in the code base can the functions be defined? Pretty much anywhere. With classes, they have to be defined as part of the class (for most languages, there are some where that’s not the case.) That gives kind of a forced modularization that, again, I find more and more beneficial the more complex the code gets.

Can you get all the same benefits by combining different approaches? Sure. But you get them all with classes, along with inheritance, polymorphism, and possibly other benefits depending on what the language provides.

You mean because you would call the function like: car.closeLeftWindow() so if you accidentally did motorcycle.closeLeftWindow() you’d get a no such method run-time error?

That’s a fair preference. Though I’ll give an example of where it can be a bit strange. For example, in Domain Driven Design, you often realize that a lot of useful domain actions are not at the level of a single Object, and must involve multiple objects together, either as an aggregate, where you might have an object composed of other objects, or as a transaction, which would be a method of multiple objects.

So what happens is that all the methods involving the object arn’t on the object itself, some of them are on other objects that use composition over the object, or are in other methods as additional arguments where they take an argument of that object’s class, or they’re up the hierarchy of inheritance on ancestor objects.

When that happens, you lose the “all relevant methods are organized together on the Class” property.

That’s where most languages introduce modules or packages. But my point being that, in complicated programs you’ll most likely end up in a scenario where methods over that object live not just in the Class but elsewhere as well, so you kind of end up with the same problem then in an FP lang, and a similar solution to it is using some form of module or even just grep through the code (or if you have static type the types can track down all usage in the code).

Hum, I don’t think everyone would agree here. But it really depends what “benefits” you want to include or not.

1 Like

Or any number of base class files or interface files, which may actually be in third-party libraries… So they can be “pretty much anywhere”, just like you criticized Haskell for.

Classes are not a benefit. They inherently lead to less reusable code because people write classes tied to their domain instead of code that is generic to multiple implementations. Functional languages favor small, generic functions – that are decoupled from their implementation and therefore more reusable.

I’ve written a lot of production OOP code over the last three decades (I started with C++ in January '92, picked up Java in '97, and have worked with a number of both dynamic and statically typed OOP languages since then – three OOP languages in production since C++ and Java, many more as learning exercises).

I’ve also written a lot of FP code, since I started doing that back in the early '80s, and have now been doing Clojure in production now for a decade.

OOP was an interesting idea but it became dogma and an entire industry built up around it. It’s a good, solid way to organize code but it doesn’t lead to the much-promised reuse we were expecting and it’s very, very hard to write really good abstractions in because it inherently couples functions and data together. It’s why we’re seeing a lot of OOP languages adding more and more functional features, adding support for immutable “data” objects (value objects), adding more generic data access, etc. Because that functional world brings more code reuse and better abstractions.

Polymorphism is great, but you don’t need OOP for it. Haskell’s type classes and higher-kinded types are way beyond what OOP languages offer. Clojure’s protocols and multimethods are also more powerful than nearly all OOP languages.

Inheritance for reuse is a discredited idea in the OOP world – that is why there’s been such a hard push toward separating interface and implementation and away from inheriting and extending implementations. Type hierarchies and mixins are what’s left and you don’t need OOP for those.

Encapsulation is required if your data is mutable but, again, the push toward value objects is a move away from both mutability and, essentially, from encapsulation as well. Encapsulation exists because, without it, OOP would be a leaky abstraction and often unsafe.

The reason OOP has remained so popular in the “enterprise” is that it imposes an (artificial) structure and it constrains developers – plus, there’s a huge “cult mindset” around it at this point. It lets you hire mediocre (or worse) programmers and they can churn out 1,000’s of lines of cookie-cutter code that gets bolted together into giant systems that no individual understands. Inertia keeps the whole business going.

It’s very clear that you’re defending OOP based on personal preference at this point: you find it more natural, easier to understand, more comfortable from an organization point-of-view.

9 Likes

Yeah, I hadn’t even thought of criticizing @Richard_Heller’s comments on the grounds of “orchestration” but you’re absolutely right! Any operation that involves more than one class type cannot possibly be “defined as part of the [one] class”!

No, they can’t. The base class stuff is in the bass class file. The derived class stuff is in the derived class file. The base class can’t be split across multiple files. Neither can the derived class. They may use stuff from other files, but the class specific stuff is self contained.

That’s simply a false statement. I’ve also never seen an FP code base that has less domain specific code than the equivalent OOP code base.

Yes, it is.

Again, simply not true.

The value objects aren’t immutable. They’re introduced to reduce boiler plate code.

I’d say the same about your defense of FP. :slight_smile:

That’s not true, though. The composite object has methods that operate across all component objects, but the interaction with the individual objects is still all done with methods on those objects. The composite object calls getters/setters/whatever on the objects its composed of.

No, you don’t. All relevant methods for each individual class are still on those classes. It’s like Legos. The bricks are still the same even if they’ve been put in the form of a house.

1 Like

I haven’t written a single line of Java in my career, but I’ve experienced C++ codebases that could only be called “OOP-happy”. Inheritance hierarchies are the main culprit when it comes to inefficient collaboration within a team (the amount of indirection becomes staggering after a while). For me, there’s no surprise in seeing new languages like Rust and Go forgo subtyping completely. If the problem domain exhibits concurrency, I found that encapsulation becomes something between a nuisance and a major hindrance. I think Bertrand Meyer was on the right track with SCOOP for the Eiffel language, but I can’t imagine Eiffel outgrowing its niche.

2 Likes