Norbert Wójtowicz

Panel: FP vs OOP

Andrzej Krzywda, Piotr Zolnierek, Norbert Wójtowicz, Tymon Tobolski, Przemysław Kowalczyk and Jan Filipowski

wroc_love.rb 2013

00:00:18.170 As for the introduction, what is the single most important killer feature of object-oriented programming (OOP) and functional programming (FP)? Okay, so I think that the best feature of object-oriented programming is the philosophy of objects and object-oriented ontology. For me, the absolutely killer thing about functional programming is that if it works once, it works always. There are no exceptions; it is just like math—it just works.
00:00:34.220 What if you turn the power off? Come again, what if you turn the power off? Does it still work? Yes, yes, of course. Okay, let's move on to more serious questions. Let's start with tests, specifically test-driven development. In pure functional programming, the result of a function depends only on its arguments, making it quite easy to test. You pass a data structure and you get a result, which you can assert against a value. In object-oriented programming, test cases typically involve quite a lot of state setup. Do you agree that it is easier to test pure functions than it is to test object methods?
00:01:24.079 Okay, so you mentioned that we often need two months to set up the state in order to test objects. I think that's a smelly situation if you have to set up everything in your test before you start testing. Even in object-oriented programming, it's bad code in my opinion. We're in similar situations regarding testing; however, you still have to set some state. You have to initialize your object and organize some data structures, right? Fair enough.
00:02:43.430 So, my point is that in functional languages, you always have the same output for the same input arguments. In object-oriented languages, sometimes you modify the encapsulated state, necessitating assertions about those changes and forcing you to perform isolation tests to determine if a certain method was called within the object or to showcase the encapsulated state. Therefore, it should come as no surprise to you that state changes happen when programming in OOP. There are ways to deal with these changes, and if you are concerned about state changes, I believe it indicates a design flaw in your objects. When comparing good OOP to bad FP, it is clear that FP has an edge.
00:03:42.260 Okay, let's move on. In functional programming, there are really just a couple of core components: data structures and functions. All you do is apply those data structures to your functions. In a sense, testing does not differ from writing regular problems. You simply provide your functions with different arguments, and that's all. Before we continue with the discussion, I would like to quote Martin Fowler: 'Everything object-oriented programming can do, functional programming can do better. The code is easier to write, runs faster, and uses less memory.'
00:04:35.000 It’s not a wrong argument; I mean, it’s Martin Fowler. You cannot argue with him. But last time I checked, about ten years ago, I read something that Uncle Bob wrote about OOP. He stated that a good program should be one without side effects. If a method doesn't have side effects, how do we classify it? It’s a function. I agree with Martin Fowler in that if you write proper code, you achieve the same benefits in OOP as you do in FP; it's just that you cannot mess it up that much in FP. By the way, think about it—everything is a function. Sometimes, however, side effects matter if your code doesn't change anything in the outside world.
00:05:58.000 A question for functional programming supporters: In FP, you can use dependency inversion to test your objects in isolation. How can you test your functions in isolation? How can you mock these lower-level functions, or do you not test them at all? First, it has already been established that if it works, it works. And that is indeed the case. Also, in functional programming, you do not write massive functions or methods; you create small, fine-grained functions. Does that mean you don’t practice test-driven development in FP? No, it does not. It means you can take any function and apply it where needed, and it will work.
00:06:44.360 Let me comment on that. When you are programming in functional programming and you utilize dependency injection, you must pass everything into a function. By design, every function uses dependency injection when needed. Another remarkable feature of functional programming is composition. You could speak to someone knowledgeable about composition for an entire night! But how do you pass this function in isolation in FP? You utilize polymorphism, right? So how can you choose whether to use your actual low-level function or a mock? You can pass a mock into the function, but that raises the question of how you select it.
00:08:03.620 Just because you are using a functional language doesn't mean you can easily mock functions. For instance, in closures, you can use the binding construct to overwrite global-level functions. If you define a function called 'foo' and another function called 'bar' uses 'foo,' you can mock 'foo' if necessary. Alternatively, you could inject dependencies directly into the function. You can invert dependencies just as easily in functional programming as in object-oriented programming.
00:08:43.150 Moreover, the dependencies in FP tend to be more decoupled than in OOP. In OOP, there is a coupling on function names. Even with duck typing, you remain coupled to the method name you are calling. In FP, when you program with functions, you are only coupled to the arguments that the function accepts.
00:09:14.320 As Ruby programmers, we can agree that high-level languages are more productive than low-level languages. High-level means it is closer to solving problems than being close to the metal. So how do you find it easier to think in terms of cooperating objects compared to functions processing data? First of all, I think the perspective of modeling the world as something you believe in is crucial. You can model the world through particles. Those who write functional code perceive the world with functions.
00:10:04.220 Conversely, I believe that our perspective from the object-oriented world is that we see everywhere objects that can possess state and behavior. The most interesting aspect for me is how you, in your model, perceive reality. If we assess this conference room, we see chairs and people sitting on those chairs. I don’t think that sitting happens to people; rather, people choose to sit on the chairs. Hence, I see objects in this environment—objects performing operations, exhibiting behaviors, and transforming states. For instance, they may drink beer and enjoy themselves.
00:11:25.660 Now, how does this work in the functional programming paradigm? The initial challenge is understanding composition versus inheritance. I believe that the world is better modeled by composition than by inheritance. Essentially, if you take an object-oriented model, you might define a class called 'Car' which has wheels, and then, entering the real world, you discover that every car varies considerably and that the amount of data structures in existence is dwarfed by the amount of behaviors these objects exhibit.
00:12:18.370 Functional programming encourages you to build many functions that operate on a single data structure. By contrast, in OOP, you likely possess several data structures with myriad methods operating on them. Essentially, if you are modeling a domain where there are more types than behaviors, OOP is the appropriate choice. But in situations where behaviors outnumber types, functional programming emerges as the preferred solution as it effectively separates data from behavior.
00:13:05.400 Moreover, using a functional programming language, you can still construct objects because an object is essentially a function that returns a data structure with attached behaviors. Often, most of the behaviors don’t occur on singular objects. You encounter structure definitions and behaviors in languages like Common Lisp, where behaviors are separated from their corresponding objects. Instead of sending messages between singular entities, you define interactions.
00:14:07.160 Could you clarify if you are arguing against class-oriented programming? Do you know the distinction between class-oriented programming and object-oriented programming? So you are currently practicing object-oriented programming in JavaScript? While it doesn't change the main argument, can you elucidate why class inheritance is considered 'evil'? Many argue that tight coupling is introduced through inheritance. For instance, polymorphism can be achieved through various methods, including prototype objects.
00:16:34.410 Let’s return to your question regarding how this room or conference appears in the functional programming world. I see a collection here, a collection of individuals. As such, the concept of collection exists in FP too. Additionally, we have various data structures aligned with the properties discussed earlier. However, the primary distinction lies in the separation of behaviors and data structures, in contrast to OOP, where behaviors are often inherent.
00:17:53.950 To the FP advocates: if functional programming is so efficient and capable of executing in parallel, how do you handle testing in environments that employ parallel functions? How do you orchestrate tests to ensure they yield reliable results? Concurrency is always challenging, regardless of context. We must recognize that concurrency and parallelism are not synonymous. Concurrency involves structuring programs so components operate independently but do not interact side-effectively, while parallelism refers to running multiple actions simultaneously to enhance performance.
00:19:12.650 To encapsulate the question, it addresses how to synchronize disparate threads in a parallel framework. In functional programming, I perceive processes rather than individual objects. Consequently, I do not see significant disparity between object-oriented programming and functional programming. For instance, even in a functional programming language, processes can maintain state, but that state is preserved within processes, not objects. Therefore, there exists substantial similarity between both paradigms.
00:20:33.830 Despite the shared similarities, we implement unit tests across both paradigms. If we examine the actual programming conducted in normal applications with state, such as when users make requests or prompts like needing to authenticate, it becomes necessary to focus on real-world applications rather than theoretical purity. You made a valid point regarding functional languages like Erlang, which are well-suited for concurrent programming.
00:21:55.070 In the context of mobile applications leveraging multiple cores, how can you rationalize reasoning about highly multi-threaded programs in an object-oriented, mutable environment? While I have yet to write such programs, I think it’s vital to manage each core effectively to prevent blocking operations, especially in scenarios where object states may interact with one another.
00:22:10.370 Nevertheless, if you encapsulate state within those threaded objects, there ought to be no significant side effects. It becomes simple to adjust the state of an object unintentionally due to reflection—where one object passes to another, and distinct threads consequently modify that object concurrently, leading to chaotic situations. In my approach, I prefer to utilize one object per thread, thus avoiding shared memory across threads by design.
00:23:07.820 This approach implies that each object will only have access to its own constructs, which means state changing will not conflict with other objects within separate threads. If you pass mutable states by reference, then those objects may find themselves exposed to unintended alterations. Furthermore, you need a significant number of objects to manage concurrency effectively, which is what object-oriented programming developers tend to criticize functional programming over, insisting that they maintain clean states without excessive object instances.
00:24:25.440 The essence of concurrency suggests that a system is non-deterministic, while the goal of parallelism is to create a deterministic environment. This leads to complications. FP’s purity comes from functions that produce immutable values without side effects. Acknowledging non-determinism, I don’t feel it harms my outcomes in any way.
00:25:55.810 If you portray functional programming as easier concerning concurrency, there’s likely a cost associated with it, as such applications can yield complex code. When seeing OOP implementations, one can visualize how the code is constructed and how various elements interact. Reading FP code, however, appears mathematic to me, often because I may not be adept at it. But isn't it solely code? Or is there a significant difference in syntax between paradigms that could make the distinction clearer? The reality is that variances in syntax often achieve comparable results in functionality.
00:27:40.140 Let’s discuss a matter of taste and experience regarding syntax. Those who grasp both paradigms may sense their meaning, but translating concepts can be challenging. In your analogy, when you're preparing food, do you relate to using a cryptic haiku or a detailed recipe? Most folks prefer straightforward instructions; yet programming requires nuanced thought. Functions in FP can succeed, or they can falter based on how we translate those abstract ideas into practical use, similar to how one perceives cooking instructions. If a recipe instructs you to use specific ingredients, it might not always yield the same results based on ingredient variability, while still following the method faithfully.
00:29:27.100 So where does this lead us? Many articulate that FP's complexity is embedded in function creation versus OOP's structural adherence. Creating distinct functions can lead to accidental complexity, raising the question of whether those functions can be efficiently constructed within an object-oriented framework. Ultimately, the goal remains to streamline operations while preserving the integrity and understandable nature of code.
00:30:33.460 The goal in programming is often defined by a balance between functional programming and object-oriented programming. By maintaining discipline in functional programming, you're implied to build modules mainly devoid of state, while leaving states to be managed in an orderly fashion in certain sections of the code. Such wiring contrasts OOP, which inherits its attributes from a discreet “object,” leading to complexities not often mirrored in FP.
00:32:02.320 We are coming to the end of our discussion, so let’s address whether functional programming and object-oriented programming are mutually exclusive or complementary. My take on it is that they are indeed complimentary. The modern functional programming languages showcase that it is possible to intermix both paradigms. Languages like Scala or Closure, known for their strong OOP backgrounds, allow for FP implementations and vice-versa.
00:33:37.650 To conclude, while they may differ fundamentally, both paradigms can be utilized to build effective systems. I encourage anyone interested in the potential of these languages to explore further into languages like Scala that almost merge the two paradigms effectively; it retains the expressiveness of Ruby while embedding immutable functional programming processes.
00:34:16.750 Thank you very much.