1. 10
  1. 5

    You pass in a map with your own shape to represent a human, dog, or cat, and the function treats them all the same.

    This implicitly means that human, dog, cat, etc. all provide the same static data needed by the function, or have to have appropriate conversions available to make an appropriate map. I’m pretty sure spec covers this, but it’s an absolute pain when functions conflict on the format or names of data they expect. Just passing around maps can hurt function discoverability when you’re unsure if a particular function has been implemented.

    Would you rather have 1 sleep function for 100 different animal types, or 100 different animal classes with their own sleep method?

    I’d rather just let the class accept appropriate parameters and:

    • Add it to a Mixin or make a utility class method (Ruby)
    • Just make it a static method on a utility class (Java)
    • Make it a free function (C++)
    • Define it somewhere in a class at or near the base of the hierarchy.
    • Use the template method pattern to adjust behavior as needed if it’s part of a larger flow.

    There is an eternal debate around Object Oriented software vs Functional software, and there likely will never be a conclusion, however, this is why I choose Functional when possible.

    Eventually I will write up the several pages of lessons I’ve learned after being thrown in OOP and also into FP code. For now, this will have to suffice.

    I don’t feel like I have to choose. I get to use languages which support both ideas and try to simplify the thing I’m working on the best I can using both.

    OOP excels at creating small types with known transitions between their various states and associating easily discoverable behavior with that data (quaternion, stack, etc.). Using interfaces to define behavior (or traits) also allows type checkers to assist in discovering incorrect usage and allow multiple data types to be used similarly (yes, there are FP constructs for this like typeclasses). You can model systems directly as classes too, but doing so is a delicate balance.

    In the problems I deal with, OOP suffers when (1) people poorly define the boundaries of a system, and use these to create a suite of classes, (2) make tall inheritance hierarchies, or (3) needlessly create additional interfaces. In the poorly defined system, you end up with a constellation of classes and objects, all shuffling data back and forth and following control flow becomes incredibly difficult. With a tall inheritance hierarchy, you’re implicitly carrying around a lot of state like a backpack, while that method called near the base of the hierarchy is actually a virtual function re-implemented 3 or 4 different times within the hierarchy, but you have to know about these to understand the control flow and it’s jumping all over the place and you have to track it down. There’s just an incredible amount of implicit behavior and state going on. With a whole bunch of needless ISomething interfaces, it’s like sitting in a lecture where some high level theory is being discussed and you’re praying your professor will actually give a concrete example. You’re trying to track down a specific bug, but that ISomething it’s using here is a Baz, class Bar here, but, wait, there’s Foos sitting too, multiplied a half dozen times with multiple interfaces with convoluted runtime state held by each group. Every attempt to get to real behavior involves at least 4 or 5 layers of interfaces and the code feels like a house of mirrors.

    FP suffers when you have (a (b (c (d (e (f (g ... ))))) and you’re trying to understand what the state of the computation looks like at when e is called. f is returning some bizarre map, but noone wrote a /spec for it, so you’re not really sure what you’re getting because that same person wrote g, h, and i which feed it and also don’t have a /spec, and it’s using some convoluted piece of data you can’t readily replicate for some reason to try it out in a REPL. There’s a lot of implicit state in terms of the control flow as it emerges out of all of these chained function calls. The other trouble I have in FP is that I often find docs which are unapproachable. It seems like they were written by someone with the stated goal of showing off their higher education and you can’t understand half the verbiage (this is how I’ve learned fun things, like what “nota bene” means) or it includes a bunch of math which “is obvious”. I know this isn’t their intent, but I literally keep a notebook of definitions like I’m in school to be able to figure out what’s going on.

    1. 1


      But I like that you gave specific examples of what you would do in each language. I think we devs suffer of a disease that makes us to want to abstract everything. OOP or FP are not single well defined things, We could discuss languages and how easy is to write good software on it. It’s possible to wire bad code in every language. We should focus on principles not rules.

    2. 4

      TBH I don’t think this does a great job arguing for “functional” programming over OOP.

      And really, I don’t believe there’s even a need to choose between them. They combine well, and IMO the “best” design is often somewhere in the middle, using both, which most programming languages allow you to do.

      Unless I’m misunderstanding, the paragraphs discussing DSLs, APIs, and documentation seem completely orthogonal to functional vs object oriented.

      “In Clojure, we use maps, seqs, vectors, lists, a few other primitive data structures to do all of what a class previously did for us.”

      I’m not too familiar with Clojure, but I’m not sure this is true. Aren’t the aggregate types created with defrecord, defprotocol, and deftype, combined with multi-methods closer to what classes provide?

      “Would you rather have 1 sleep function for 100 different animal types, or 100 different animal classes with their own sleep method?”

      It seems to me that if a single sleep function is sufficient for all of the animal types, then one sleep method in the Animal base class should also be sufficient. No need to create a hundred overloads (in either paradigm) unless they’re specialized somehow.

      1. 3

        The Clojure community doesn’t use defrecord, defprotocol, and deftype very much (at least, not in my experience). Everyone just uses maps. Which comes with all the same drawbacks as “just using dicts/hashes” in Python or Ruby.

      2. 1

        That oldschool web page formatting!

        What’s the preferred “reader mode” plugin for Chrome nowadays?

        1. 3


          1. 1

            I resorted to w3m in a terminal. Seemed appropriate enough.

        Stories with similar links:

        1. Enterprise C# to Self Employed React and Blazor - followup authored by jee 2 years ago | 2 points | no comments
        2. 1 year on Hacker News authored by jee 2 years ago | -2 points | 4 comments
        3. Monte Πthon - Monte Carlo, Π, and 8 lines of Python authored by jee 2 years ago | 3 points | 2 comments
        4. Migrating from Enterprise C# to Self Employed Clojure & React authored by jee 2 years ago | 3 points | no comments