1. 26
  1.  

  2. 24

    OO is not about state.

    Objects are not data structures. Objects may use data structures; but the manner in which those data structures are used >or contained is hidden. This is why data fields are private. From the outside looking in you cannot see any state. All you >can see are functions. Therefore Objects are about functions not about state.

    So because you make all the state hidden an implicit, it suddenly doesn’t contain state anymore?

    1. 3

      Yes, the start of the essay did not make much sense to me. One part of the FP approach is that a function is a mapping. The OO approach is that a class method is a mutable mapping, since the function itself can change based on the state of the object it belongs to.

      There are some arguments that FP is easier to debug, but I think if you write proper test cases Classes are also easy to write and write correctly.

      I think FP would hamper me because sometimes I write code that involves making write changes to a large data structure. Perhaps, if I was a FP maven, I would rethink the entire architecture of what I was doing, but having read only inputs would mean I would be copying large chunks to data on the return of the function that I would rather not repeatedly do.

      In all of my work I’ve found that nothing really stops you from writing C or Python functionally - perhaps you don’t get the fancy stuff that is in Haskell (that I don’t understand yet :)) but you can write everything as const input -> output, if you wanted to.

      1. 9

        Some functional languages implement structural sharing (also called persistent data structures), which means that when you request a modified version of an immutable data structure, most of the returned data structure consists of pointers to sections of the old one, with only the changed part of the returned structure being new. Scala and Clojure are two such languages.

        Using structural sharing when modifying a large data structure avoids “copying large chunks to data”. I don’t think it forces you to “rethink [your] entire architecture” either.

        1. 3

          Add OCaml to that list. It has a syntax for “functional updates”, which do exactly what you described.

          1. 2

            Functional updates in OCaml aren’t especially efficient, because they’re not implemented by persistent data structures. Persistent data structures are mostly a feature of your library, not your language. I haven’t tried Reins and so I don’t know if it’s good or not.

            1. 1

              Don’t they, though?

              type 'a list = { head : 'a; tail : 'a list option }
              let singl   x = { head = x; tail = None   };;
              let ( ^ ) a x = { head = a; tail = Some x };;
              
              let lst  = 1 ^ 1 ^ 1 ^ singl 1 
              let lst2 = { lst with head = 2 };;
              
              # lst2 = lst;;
              - : bool = false
              # lst2.tail = lst.tail;;
              - : bool = true
              
              1. 1

                Don’t they what?

          2. 2

            Haskell does this as well.

          3. 8

            There are some arguments that FP is easier to debug, but I think if you write proper test cases Classes are also easy to write and write correctly.

            I’ve never understood the claims that FP is easier to debug. In an OO program the state is in object fields where it’s at least inspectable at a point in time. In a functional program the same state tends to be buried in anonymous closures (e.g. state monad, iteratees) where it’s just completely invisible.

            1. 1

              You can do FP that way, and then you can try to debug it with a debugger which for some reason supports inspecting object fields but not closure bindings, but as much as possible, what we try to do in FP is put that data in our arguments instead of in closure bindings.

          4. 2

            If you have the time, I highly suggest listening to this interview with Uncle Bob: http://www.functionalgeekery.com/episode-1-robert-c-martin/

            One of his points in the episode is that OO and immutability can be compatible. Instead of mutating your original object, a method can just return a new copy of the object with the appropriate changes.

            I think that idea helps make sense of the article. But I agree it sounds odd without additional context.

            1. 6

              Uncle Bob discusses OO and immutability from 41:00 to 43:37 in the interview. It corresponds to “Functional Programming and Object Oriented Compatibility” in the list of topics on the episode page. I have transcribed that part:

              Steven Proctor: So I know you still go out and preach for good object-oriented design.

              Robert “Bob” C. Martin: Sure.

              Proctor: And one cannot blame you. How has your message changed when you reach out to the developers doing object-oriented programming?

              Bob: I don’t know that it’s changed at all. If we’re doing OO, we have a set of principles that we need to follow, we need to write good code, we need to develop systems that are decoupled, and plugins with a plugin architecture – all of that stuff I believe remains the same.

              FP does not change what OO is. There has been a fair bit of discussion over whether or not OO and functional are even compatible. And I find that very interesting, because, from my point of view, they are completely compatible. And orthogonal. They have almost nothing to do with each other. A program can be both OO and functional simultaneously.

              The reason people get confused about that is that they think of objects being inherently mutable. But there’s nothing about an object that requires it to be mutable. You can operate on an object, and its return value can be another object, rather than a mutation of the existing code. Which means you can write very reasonable object-oriented programs in a functional style.

              One of the benefits of the OO style is polymorphism – the ability to invoke a function and not know precisely what function is going to be invoked. There’s this very interesting inversion of dependencies that occurs across a polymorphic boundary. And that inversion of dependencies can still exist in a functional language.

              Example: Clojure has a facility called protocols, and records, where you’re allowed to create these – they look like methods. They look like method declarations. And then they can be implemented by a separate module entirely, with the same kind of dependency inversion structure. So you get all the benefit of polymorphic OO – all the same principles apply, as far as that’s concerned – and you can also insert immutability. And get a decent, functional, object-oriented program. Which is structured, too, by the way.

              1. 4

                The reason people get confused about that is that they think of objects being inherently mutable. But there’s nothing about an object that requires it to be mutable. You can operate on an object, and its return value can be another object, rather than a mutation of the existing code. Which means you can write very reasonable object-oriented programs in a functional style.

                This is a factually true statement. However I’m not aware of any language that does this at scale, therefore it’s a bit misleading to try to claim this is a reasonable approach.

                One of the benefits of the OO style is polymorphism – the ability to invoke a function and not know precisely what function is going to be invoked.

                This is the only benefit of OO, as far as I can tell. It is the only thing that OO brings to the table. While it is useful, I find it useful in an extremely small number of cases. The value of run-time subtyping is vastly over estimated, IMO.

                1. 4

                  This is a factually true statement. However I’m not aware of any language that does this at scale, therefore it’s a bit misleading to try to claim this is a reasonable approach.

                  Not sure what “at scale” means, but I do that in Scala all the time. Have an object, call a method on it that makes a copy with a change (either an OO-style abstract method or using a lens), then call a polymorphic method on it.

                  1. 4

                    I once went through my 600k line application at work to see how many times we used dependency inversion to inject polymorphic behavior. It was three times. The other 381 interfaces were all for unit test mocking. :(

                    1. 2

                      How is that not injecting polymorphic behavior?

                  2. 1

                    So a standard OO task I do is this. I have a large-ish list of objects and iterate through it and conditionally call methods that change values of those objects in-place, sometimes returning keeping track of the process and returning a value. I assume I could do by generating a whole new list with some operation like map or whatever. But it seems like doing things that way would be inherently inefficient - new memory would have to be allocated, all the base object’s values would have to be copied and so-forth. Even if the old object data is instantly deleted after things are done, the amount of bytes moved around would be a lot more. Am I missing something?

                    1. 6

                      Efficiency can be counterintuitive. It’s true that copying is inherently inefficient. But mutation is also inherently inefficient!

                      If your list is largeish, then the list, and many of the objects in it, have probably been around for a while. If you’re using garbage collection, that probably means many of them aren’t in the nursery. Every time you mutate one of them, the garbage collector’s write barrier gets invoked, and then the garbage collector has to scan them at the next minor collection, which slows down the minor collection. They’re probably also, for the same reason, scattered all over your address space, so every time you iterate over that list and touch all the objects, you’re probably incurring both L1D and TLB misses. By contrast, if you create a new list of new objects, all of them will be in the nursery, probably all in the same few pages, and if you throw away that new list before the next minor collection, it won’t cost the GC any time at all, except by triggering a minor collection sooner. If they do survive and get copied into the next generation, they’ll probably still be close together in the next generation, so you can potentially have better locality. Allocating in the nursery is very fast, if you’re using a garbage collector optimized for this; it consists of adding an immediate value to a register and checking to see if it’s hit the end of the nursery yet. All of this does, as you say, have to be weighed against the locality penalty of having two copies of the list and its objects instead of one.

                      (The above depends greatly on how your GC is tuned. OCaml and Erlang have GCs that are tuned for high allocation rates, small objects, and very little mutation. Java and C# have GCs that are tuned for lower allocation rates, larger objects, and lots of mutation. Golang’s GC isn’t really tuned for much of anything, but Golang isn’t a strict object-graph language in the way those others are, so its GC has a much easier job, while copying an object to change a field or two is correspondingly much more expensive. Python’s GC isn’t tuned for much of anything either, but because its interpretation and late-binding overhead is commonly more than an order of magnitude, the GC isn’t the overwhelming efficiency concern that it is in languages with more efficient implementations. I don’t know anything about the GCs used for JS and Ruby.)

                      If you’re running multiple threads that all share access to the same list — and you are of course running multiple threads if you’re concerned about efficiency in 2015 because that makes your code run between 4× and 32× faster, except in pathological cases — you have to figure out how to synchronize their accesses. If you lock the individual objects in the largeish list, your program can easily spend all its time locking and unlocking the bus, and you won’t get a performance benefit of multiple threads. If, instead, you lock the entire list, only one thread can touch it at a time, and so you can easily reduce your performance to that of single-threaded case, again; and of course every time one of your cores mutates an object, its cache has to acquire exclusive access to that cache line first, slowing down all the other cores. Meanwhile, you can easily get into an oscillation between adding deadlocks and race conditions by making your locks so fine-grained that you can’t reason about them without making mistakes, and making them so coarse-grained that you stop getting a speedup. Making the list immutable and copying it avoids all of these problems.

                      All of this stuff about performance, though, is often less important than the questions of expressiveness and correctness, which are two concerns that are at odds with each other and with execution efficiency. But you probably know that already.

                      1. 2

                        That is indeed a problem with the FP in common imperative OO interpreters/vms. They’re not well-designed for the persistent data structures that make your use case cheap, and that sort of thing is never in the stdlib. For example, the hamster gems adds PDSs to Ruby - I’m kind of amazed it didn’t have to be written in C.

                        1. 2

                          To be fair, I think PDSs usually do have some kind of asymptotic slowdown, usually log N or log log N, over the mutating version.

                          1. 2

                            To be fair, log(log(1030)) ~ 1.5.

                            To be extra fair, they also often have worse coefficients, too.

                    2. 1

                      I’ve been mulling a functional object language for awhile that encourages this style.

                      I really like FP, but I still think there are cases where ‘closing over’ a small amount of immutable state is beneficial from both a modeling and an encapsulation standpoint. It would need some syntax where the current object can return a copy of itself with a few changes (much like Haskell’s record update syntax), but that’s not a big deal.

                      1. 4

                        Ruby has a nice feature.

                        “freeze”

                        You can freeze an object and it will throw an exception if you attempt to mutate it.

                        These days, by default, I invoke “freeze” on every object at the end of the constructor.

                        If I hit this exception, I review the code and consider whether I really need mutation, or whether it would be cleaner to provide the information up front to the constructor.

                        Usually the answer is, I can trivially refactor to provide everything I need for the object at construction time.

                        Occasionally it makes sense, it is easier and cleaner to have a mutable object, if so I remove the freeze from the constructor.

                        I also quite like metamorphizing objects.

                        Often certain methods make no sense until the object has accumulated enough state.

                        And once they do make sense, adding state to the object no longer makes sense.

                        So it would seem the object, like an insect, has different phases in it’s life cycle.

                        So sometimes I construct a mutable object which only has “add state” methods and a metamorphise method.

                        The metamorphise method freezes the object and it’s state, and wraps that state in a new frozen immutable object that has querying methods, no state manipulation methods.

                        The metamorphise method returns the “adult” stage object, leaving the behind the frozen dead skin of it’s larval stage.

                        1. 5

                          The adamantium gem is very convenient: it can freeze an object after the initializer runs, and it provides a ‘transform’ method that dupes the current object with a block for mutation.

                        2. 1

                          Existential types can do this. They’re discouraged in Haskell because people often turn to them immediately to try to implement some nasty OO-alike subclassing hierarchy… but they’re not inherently bad! There are a fairly large number of “degenerate” ones, though.

                      2. 1

                        I thought that particular part was rather a stretch as well.

                      3. 10

                        Objects are bags of functions, not bags of data.

                        If that were true, then we’d just create modules and write regular functions. Objects exist because of state, because of data.

                        1. 5

                          I’m fairly convinced these days that OO is not much more than FP with open recursion. It’s quite true that open and closed recursion share a special bond, but the rest of what people classically think of as OO can be fairly easily composed out of FP primitives.

                          The difference is the mental model of OO, the idea of cellular interactions born as early as Alan Kay himself, which arises from a huge set of interacting language design choices. From an FP perspective there’s little reason to compose all of these together in this unique way—its a little Rube Goldbergian.

                          1. 5

                            So essentially OO couples a bunch of design details together needlessly under the ‘OO’ banner? Can you elaborate on what these are?

                            I have:

                            • implicit ‘this’ binding carrying instance data
                            • single/multiple dynamic dispatch
                            • a ‘namespace’ for methods (via dot notation or otherwise)
                            • inheritance/mixins
                            • affordances to modeling (con: kingdom of nouns)

                            I find it interesting that OO, or parts, find implementations in a variety of FP langs. I’ve been on the pure FP bandwagon for a bit now, but I keep coming back to parts of it. I don’t like all of it – but I think a lang with some OO principles mixed with FP has the potential to not be as scary as Scala. :)

                            1. 3

                              The implicit this bit is the open recursion bit. The important point is that this isn’t resolved until the object is “sealed” with new. The other major thing that you have is existential type. A lot of what remains is dressing around those notions.

                              Inheritance and mixins are well-simulated by functions (just see a “class” as a function which results in some kind of object type). Namespacing is a syntactic construct; it can be stylistically added to FP or OO languages in many ways. Singular dynamic dispatch is an implementation method for open recursion. Multiple dynamic dispatch might be another novel bit and I’ll think on that.

                              I’ve been fooling with OCaml for a little bit now and I find that they capture a lot of OO niceties even before you dip your toes in the actual OO system (which is largely neglected as I understand it excepting to use its type system as phantom typing in the functional side).

                              1. 2
                                • final encodings
                                1. 1

                                  I disagree with @tel that OO brings a bunch of design choices under one roof needlessly. OO has one design choice: opaque state + dispatch table. This lets objects be their own universe. That is the problem: objects can do anything. There is no mechanism to limit what an object does (or at least nobody has proposed a good one). What many people don’t realize, or maybe I’m wrong, is that value is derived from constraints. But limited what something can do I can understand it more easily. We are still struggling, as an industry and an academic group, to figure out how to express constraints well, but that is where almost everyone is going.

                                  1. 1

                                    Well, Golang does the “opaque state + dispatch table” thing, but it doesn’t do inheritance or implicit binding of a this. (Unless you count anonymous inner structs as “inheritance”.) Lua does the dispatch-table thing, and you can implement inheritance in it, but it isn’t in the language, and it doesn’t do opaque state. Python doesn’t do opaque state or implicit this either, except sort of, via closures. Common Lisp without CLOS does opaque state by way of packages, but without the other things; CLOS provides the usual set of OO facilities. OCaml’s polymorphic variant tags add subtyping to it, and if you use that without using its object system, you have (at least one kind of) inheritance, but without opaque states and dispatch tables. I think you can make reasonable arguments that Lua or Golang either are OO or are not OO, and that fuzziness demonstrates that these design choices are not tightly coupled together.

                                    I agree that OO is definitely on the “expressiveness” side of the expressiveness/correctness/efficiency tradeoff.

                                    1. 1

                                      Re: inheritance

                                      I don’t see inheritance as much more than syntactic sugar for creating a dispatch table that looks a particular way.

                                      1. 1

                                        Inheritance is what makes your open recursion open, which is very flexible and also super error-prone.

                                        Here’s a bug I ran into last week. Some Python I wrote with asyncore around 2002 was apparently working on the new machine, but using 100% of a CPU. strace revealed that it was alternating between writing zero bytes to fd 5, the tty, and selecting with a set of writable FDs that included fd 5, which would return immediately because the terminal didn’t have a full output buffer. Turns out it was using its own UI subclass of asyncore.dispatcher_with_send to write to the tty, so that if the tty did get blocked, it wouldn’t become unresponsive to network traffic. At some point, the dispatcher_with_send class started implementing asyncore’s writable() method with not self.connected or self.write_buffer != "" — i.e. when it’s not connected it’s always writable. But the tty’s dispatcher was never getting connected set on it, because the UI subclass would never invoke self.connect, and at some version of the asyncore library, that started making it always writable. Setting self.connected = True in __init__ solved the problem.

                                        This is a specific case of the general rule that, for two pieces of code to be decoupled so that you can change them independently, you want the interface between them to be as minimal and easily verifiable as possible; but inheritance means that the entire superclass is in some sense exposed to the subclass, and so they are very tightly coupled.

                                2. 2

                                  In my experience, the only thing that matters in OO is dynamic dispatch. Everything else can be emulated. While dynamic dispatch makes some things harder to reason about, FP hasn’t come up with a good alternative to this day.

                                  While the article doesn’t say anything new, probably the only people unhappy about it are some relicts from an dogmatic FP-only/OO-only era.

                                  OO and FP work fairly well together, the problem is that not many languages actually try to make both of them work well; instead they just say “OO sucks” though its only their implementation that sucks (same with FP).

                                  1. 3

                                    I’m not sure that I’ve thought it through completely, but I tend to feel that dynamic dispatch = open recursion. You can emulate this trivially in FP, but it’s true that it’s not well-favored in most FP-focused languages.

                                    OO and FP work fairly well together

                                    This is really what I’m arguing for. The way that I understand it is that FP has decomposed the entire solution space much further than “OO-focused” languages have in the past and therefore can be taken as a more solid foundation. Atop it some OO niceties are valuable. In particular, their focus on existential typing is very valuable.

                                    I just recently read a paper by Andreas Rossberg about a variant of SML called 1ML which has a lot of the properties I’m thinking of—though not open recursion. It also suffers from contaminating the entire language with module type inference difficulties which makes me wonder how endemic poor inference is to “OO-like” features generally. Typically this is the part where you have introduced rampant subtyping and thus thrown away principal types and unification for subtypes and semi-unification (highly complex, no principal solutions exist). I think this is a similar story to the “large” module language in ML/1ML. All that said, complete type inference is not necessary, I feel, despite people often desiring it.

                                    http://www.mpi-sws.org/~rossberg/papers/Rossberg%20-%201ML%20--%20Core%20and%20modules%20united%20[Draft].pdf

                                    1. 1

                                      Module system + HKT + limited implicits sounds a lot like Scala (although a bit limited as mentioned by you and in the paper itself). It’s great that more languages are exploring this direction, and adding type inference and top sounds like a lot of interesting work!

                                      I think some of the remaining differences are now down to whether language designers want to support an open world assumption/separate compilation/late binding, i. e. whether modularity is eliminated at compilation time or not.

                                      1. 1

                                        I agree that Scala did some interesting experimentation there. I’m interested in catching up on Odersky’s Dotty calculus work eventually.

                                      2. 1

                                        I think several variants of Abadí and Cardelli’s object-calculus have terminating type inference algorithms that support open recursion with contravariant argument types (with covariant typing for self, naturally) and subtyping. No?

                                      3. 4

                                        Nah you can do that too

                                        (It’s a vtable implementation just like an OO language too)

                                        You get inlining, no vtable, better perf typically if you can avoid sticking the existential in there. :)

                                        1. 2

                                          It requires a language pragma, has a painful amount of boilerplate, gives no compiler error for unimplemented methods, and plenty of people say “never do things this way”.

                                          All of this illustrates the points about “everything else can be emulated” and “not many languages actually try to make both of them work well” quite nicely!

                                          Thanks, I think I’ll use this example in the future.

                                          1. 3

                                            Plenty of people say “never do things this way” not because it’s a bad technique but because it’s rare to find examples which aren’t more clearly handled without using dynamic dispatch.

                                            Often I find that I’ll create a program which does involve a kind of dynamic dispatch system and then after playing with it a bit I’ll refactor down into a simpler, faster one which doesn’t require it and has all of the same extensibility properties.

                                            1. 1

                                              Isn’t this a clear case of either ““OO sucks” though its only their implementation that sucks” or bad language design? Why have both when one of them is not useful in 95% of the cases?

                                              1. 1

                                                The features which enable this stuff are interesting that they provide a lot of power in combination. Each one by itself tends to be well justified and defined as well. I think there is significant exploration here still. It’s not that these examples don’t exist or even that these examples aren’t dominant… just that they tend not to come up.

                                                I personally am not too shy at all to use existentials at least to begin.

                                        2. 2

                                          You say:

                                          While dynamic dispatch makes some things harder to reason about, FP hasn’t come up with a good alternative

                                          But the crucial thing that makes a language usable for FP is (contra Martin’s ignorant bloviation about immutability) the ability to pass functions as arguments, which the callee can then invoke; and then their call is necessarily dispatched dynamically! So you can make a reasonable argument that, far from FP lacking dynamic dispatch or any “good alternative”, FP is all about dynamic dispatch!

                                          1. 1

                                            In my experience, the only thing that matters in OO is dynamic dispatch

                                            I agree with this. I think that the value of dynamic dispatch is very small though. I think compile-time subtyping is a superior alternative that has almost all the pros, very few of the cons, and someone can emulate dynamic dispatch in the few cases where it is actually valuable.

                                            http://snowsuit.io/issue03.html#sec-2-1

                                        3. 7

                                          This article seems like trolling. I’m constantly amazed by Bob Martin’s apparent level of cluelessness. There probably isn’t a single screenful in this essay that doesn’t contain at least one major factual error or case of missing the point. For example:

                                          OO is not about state.

                                          Objects are not data structures.

                                          Objects are bags of functions. (Yeah? What’s a class, then? Or a module? Or an ML functor?)

                                          Is there really so much difference between f(o), o.f() (missing the point)

                                          FP imposes discipline upon assignment (missing the point; FP is about higher-order abstraction facilities, not immutability)

                                          A true functional programming language has no assignment operator. …a functional language imposes some kind of ceremony or discipline on changes of state. (Don’t tell the Scheme guys.)

                                          OO imposes discipline on function pointers. (And I suppose Hindley-Milner typing doesn’t?)

                                          Trying to write polymorphic code with function pointers (as in C) depends on complex and inconvenient conventions that everyone must follow in every case. (This sounds like somebody who’s never tried doing it. Or bothered to glance at the Linux kernel source.)

                                          It goes on and on in this embarrassingly ignorant vein.

                                          1. 3

                                            Personally, I find this comment counterproductive. It consists primarily of name-calling (“clueless”, “ignorant”, etc.) and a list of points that you disagree with. For some of those points you offer snide counterpoints, but you never argue directly.

                                            I would encourage you to engage discussions like this in a more respectful and productive manner by arguing your points directly, and avoiding the name-calling. Not just because I dislike the tone, but because I suspect you probably have some good points, but the way the comment is structured makes it difficult for me to evaluate them.

                                            1. 1

                                              Arguing directly against this kind of nonsense would take far too much time. Simple ridicule is both easier to write and more entertaining to read. You could write a paragraph about what’s wrong with any of the points I quoted above. I did, about one of them. Then I deleted it, because who will read that stuff?

                                              If we want people to get the point, the way to do that is not, probably, by rebutting essays that manage to go off into the weeds within the first paragraph and then blather for pages about nonsense; it’s by explaining the point, clearly and enjoyably, with good examples and careful reasoning. If someone starts out by saying that Bolivia is in Africa, it’s no use debating them about the pros and cons of legal coca cultivation there. Anybody with half a clue has already tuned out. The best you can do is make fun of them.

                                          2. 2

                                            Why not get the best of both worlds….

                                            https://yow.eventer.com/yow-2014-1222/cool-things-about-d-why-and-how-we-use-it-at-facebook-by-andrei-alexandrescu-1741

                                            D allows you to impose OO and FP disciplines where appropriate.

                                            1. 2

                                              Makes sense to me.