1. 28

Author argues the FP vs OO mindset is harmful to learning new concepts in programming. Further argues that beneficial aspects of one can often be done in the other. Gives specific examples.

  1.  

  2. 19

    I’ve seen this argument a lot that you CAN do one in the other, however syntax matters and defaults matter.

    1. 3

      Case in point:

         (assoc map :key value)
      

      In JavaScript:

         Object.assign({}, {key: value})
      

      Which is both more complex, less obvious and less efficient.

      1. 2

        Redux apps can use ES6 spreads for this: { ...map, key: value } - less efficient but certainly not too onerous syntax wise.

        1. 1

          This is not ES2015 (ES6) but ES2017. ;)

          1. 1

            Actually, it is not standardized at all. It is still a stage 3 proposal.

            https://github.com/tc39/proposal-object-rest-spread
            https://github.com/tc39/proposals

        2. 2

          Would anyone do that in real JS? (I don’t write JS, but if the answer is “no”, then I don’t see the point)

          As time goes on I have to admit that FP hype gets on my nerves. I enjoy the functional combinators as much as the next person, I love getting work done in Erlang, I am sometimes grouchy when writing Go when I can’t just map over a container, I love testing pure functions in any language, but maybe it has been python and rust that have made me a little happier in general to fall back on the boring procedural control structures (they support both paradigms, and yet people tend to use explicit iteration instead of the combinators pretty frequently). Readability is nice.

          1. 5

            Would anyone do that in real JS? (I don’t write JS, but if the answer is “no”, then I don’t see the point)

            Can’t speak for others, but we do that a lot in our React/Redux app to avoid mutation of objects from spreading.

            I have been using Python in a functional style, with LCs, map, filter and just about everything from the itertools and operator modules, but it was just not idiomatic. So when I switched to Clojure where this is the dominant way to do things it feels like coming home. I think the procedural control structures, especially when used in places where a map or fold would suffice makes it more difficult to understand what is going on because they could do anything.

            1. 3

              You might be interested in Coconut (http://coconut-lang.org).

              1. 1

                We should use the abstractions that minimize incidental complexity. I like using combinators when they fit on a single clean line. I tend to get lost when reading code that nests them, or is like a chain of 4+ combinators that hasn’t been documented more thoroughly than average. While I enjoy writing clojure and erlang (languages heavily reliant on combinators) they tend to be languages that I despise reading other people’s code for the most. I’m happy when people spend the time to make their stuff clean and fucrs-compliant, but it’s so damn painful for me to follow a nasty chain of nested combinators.

              2. 2

                Oh yeah, I have a React/Redux project that sometimes felt like half my code was calls to Object.assign. It’s the easiest way to do a shallow copy of an object so you’re not inadvertently passing references to the same object around and mutating them.

                Python does cause you to use more of the “boring procedural” control structures, and it does nominally support some functional programming, but some of it is more because of inconvenience than anything else. For example, I write far too many nested for/if loops where filter would be more natural if filter/map/lambda was less clunky and faster. I don’t think this makes it easier to read. I also think its reliance on list comprehensions is wrongheaded, because even now, after knowing Python for… ugh, nearly twenty years… I can never remember the order for iterating through nested lists in a list comprehension.

          2. 9

            IME, the FP vs OO debate usually has a lot to do with the state of things right now. Maybe the author is correct in that you could do a lot of these things in either. But who knows because it’s not done. Of the three languages I know of that “successfully” combine FP and OO:

            In Ocaml, objects are almost never used. I do see objects show up now and then but very infrequently.

            In Scala, I do not have enough experience to comment however people I trust struggle quite a bit with it.

            In F#, it seems like it tries to be as much like Ocaml as it can get and still survive in .Net. But I don’t have enough experience in it to draw strong conclusion.

            I think the author also misses the mark as he seems to be comparing languages rather than paradigms. In my experience, if one looks at the successful examples of OO languages doing functional things and people liking it, it’s usually because they are explicitly getting away from the OOP tar pit. The problem with objects is they are too powerful. Each object is its own universe and one rarely wants that. Looking at Java incorporating FP concepts and calling it a win for OOP is missing the point, I think. It’s rather an admittance that usually one probably doesn’t want objects and if you hold your nose and squint really hard you can sort of get Java to do that by convention.</flame inducing statements>

            Clearly I fall on the FP-side of things.

            1. 8

              Of the three languages I know of that “successfully” combine FP and OO:

              Arguably the reason this is such a big debate is that “OO” refers to a big pile of things, some of which are problematic (inheritance) and others which are no-brainers (hiding implementation details) and some which are somewhere in the middle (polymorphism, message passing).

              The other reason is that like speed, “FP” is a property of programs, not of languages. A language can only be “more FP” or “less FP” in that it contains the means for making FP programs convenient or awkward.

              1. 3

                FP is also a big pile of things, or, rather, several big piles of things that vary depending on who you ask that most people pretend are together just one pile of things.

                FP isn’t a technical definition. There are a couple good technical definitions for things like “pure” and “value semantics” and sometimes people use these in place of “FP”. There are also cultural movements and quacks-like-a-duck style arguments to be had. They all fall down the same way defining OO does.

                The author has it dead on: interesting questions only exist beneath the surface here. Beyond that, saying FP or OO is nothing more than stating your nationality.

              2. 5

                Rust seems to successfully combine them. If you discount the immutability requirement so does Go. The boundaries for the two paradigms are definitely blurring. C# has lambdas and closures and has had for a long time. Java recently got them. I don’t think the current division will last long.

                1. 13

                  Rust is not OO. Rust’s style can be thought of as closer to C-style structured programming with Haskell’s type system.

                  1. 2

                    In terms of programming style, Rust code reminds me a lot more of C++ style programming than of C-style structured programming. It’s true that it lacks several of the classic OO features C++ supports, especially the runtime oriented ones like dynamic dispatch on subtypes. But inheritance is deemphasized in modern C++ anyway, and the other features that make C++ style programming considerably different than C-style programming largely are present in Rust: RAII, monomorphized generics, exceptions, destructors, etc.

                    I don’t think that necessarily makes it OO (arguably certain styles of modern C++ are barely OO either), but it really doesn’t look much like C-style structured programming.

                    1. 3

                      C++ is explicitly a multi-paradigm language. One which can be best characterized as “stealing every popular feature from other popular languages since 1983.” You’re right that modern C++ does not match the OO paradigm, but one can still certainly write C++ in an OO style (heck, C++ started as “C with classes,” literally the addition of OO faculties to C).

                      1. 1

                        C++ starts with Simula. He wants to add its style to C in form of C with classes. Then, adds stuff from ALGOL68, Ada, CLU, and ML. So, yeah, a pile of features from other languages all thrown into one. I’ll add they’re also from languages that were sometimes really different from one another. It had to be done with close compatibility to C w/ low-cost abstractions. This situation leads to a language that’s ridiculously difficult to compile or even learn easily.

                        So, yeah, multi-paradigm for sure. Not cleanly like LISP either. ;)

                    2. 1

                      Rust is absolutely OO. It has objects with methods. It also has a type system inspired by Haskell. The two are not mutually exclusive. Your statement just makes the OP’s point for him. The lines here are blurring and it’s probably a good thing.

                      1. 8

                        Rust does not have objects, nor does it have methods in the way that term is understood in OO.

                        I can and will get into a technical explanation of why characterizing Rust as OO is wrong, but I think the philosophical perspective is more important:

                        Programming paradigms are mental models for thinking about programs. When a programming language is said to fit a particular paradigm, it means that the language provides facilities which are conducive to the writing of code which flows neatly from the model that paradigm encapsulates. When I say that a language is functional, I do not mean that it has the particular qualia of functional programming (as any attempt to list out a set of necessary and sufficient conditions for this categorization based on those qualia is doomed to fail), I mean that when I mentally model my program in the manner implies by the paradigm, I can easily convert that model into real code in the given language.

                        From this perspective, Rust is not OO, as an OO mental model is one where the program is understood as a collection of objects in a hierarchy communicating between each other to facilitate some behavior. This is not the mental model for which Rust is designed. You may be able to translate from such a mental model into a Rust program, but as anyone who has tried will tell you, it is real translation work. Strike one against the notion that Rust is OO.

                        Now, to some technical points. The difficulty here is that there is more than one characterization of the necessary and sufficient conditions for OO categorization. Rather than wade into that argument, I will take a look at a few of the features commonly agreed to be necessary, if not sufficient, and look at whether Rust meets them.

                        First, one distinction commonly found between the OO paradigm and the functional paradigm is how polymorphism is provided for. In the world of functional programming, parametric polymorphism is more often found. In OO, it is more often subtype polymorphism. Rust’s focus is clearly on parametric polymorphism, facilitated by the constraint mechanism of traits. Same as Haskell with its typeclasses. Now, Rust does have a limited amount of subtype polymorphism, but is found solely in polymorphism over lifetimes, to allow for the use of something which lives longer than a required constraint. A more general mechanism for subtype polymorphism that one would generally expect in an OO language (usually provided for via inheritance) does not exist in Rust. Strike two against the notion that Rust is OO.

                        Second, let us return to the notion of methods. Rust does have functions which can be called using the familiar “method notation” of data.function_name() with the data being implicitly passed as a first parameter. But using this syntax does not mean that Rust’s facility here is akin to methods in OO languages. In fact, Rust lacks a key feature here, which distinguishes it. In other OO languages, you have something called “open recursion.” Bob Nystrom has a nice explanation of this term, which I encourage you to read. What this means is that all the methods for a given objects are visible to each other, and that methods defined on a base objects have access to the real receiver of the method call (which may be some subtype of the base object). Rust does not have this, as Rust does not have inheritance, and the extension of this to “open recursion over subtype lifetimes” is essentially meaningless, as the lifetime system doesn’t parameterize functions over concrete lifetimes anyway, but on abstract lifetime constraints (but I digress). Anyway, strike three against the notion that Rust is OO.

                        I could go on, but I will simply add to this that questions of how to do OO programming in Rust pop up all the time, and the consistent answer is that it can’t be done and you need to figure out another mental model for working with Rust. Either this is true, and Rust is not OO, or this is wrong, and somehow a bunch of people have all missed how Rust is totally OO and workable with that paradigm. Occam’s Razor certainly suggests the former.

                        1. 4

                          Both of your technical reasons are just “Rust doesn’t have subclassing/inheritance”. This is often associated with OO, but I disagree that it’s a necessary part of OO. Based on a cursory peek, it seems like many definitions online agree with me on this.

                          1. 4

                            Okay, fine. Let’s try some other ones.

                            • Dynamic dispatch: Rust has this via trait objects, but it’s very different from how dynamic dispatch is done in OO languages. Trait objects undergo type erasure, meaning the full capabilities of the original type are not accessible, and instead only the capabilities provided for in the associated trait may be used.
                            • Encapsulation: Rust has a privacy mechanism, but it’s at module boundaries, not data boundaries.
                            • Objects and classes: Rust does not have classes, nor does it have prototypes. These are the two classical systems for modeling the specification of objects.

                            I could go on. I gave those two examples because they were the first that came to me, not because they are the only ones.

                            1. 1

                              I think that perhaps the core of this disagreement is that you have a definition of OO that does not fit with the wider more common definition of OO. Which again is sort of the point of the OP’s article. Being precise about what particular feature/property someone means when they say something is OO or Functional is a far more useful conversation than the general terms OO and Functional can communicate.

                              For instance, I find Rust’s properties of Immutability by default, Objects with Methods defined on them, Dynamic Dispatch, ADT or Enum datatypes, and Pattern Matching to be quite useful. That is both a more detailed and more useful discussion than whether or not Rust is a Functional or Object Oriented language.

                              1. 3

                                The items I listed are literally the heading items from the Wikipedia page for object-oriented programming.

                                1. 2

                                  Instead of Wikipedia, I’d say describe which items on your list were present in Simula, Smalltalk, and C++ since they’re the languages that brought us OOP style either inventing it or popularizing practical use of it.

                                  1. 2

                                    I’m not going to play the game of which definition of OO is the perfectly right one, trying the proper incantation of features to analyze to show that Rust isn’t OO. I have made my case in terms of the most common features which available references list as representative of the OO paradigm. If you are unpersuaded, fine. I will not follow moving goalposts.

                                    1. 1

                                      I didn’t see a game, never mentioned Rust, and wasn’t trying to be persuaded. I just noted that Simula invented OOP with Smalltalk and C++ popularizing it from different angles. It seems what defines OOP would be in those, esp Simula or Smalltalk. Anyone redefining it from there would be likely moving goal posts since those two distilled its essence in powerful form a long time ago.

                                      And then much conversation followed by all kinds of people who didn’t invent anything like that but copied parts of their concepts. Much debate focuses on those successors when the pioneers might clear some things up.

                                      1. 3

                                        You’re commenting in a comment chain about whether Rust is an OO language or not, where I have taken the stance that it isn’t. Each post (yours is the third) asked for an argument based on a different standard. I was willing to do that twice. Not a third time.

                                        Also, I hardly think that judging OO today with anything other than Simula, Smalltalk, or C++ is moving goalposts. If it is, the goalposts moved so slowly that we’ve all had time to adjust. I have never in my life met a Simula or Smalltalk programmer. The ability to even find people who understand OOP as it was originally done in these languages is limited. It’s simply not a reasonable standard when discussing what OOP is commonly understood to be today.

                                        1. 1

                                          It seems what defines OOP would be in those, esp Simula or Smalltalk

                                          Maybe in some discussion, but the OOP 99% of developers are using today is really disjoint, from Smalltalk at least. Java, C#, and C++ share very little in common with it. I can’t speak for Simula.

                                          1. 1

                                            Good point. Far as C++ model, it was inspired by designer’s experience using Simula. He loved it.

                            2. 1

                              What you’ve said about “open recursion” is really interesting. Do you have any links to really useful applications of open recursion in terms of object hierarchies?

                              1. 3

                                Thanks! I’m not quite sure what you mean by that. Are you asking for examples of where open recursion is required / used in OOP?

                                Well, first let’s clarify what’s meant by “recursion” here. In this context, “recursion” means that the methods of a class are visible to each other; they can see each other’s names. So, this works:

                                #!/usr/bin/env ruby
                                
                                class Counter
                                    def initialize
                                        @value = 0
                                    end
                                
                                    def increment
                                        set(get + 1)
                                    end
                                
                                    def set(value)
                                        @value = value
                                    end
                                
                                    def get
                                        @value
                                    end
                                end
                                

                                In this example, increment can see the set and get functions without them having to be in a particular order, and without any special care or effort on the part of the programmer. This is “recursion” as it is meant in the term “open recursion.”

                                So what about the “open” part? Here “open” means “late-bound,” or put another way “including both functions already defined, and functions which be defined in subclasses introduced later.” Here’s what that looks like:

                                #!/usr/bin/env ruby
                                
                                class Counter
                                    def initialize(value: 0)
                                        @value = value
                                    end
                                
                                    def increment
                                        set(get + 1)
                                    end
                                
                                    def set(value)
                                        @value = value
                                    end
                                
                                    def get
                                        @value
                                    end
                                end
                                
                                class LoggingCounter < Counter
                                    def get
                                        puts "getting #{@value}!"
                                        @value
                                    end
                                
                                    def set(value)
                                        puts "setting #{value}!"
                                        @value = value
                                    end
                                end
                                

                                In this example, LoggingCounter inherits from Counter, and redefines get and set to print what they’re doing. If you create an instance of LoggingCounter and call increment on it, you’ll see that it does indeed call the get and set defined in LoggingCounter and not the get and set defined in Counter. This is “open recursion” because the implicit “this” (or “self”) being used to determine the object on which to find the get and set methods is late-bound. Rather than being determined statically, it is determined dynamically. At runtime, the language sees that the object on which the increment method is being called has defined get and set, and it calls those get and set functions, rather than the get and set functions from the original class in which increment was defined.

                                That’s open recursion!

                                1. 2

                                  Does Rust support open recursion via traits? IIRC, in Haskell, open recursion is the one situation where the dispatch table of a typeclass cannot be compiled away, if a compiler were so inclined. Although, I believe open recursion shows up differently in Haskell than in the standard OOP example due to the lack of a type heirarchy, but I don’t have an example handy.

                                  1. 1

                                    Are you asking for examples of where open recursion is required / used in OOP?

                                    Yes, exactly. Your example in Ruby is a very clear example of how subclass methods can be called by superclass methods. It’s hard for me to imagine doing this in a language without implementation inheritance. In fact I think I now understand what the point of implementation inheritance is! For the last approximately 10 years I’ve believed it was useless. On the other hand, I basically never use implementation inheritance, which suggests to me that open recursion really isn’t that necessary is a program design tool.

                                    So are there designs where open recursion (and therefore implementation inheritance) is critically needed? I can’t think of any off the top of my head. Perhaps something to do with creating new widgets in an GUI system?

                                    1. 1

                                      I’m not sure it’s critically needed, but I’ve seen implementation inheritance commonly used in statistical machine learning libraries, at least those of a certain peak-OOP era. There are often variations of a basic method that only differ in a few respects (e.g. a different sampling method), so one implementation technique is to have these variants inherit a base implementation and then override the one or two methods that differ for that variant.

                                      Obviously there are non-OO approaches to solving that problem as well. The classic C approach would be to just have one big implementation with a bunch of configuration flags (R packages also often use this approach). An FP approach might be to write a generic version of the method parameterized by functions passed as parameters, so e.g. passing in a customized sampling function as a parameter, overriding the default, plays the same role as overriding the sampling method would in the OO version.

                        2. [Comment removed by author]

                          1. 4

                            When I wrote CL for a living (circa 2006-2014), it seemed like the community leaned more toward an OO style (in the sense of using lots of objects and mutable state–of course lambdas were also heavily used.). There was a sense that this was more pragmatic and modern, and purely-functional approaches were dated or overly precious. I’ve heard that this has changed a bit now as FP has grown more popular in general. (It’s also possible that my perceptions were colored by the codebase I worked on, which was already old in 2006 and was heavily OO.)

                            1. 3

                              It is true that OO Style is more common in CL, but I’ve never seen functional approaches being referred to as dated. Personally I think reason is that, although CL is multi-paradigm it is really easy to write in an imperative style with small functions.

                              But there are a lot of examples of FP in CL. For example a parser combinator library, smug, is written using monadic parsers. Scott McKay, of Symbolics fame, is vocal about writing CL in a functional style. He has written Fset, a functional data structure library and a generalization of map designed to replace the use of loop. He even has a small extension that move the local functions after the body which makes them add less noise to the function definition.

                              You can in SICL’s Loop implementation to see CLOS being combined with parser combinators. The parsers build the AST and generic functions emit the code.

                              The antagonistic perspective of FP vs OO is detrimental, why [learn from] not both. Recently I was reading up on parser combinators and found the monadic variant of it. Wadler’s How to turn a failurue into a list of successes did a good job explaining the advantages of using a parser monad and how monads (which I now think of as design patterns derived from math) can help the every day programmer. I wouldn’t jump through hoops when a setf does the job and I wouldn’t write tons of mix-ins when I can just compose small functions.

                              1. 3

                                I think that’s been true for most of CL’s existence… the people who wanted more of an FP style (mainly immutable data, mainly recursion for control flow, etc.) went to Scheme, while CL was a standardization of the “industrial” dialects of Lisp that didn’t have those goals. Although I believe in Lispland only code that is heavily CLOS based would really be considered “OO”, and there’s plenty of CL code that is neither in an FP style nor CLOS based.

                          2. 7

                            There are so many everything-looks-like-a-nail situations with programming paradigms. When I write C, I write using a structured procedural style. When I write Python, I write in a mostly-OO style. When I write Erlang, I write in a functional style. They all have their benefits and their drawbacks, and no one style is better than others.

                            (There are parts of our product that pretty much have to be written in C, since they deal with really close to the hardware stuff. Maybe Rust would work, but I don’t know it well enough to know. Most everything else is in Python out of convenience. Most of my personal projects are likewise C and Python.)

                            1. 11

                              Interestingly, really good OO code starts looking very functional, with state confined to a few places, and side effects contained in boundary objects. I don’t know if good procedural code has the exact same characteristics, but I imagine it would also look similar in terms of modularization and coupling. In other words: the aesthetics of good code transcend paradigm, but different paradigms can have a big effect on how easy it is achieve this. There’s also the issue of culture at work here: some software cultures are far more tolerant of practices that make it difficult to realize excellent code.

                              1. 4

                                really good OO code starts looking very functional

                                I’ve often found that to be true, for sure. Even in OO languages without lambdas, or if you want to be an OO purist and eschew lambdas, you can do lambda-like things with “runnable” classes. Look at how Smalltalk did blocks, even before they were true closures (as was the case in the earliest Smalltalks).

                                1. 9

                                  Does that mean that one style is better than another? I’ve never actually seen (but haven’t looked hard) someone say “Good FP code looks object oriented”. But I hear the opposite very frequently. Maybe everything isn’t equal and some ways of expressing solutions are better.

                                  1. 3

                                    If you drop the commonly cited immutability argument, any FP code that uses a lot of closures looks remarkably like OO code. You can think of an object with methods as either a bag of closures or a bunch of curried functions.

                                    This is one of the reasons the lines between the two have become increasingly blurred. I think as concepts and patterns are discovered they tend to migrate between the two.

                                    1. 1

                                      Except that would not be considered good functional programming. Codebases that are described as functional are not designed in this way.

                            2. 3

                              One particular problem I am facing with both FP and OO languages are elegant ways of dealing with sharing and cycles in immutable data structures.

                              Example from Java: enforcing immutable objects using ‘final’ disallows cycles to be constructed. Example from Haskell: any inductive data type prohibits referencing outer terms.

                              I know coalgebraic/coinductive data types exists, but I am not yet as far to know how to use them to solve practical programming problems.

                              1. 2

                                In Haskell you can use something like ST to write a pure algorithm involving cycles or IO for an impure one. You can also use these techniques to “tie the knot” and make a pure value that has a loop in it. As soon as you begin viewing/modifying this value, however, it must be “untied”.

                              2. 3

                                What seems to be missed in pitting Functional Programming “versus” Object-Oriented Programming is that they are ways of structuring thought, of considering processes that will be executed in a computer program: rather they are treated as exclusive, mutually incompatible tools that must be selected in constructing a computer program. You can think “this thing sends a message to that thing” and write software in MOSFET 6502 machine language, and you can think “this information is transformed in this way” then write software in Smalltalk.

                                You can even think about both in modelling the same process, and that’s OK.

                                When talking about language features or syntax points that “promote” OOP or FP, I tend to think they prefer expression of ideas considered in those ways. You don’t not do functional programming because your language doesn’t have flatMap, you don’t do functional programming because your brain didn’t come up with flatMap. Conversely, where a language has classes and interfaces, whether you treat those as object classifications or data types and type classes is up to you.

                                1. 3

                                  ‘Just wanted to laud (in case there are anyone who doesn’t know of it) Backus’ 1978 paper on FP. It’s quite readable & comes from a unique perspective on the matter.