395 comments

[ 2.7 ms ] story [ 437 ms ] thread
I've done plenty of maintenance programming. Most OO involves abuse of the paradigm anyway.

Most people don't use inheritance to large AND good effect. Or they use it and create a mess. Deep hierarchies of inheritance need to be carefully designed or they become a mess. All the hiding means less clarity about what is happening when things go wrong.

That said, its a valid approach and can yield useful results. Especially when you can just add a thing to a list and simply call a method on it without having to bother to know what it is.

>Deep hierarchies of inheritance need to be carefully designed or they become a mess.

Deep hierarchies of anything need to be carefully designed.

I try to use the rule of after three levels of inheritance or abstraction then it needs to be a black box and then you can reset your count.
The flip side of my impromptu "rule" is that you can drop the "careful" part as long as you are comfortable with a little or a lot of mess. Obvious common sense lowers the tolerance. I wonder how others set their tolerance? My perspective is that most OO hoerarchies don't go too deep. I've seen 4-5 at rare extremes. Around 2-3 is common. I'm sure someone has encountered 10 or more. I shudder to think how well that turns out in practice.
the author is right of course, but in practice he's wrong.

I've been in a 500k loc codebase mostly in python. As things got complicated, instead of the couple of arguments seen in his example you need up with half a dozen or more, or variable argument lists, people passing callbacks with callbacks and all sorts of shit like that instead of just writing an object and context manager or two.

In a large codebase these rapidly become very difficult to reason about and are better structured as objects with a discrete set of operations.

(Author here) I used to think that, but I've since found myself working on a similar sized codebase where we use very little OO and none of these problems have manifested themselves. Avoiding large function signatures and passing higher order functions around takes a little discipline, but wrapping those things with a bit of OO doesn't make the root problems go away, it just encourages their proliferation. (To reiterate the article, I'm by and large talking application development as opposed to library development).
I found when I started programming I never used OOP. Then I used it too much. And then recently I use it incredibly sparingly. I think this is most people's experience.

However, there are certain situations where I cannot imagine working without OOP.

For example, GUI development. Surely nobody would want to do without having a Textbox, Button, inherit from a general Widget, and have all the methods like .enable(), .click(), and properties like enabled, events like on_click, etc.?

Similarly, a computer game, having an EnemyDemon inherit from Enemy, so that it has .kill(), .damage(), and properties for health, speed etc.?

I'd really like to know how the most anti-OOPers think situations like this should be handled? (I'm not arguing, genuinely interested)

At least regarding games, ECS is the popular flavor-of-the-month alternative to OOP as a design strategy. The main problem being that games often have a lot of special cases, which break the inheritance hierarchy quite quickly.

E.g. Defining a weapon > {sword, wand} hierarchy, with respective properties for melee and casting, and then defining a unique weapon spellsword which is capable of both melee and casting. You could inherit from weapon, and copy & paste sword/wand code, or inherit from sword/wand, and copy & paste the other, but the hierarchy is broken.

ECS would rather have you define [melee] and [casting] components, and then define a sword to have [melee], wand to have [casting] and spellsword to have [melee, casting]. So instead of representing the relationships as a tree of inheritance, you represent it as a graph of components (properties). And then you generically process any object with the melee tag, and any object with the casting tag, as needed.

And of course then you could trivially go and reach out across the hierarchies and toss [melee] onto your house object and wield your house like a sword -- I don't know why you'd want to do that, but the architecture is flexible enough to do so (perhaps to your detriment).

Dwarf Fortress probably has the best example of this: https://github.com/BenLubar/raws/blob/archive/objects/creatu...

That's probably more an example of "metadata-driven" but it's ultimately the same thing -- an entity in the game is defined by its components, and the job of the game engine is to simply drive those components through the simulation. That particular example has its metadata (e.g. aesthetics: [CREATURE_TILE:249][COLOR:2:0:0]), its capabilities (e.g. [AMPHIBIOUS][UNDERSWIM]) and its data (e.g. [PETVALUE:10][BODY_SIZE:0:0:200]).

And it even has inheritance :-)

    [CREATURE:TOAD_MAN]
       [COPY_TAGS_FROM:TOAD]
       [APPLY_CREATURE_VARIATION:ANIMAL_PERSON]
Note for readers who want to search for more: ECS is "Entity Component System"

And Eric Lippert has a fantastic series of blog posts where he also discusses this problem: https://ericlippert.com/2015/04/27/wizards-and-warriors-part...

exactly the post I was trying to remember when talking about spellswords :)

Those posts are also cool in that defining games as a set of rules that operate on things within it is a really neat mental model -- the program should basically look like a DnD rulebook, with statblocks and all.

Yeah, it's definitely among my favorite essays. Btw, your explanation was excellent as well!
Thanks for sharing that Dwarf Fortress file, I never thought about the structure for all those attributes.
I've been hearing about ECS for a decade so it's definitely more then flavor of the month. However the issue is that unfortunately Unreal/Unity are both OO first.
It's definitely been around but I think Unity's (never-finishing) ECS + Rust gamedev community's focus on it has really spiked its popularity/interest lately. Otherwise pretty much every recommendation/engine is OOP-based, with a few straggling extensions/libraries for ECS here and there.

No idea about usage in industry though, but it comes up randomly e.g blizzard: https://www.youtube.com/watch?v=W3aieHjyNvw

This has been a recognized problem for game devs for a while and recently Unity introduced DOTS to gain performance. From what I understand, doing traditional OOP means going through a lot of pointers to find information. If you put everything into arrays instead, you reduce the time spent looking up things.

https://unity.com/dots

IIRC, this is the textbook that unity pulls from if you want to learn more. https://www.dataorienteddesign.com/dodbook/

The way to really grasp ECS architecture is not to look too hard at the implementations(which are all making specific trade-offs) but to recognize where it resembles and deviates from relational database design. A real-time game can't afford the overhead of storing data in a 3NF schema, but it can design a custom structure that preserves some data integrity and decoupling properties while getting an optimized result for the common forms of query.

The behavioral aspects are subsumed in ECS to sum types, simple branching and locks on resources, where the OOP-embracing mode was to focus on language-level polymorphism and true "black-boxing". Since the assumed default mode of a game engine is global access to data and the separation of concerns is built around maintaining certain concurrency guarantees(the order in which entities are updated should have minimal impact on outcomes), ECS makes more sense at scale.

The implementation trade-off comes in when you start examining how dynamic you want the resulting system to be: You could generate an optimal static memory layout for a scene(with object pools used to allow dynamic quantities to some limit) or you could have a dynamic type system, in essence. The latter is more straightforward to feed into the edit-test iteration loop, but the former comes with all the benefits of static assumptions. Most ECS represents a point in the middle where things are componentized to a hardcoded schema.

isn't ECS a composition pattern on top of OO?
Yeah, it is basically OO with most of the classes having no parents (e.g. without inheritance, but still incapsulated, and polymorphic).
It's an alternative to inheritance, which is the defining part of traditional OO.
I’m not sure it is; the heart of OOP is encapsulation of data + methods — the object itself. (Or message-passing if you’re being kinky)

ECS is almost the opposite; the object in question is barely defined — rather it’s derived from the composition of its parts. Like a DB, there’s roughly no encapsulation at all; just relations defined where, should you put them all together properly, you get the “object” back.

That is, in OO a particular object is composed of certain properties and capabilities. In ECS, if certain properties and capabilities exist, we call it a particular object.

Implementation-wise, the main difference seems to be the SoA vs AoS argument

But there’s also things like unity’s ECS deviating from rust’s ECS definition — I believe in Unity, components (data) and systems (behavior) are coupled, which fits better towards the OO world (components are the object of question). In rust ECS they’re decoupled (which unity is moving to, someday) and you’re really just dealing with structs and functions. And there’s a whole thing about the “true” ECS definition but it doesn’t really matter.

I've described an ECS to my friend as a combination Strategy pattern and Composition, so you can have a huge list of "traits/components/behaviours" and then build your object hierarchies (and their behaviours) from that, instead of from Inheritance. The way most game editors are made, when you configure things using the ECS, it visually looks like inheritance, but under the hood it is more like Composition. Once you go down the rabbit hole, you would see that and ECS/Strategy pattern & Composition makes more sense (and way easier to cater to exceptions) than plain old inheritance.

I want dare say that the enterprisey-version of an ECS would be the Onion Architecture that's been floating around in Java/C# camps. Not 100% match but they feel the same to me.

Obviously for normal GUI programming (at the outer layer that we see at least), inheritance is still king.

> E.g. Defining a weapon > {sword, wand} hierarchy, with respective properties for melee and casting, and then defining a unique weapon spellsword which is capable of both melee and casting. You could inherit from weapon, and copy & paste sword/wand code, or inherit from sword/wand, and copy & paste the other, but the hierarchy is broken.

Or, you could, in OO Python:

  class SpellSword(Sword, Wand):
    ...
or, possibly even:

  class Melee:

  class Casting:

  class Sword(Melee):

  class Wand(Casting):

  class SpellSword(Melee, Casting):
> So instead of representing the relationships as a tree of inheritance, you represent it as a graph of components (properties).

Multiple inheritance also represents relationships as a graph of properties.

If we step into the haskell land of monads having explicit functionality kept in individual monads with their own instances would be one way to segment this type of stuff. Something vaguely written as below would let you run actions where anyone can move or is an enemy, and default implementations can be provided as well.

    data Demon = { ... }

    data Action
      = Dead
      | KnockedBack
      | Polymorphed

    class Character a where
      health :: Int

    class (Character a) => Movement a where
      speed :: Int

    class (Character a) => Enemy a where
      kill :: a -> Action
      damage :: a -> Action

    instance Character Demon where
      health = 30

    instance Movement Demon where
      speed = 5

    instance Enemy Demon where
      kill _ = _
      damage _ = _

https://soupi.github.io/rfc/pfgames/ is a talk going through an experience building a game in a pure fp way with Haskell and how they modelled certain aspects of game dev. Most of the code examples are when you press down in the slides.
the patterns can still be completely the same without OOP - pairing data and functions that operate on said data. enemy.damage() and Enemy::damage(enemy) are functionally and semantically equivalent. But in the latter case (where you separate data and code) you don't need to worry about object assembly/IoC, how to pass a reference to A all the way through the object graph to object B, composition over inheritance becomes the default (at least in my experience using TypeScript interfaces, YMMV with other languages). The benefits of OOP, primarily state encapsulation, stop looking like benefits when it turns out your state boundaries weren't quite right.

Of course I'm biased as I went through the same "procedural => OOP => case-by-case" learning curve as the GP. But I ended up spending a lot of time trying to satisfy vague rules when using OOP - with procedural/functional programming with schema'd data, I get to spend a lot more time on what I actually want to do. Not worrying about SRP, SOLID, object assembly, how to fix my object graph now that A needs to know about B, and so on.

> how to pass a reference to A all the way through the object graph to object B

you just end up replacing the object graph by the call graph, which makes all the business logic much messier as now every function call takes a "context" argument

That's not been true in my experience - what you do end up with is a global data structure, which is the shared state for all or most non-ephemeral top level concerns. Aside from recursive functions I find call stacks tend to be quite short.
(comment deleted)
> Similarly, a computer game, having an EnemyDemon inherit from Enemy, so that it has .kill(), .damage(), and properties for health, speed etc.?

I'm not a game developer in the slightest but as a gamer and developer I've often thought about similar things a little bit.

In another example, let's say you were playing a game like Diablo II / Path of Exile where you have items that could drop with random properties. Both of those games support the idea of "legacy" items. The basic idea is the developers might have allowed some armor to drop with a range of +150-300% defense in version 1.0 of the game but then in 1.1 decided to nerf the item by reducing its range to +150-200% defense.

Instead of going back and modifying all 1.0 versions of the item to fit the new restrictions, the game keeps the old 1.0 item around as its own entity. It has the same visible name to the player but the legacy version has the higher stats. Newer versions of the item that drop will adhere to the new 1.1 stat range.

That made me think that they are probably not using a highly normalized + OOP approach to generate items. I have a hunch every item is very denormalized and maybe even exists as its own individual entity with a set of stats associated to it based on whenever it happened to be generated. Sort of like an invoice item in a traditional web app. You wouldn't store a foreign key reference to the price of the item in the invoice because that might change. Instead you would store the price at the time of the transaction.

I guess this isn't quite OOP vs not OOP but it sort of maybe is to some degree.

I'd be curious if any game devs in the ARPG genre post here. How do you deal with such high amounts of stat variance, legacy attribute persistence, etc.?

> I cannot imagine working without OOP ... in GUI development

What is React but essentially a (wildly popular) functional GUI framework?

But react isn’t the widget library
Is this meaningful? You can define a widget library easily in React and it's capable of representing hierarchical GUIs in a way that's not OO
React with hooks offers one decent paradigm for this. I would definitely class it as an area that doesn't yet have a canonical settled solution yet.
I completely agree with you when it comes to UI although the key here is that OOP is syntactic sugar, it shouldn't be the overarching pattern. I think of it as augmenting types, or prototype-based OO.

And when it comes to games, nope; entity patterns with composable behaviours added to dumb objects is far more productive that traditional OOP, as you need many objects with slightly different behaviours/abilities/types. Composition over inheritance is key here.

I used to think this too. And I have to admit OO does lend itself well to GUI and hierarchical structures (IMO probably the only case I think it is actually useful). But that's not to say that there aren't declarative methods that are equally nice to use, Elm has done a lot to popularize this style of coding GUIs.

example in Haskell, https://owickstrom.github.io/gi-gtk-declarative/app-simple/ or even take a look at yew in Rust which is also elm-style, https://github.com/yewstack/yew/blob/master/examples/counter...

There are a bunch of FP GUI development styles without OO. The Elm style[1], which propagated to a whole family tree of similarly structured system, and similar ways in Reagent based apps in the ClojureScript world.

https://guide.elm-lang.org/architecture/

The answer is simple: Traits

The weakness of OOP structurally stems almost entirely from inheritance, which I think is very poor construct for most complex programs.

How should the widget situation be handled? Well, what is a widget? It's hard to define, because it's a poor abstraction.

Maybe all your "widgets" should be hide-able, so you implement a `.hide()` and `.show()` method on `Widget`. Oh, and all your widgets are clickable, so let's implement a `.click()` method.

Oh wait.. but this widget `some-unclickable-overlay` is not clickable, so let's build a `ClickableWidget` and `ClickableWidget` will extend `Widget`. Boom, you're already on your way to `AbstractBeanFactory`.

We got inheritance because it's an easy concept to sell. However, what if we talked about code re-use in terms of traits instead of fixed hierarchies?

So, our `some-unclickable-overlay` implements Hideable. Button implements Hideable, Clickable. We have common combination of these traits we'd like to bundle together into a default implementation? Great, create super trait which "inherits" from multiple traits.

Rust uses such a system. They don't have classes at all. Once you use a trait system, the whole OOP discussion becomes very obvious IMO.

1. Shared state can be bad, avoid if possible

2. Inheritance is a poor construct, use traits, interfaces, and composition instead.

3. Don't obsess about DRY and build poor abstractions. A poor abstraction is often more costly than some duplicated code.

4. Use classes if they're the best tool in your environment to bundle up some context together and pass it around, otherwise don't

This may be a bit pedantic, but OOP != inheritance. Inheritance is the worst part of OOP. OOP is also modules and namespaces and encapsulation, all of which are great.
How are modules and namespaces OOP?
To me OOP is about separation of concerns, abstract data types, information hiding. Modules/namespaces support decomposition the same way that objects do, and historically they became popular because of and in tandem with object-oriented languages. So it's true as others have pointed out that they can and do exist in other paradigms; I would argue that is evidence of the influence of OOP and the fact that its best ideas have been incorporated all over the place.
Those are just good programming principles in general though. Abstract data types, information-hiding, and separation-of-concerns were being done in C well before OOP was a thing. Granted, the information-hiding wasn't as sophisticated as it got once OOP was introduced, but I don't think it makes sense to attribute information-hiding to OOP as a result, especially since newer programming language have used visibility attributes in the absence of OOP classes.
By and large, I think inheritance is a major mistake. Interfaces are the only "good" part of inheritance.

Sure, there are edge cases where inheritance is truly the best way to do things, but those are few and far between.

I cringe every time I dive into some of my day job's more deeply inheritance based code. It is SO hard to make changes there without breaking a bunch of stuff unintentionally.

The way I'd put it, the mistake was to make inheritance the default. It's a sometimes useful mechanism (called "open recursion" in the FP world).
Ironically, in Python you can use interfaces without ever resorting to inheritance. :)
Truly a description of dynamic typing :) Interfaces without declaration.
I didn't say that they were not declared. You can have proper, type-checked and runtime enforceable interfaces, without having to inherit from them.
Agreed. Inheritance CAN be very useful but I would consider it an advanced feature for special cases. It’s definitely not something that should be taught to beginners.
Things which all exist in non-OOP languages and before OOP became a fad. No OO isn't inheritance, but implementation inheritance is one of the few things which is unique to OOP based programming. Most other aspects of OOP will be found within other paradigms.

More importantly OOP is not merely a bag of syntax and features but a way of thinking about software development and structuring your programs. OOP says you organize your code around objects and the actions done on those objects.

That is something I find that often isn't a great way of structuring your code. But I don't think OOP is useless. I do use OO thinking in my code, just not as much as I used to. I prefer functional thinking. Often I organize code arounds verbs rather than nouns. So one file may be a similar kind of action performed on many different kinds of objects.

E.g. when writing a rocker simulator, I would have one source code file which contained mass calculations for a variety of objects.

Another file would contain rocket thrust calculations.

I would say though that in GUI programming I find that OO thinking tends to make a lot of sense.

I don't think modules or namespaces really count as OOP. And encapsulation can exist in the absence of OOP if the privacy barrier exists at (eg) the module level instead of the class level.

I'd argue that inheritance is really the core of OOP (and by extension methods, which are only different from functions insofar as they interact with inheritance).

The example given in the article is not compelling -- there are only two arguments in play, so passing two arguments directly instead of passing an object holding those two things isn't painful.

To me the big win of classes isn't inheritance, it is that functions and related data live in a common scope.

If you write your code as a bag of data structures and a bag of functions that you pass those ad-hoc data structures to, it is less clear which things play together.

Not only that, but the second example is object-oriented, he's just doing it "by hand" rather than taking advantage of the built-in language syntax (which, in spite of his insistence to the contrary, does make it clearer what's actually going on).
I would say that is simply down you you not being experienced with writing code this way.

Once you begin writing functional code more regularly, you will get much the opposite way of thinking. Today I find it frustrating that I need an object first. In my mind, I already know what action I want to do. I know what function I want to call. I start with that and look at what arguments it takes in.

OOP programming is frustrating now, because instead of going straight for the function I have to locate the appropriate object first. That is like a big detour.

> If you write your code as a bag of data structures and a bag of functions that you pass those ad-hoc data structures to, it is less clear which things play together.

Noting suggest you need to write code like that. People who write functional code tend to have a lot of well defined data types. I use well defined data types in Julia. Haskell and OCaml developers use well defined data types. We are not using using dictionaries or something.

This is exactly how I felt after learning Elixir and then coming back to Python. Object orchestration is painful.
This post says more about the author's experience than Python, especially with the list of exceptions. The common pattern behind those exceptions is that they're not as simple as the examples, which gets to a better lesson that OO is not pointless but that you should use it cautiously when you know that your problem domain has a sufficiently complex combination of state and code. Anything is going to seem simpler when you have 2 variables and the whole thing is 20 lines and while it's definitely useful to pause before accreting complexity it's also important to remember that projects and teams come in many different forms and there's no guarantee that something which works for you on a project now will be the best option for someone else with different needs.
Hi, I think you're correct in some domains - I probably should have spelt out that I'm by-and-large talking about application development, where if you have a "sufficiently complex combination of state and code" you probably have a problem rather than a candidate for wrapping in a class.
> where if you have a "sufficiently complex combination of state and code" you probably have a problem rather than a candidate for wrapping in a class

Ironically, I think most people in that situation would agree with you that they "probably have a problem" -- the difference is that they see the problem as the important thing to focus investing resources into solving whereas you see its existence as an inherently poisoned entity that must be conceptually purified. While both parties would probably agree on the existence of /a/ problem, the definition of what needs to be addressed and how is likely to differ, and I can't say that the latter attitude is the norm at any high productivity engineering organization I've ever worked at, built, or encountered.

> I probably should have spelt out that I'm by-and-large talking about application development

I was as well. My point is just that not everyone works on the same things or has the same resources, and there is no global optimum. I'm generally in the “more functions, fewer classes, minimize inheritance” camp personally but the most important underlying principal is remembering that methodology is a tool which is supposed to help, not hinder, and adjusting based on local conditions.

Part of the value an experienced developer brings should be having a good sense for when a particular approach is a good fit for the problem at hand and being willing to say that whatever you're doing needs changes. This can be tricky because methodologies often have a quasi religious aspect where people take the decisions very personally but the worst project outcomes I've seen have been from cases where people treated the status quo as holy writ and refused to consider how problems could be avoided or transformed by picking something different.

A decent rule of thumb I sometimes apply is:

1. What I have could be formalized into a state machine. 2. That state machine needs to be reused and re-entered. 3. I want to apply inputs to the state machine with method calls.

Of course you can overapply the thought and end up with an enterprise architecture - that's why YAGNI is important. But when a section builds up a bit of conceptual redundancy there's usually a state machine that can be pulled out of it in OO form.

And I would certainly do it that way in Python, as well as other languages.

Hi, what I'd really like is to test my conjecture - would you (or anyone reading) be able to email me a (< 500 line, ideally not super-deep-library-code) bit of code (constructed or otherwise) that you think is "so innately OO friendly that the conjecture won't hold".

Given some examples, I'll do a follow-up blog post.

One good use for Python's OOP is operator overloading. Since Python doesn't have type signatures, you can't overload the "plus" operator with a free function like you can in C++. Writing numerical code without operator overloading is painful.
I'd argue that's not object-oriented programming.

You are using Python's class mechanism, but that's where the OOness ends. The types themselves are immutable value types, carrying no state and reacting to no messages, and they don't exploit a class hierarchy beyond having a common "Number" class. The "methods" don't even behave like methods, e.g. __add__(self, other) has special wiring so it obscures which side of the addition is "self".

That's very different than a classic OO scheme like a UI toolkit.

Why not have the client stored in a dictionary, it's just a url location, instead of having a dataclass? You don't even need a "construct_url" function.
OOP is meant for re-use across different projects. Frameworks, or self-contained classes.
In general, object orientation is a reasonably elegant way of binding together a compound data type and functions that operate on that data type. Let us accept this at least, and be happy to use it if we want to! It is useful.

What are emphatically not pretty or useful are Python’s leading underscores to loosely enforce encapsulation. Ugh. I’d sooner use camelCase.

Nor do I find charming the belligerent lack of any magical syntactic sugar for `self`. Does Python force you to pass it as an argument to make some kind of clever point? Are there psychotic devs out there who call it something other than `self`? Yuck!

And why are some classes (int, str, float) allowed to be lower case but when I try to join that club I draw the ire from the linters? The arrogance!

...but I still adore Python. People call these things imperfections but it’s just who we are.

PS I liked the Python5 joke a lot.

PEP 20 -- The Zen of Python [0] ... "Explicit is better than implicit."

In the case of the unnecessarily repetitious `self` it means to violate DRY, make the mundane manual - and therefore error prone - and tedious.

[0] https://www.python.org/dev/peps/pep-0020/

> In general, object orientation is a reasonably elegant way of binding together a compound data type and functions that operate on that data type. Let us accept this at least, and be happy to use it if we want to!

Is it elegant? OO couples data types to the functions that operate on them. After years of working on production OO I've still never come across a scenario where I wouldn't have been equally or better served by a module system that lets me co-locate the type with the most common operations on that type with all the auto-complete I want:

  //type
  MyModule {
    type t = ...
  
    func foo = ...
    func bar = ...
  }

If I want to make use of the type without futzing around with the module, I just grab it and write my own function
Totally. That structure also allows for multiple dispatch, which makes generic programming much much more pleasant than OO (which is basically single dispatch). Eg. See Julia.

To elaborate, tying methods with the individual/first argument makes it very difficult to model interactions of multiple objects.

> tying methods with the individual/first argument makes it very difficult to model interactions of multiple objects.

The best example of this in Python is operator overloading. We need to define both `__add__` and `__radd__` methods to overload a single operator. Even then, there are situations where Python will call one of those where the other would be better.

How do you compensate for the lack of type-dependent name resolution? MyModule.foo(my_t) seems verbose, compared to my_t.foo()
FWIW this is how Elixir works. You just do MyModule.foo.
If anything, this is an aid to type-checking. Since MyModule.foo takes only things of type t, the type-checker's job is extremely easy and it will help you a lot more in cases when your program is incomplete.

So often when using C#-style fluent APIs I find that I'm completely on my own and have to turn a half-written line into something syntactically correct before Intellisense gives me anything useful. Using an F#-style MyModule.foo, the compiler can tell me everything.

Most of the time you have:

- short names inside modules. I.e. you might have a function called Foo.merge(...) instead of x.merge_with_foo(...)

- a way to bring modules into scope so you don’t need to specify the name

- not using that many modules. Most lines of code won’t have more than one or two function calls so it shouldn’t matter that much (other techniques can be used in complicated situations)

The key advantage of type-dependant name resolution is in using the same names for different types. You might want to write code like foo.map(...) and it is ok if you don’t know the exact type of foo. With modules you may need to know whether to call SimpleFoo.map or CompoundFoo.map.

(comment deleted)
Isn't that just reinventing classes?
No, a class is a data type coupled to functions operating on that class. For the purpose of this discussion a module is a way to semantically group types and functions without coupling them (languages like OCaml get fancy by actually typing modules which enables some neat ideas, but is not important to this discussion).
It's the good part of classes (namespacing) without the bad part (inheritance).
Doesn't seem like it necessarily stops inheritance. You need to throw away method references or you're just back to open faced classes.

You can still write new methods that blow away any assumptions previously made about type state,causing bugs. It seems like it could be even worse seeing as you possibly have less access controls for fields.

With the typing system of Haskell, maybe Rust too, you can do a lot to prevent the type from being able to represent wrong state. Immutability also does a lot to prevent issues with false state and public fields.

If you want to prevent people outside the module from adding functions, you can do that in Haskell by only exporting the type-name. This prevents other modules from actually accessing any data inside the type, whilst still allowing them to use the type in functions. This includes sometimes making getter functions to read values from the type. I'm pretty sure Rust allows for something similar.

In general, FP still allows for encapsulation. Moreover, it uses immutability and to some extend the type-system to prevent illegal states of data types.

I agree. Also, here are two things I find not great with OO style apis:

1. Functions with two arguments of your type (or operators). Maybe I want to do Rational.(a + b), or Int.gcd x y, or Set.union s t. In a Java style api I think it looks ugly to write a.add(b) or whatever.

2. Mutability can be hidden. If you see a method of an object that returns a value of the same type, you can’t really know whether it is returning a new value of the same type or if it is mutating the type but returning itself to offer you a convenient chaining api. With modules there isn’t so much point in chaining so that case may be more easily hidden.

I'm sure it enables other things, but I find the "Module.t" thing very inelegant. Names should be meaningful and distinguish the named object from other things that could go in the same place, but "t" is meaningless and there's often nothing else (no other types anyway) for it to distinguish itself from. It feels like a hack, and I much prefer the mainstream dot notation.
Whats the advantage other than some intellisense clean up through hiding some functions? As you're well aware, you can write new functions of OO types as well.

Either you lose private/protected state and the advantages or you replace them with module visible state and the disadvantages.

(comment deleted)
> Are there psychotic devs out there who call it something other than `self`? Yuck!

I was converting some old Java code to Python recently and I almost decided to switch to `this` just to make the task less repetitive. Luckily, sanity returned after I saw the first def abc(this,

> Nor do I find charming the belligerent lack of any magical syntactic sugar for `self`. Does Python force you to pass it as an argument to make some kind of clever point? Are there psychotic devs out there who call it something other than `self`? Yuck!

If you weren't an ignoramous you would understand it can be something else, like klass.

You spelled it wrong!

Ruby’s approach is the most delightful, I find. elf is an instance of Elf and in elf.health, self is the elf.

Elf is an instance of Class and in Elf.surpass, self is the Elf class itself.

Ruby's irregular syntax drives me crazy. But it is beautiful.
I gotta say as a Python developer I kinda like this compared to thing = Thing()
I'd personally contest the notion that binding state and functionality directly together is "reasonably elegant" in the first place.

But ignoring that, it depends on the flavor of object orientation. Yes, the most mainstream style bundles state directly with functionality but not all do. But for instance the CLOS family of OOP maintains separate state and functionality and one binds desired functionality to those classes which should have it. This is not too dissimilar from typeclasses IMO.

It makes a lot of sense for modeling real world objects that can do things and have state.
Which makes a lot of sense for the situations where modeling real world objects that can do things and have state makes a lot of sense. Which isn't as often as the OOP advocates would have one believe.
> Are there psychotic devs out there who call it something other than `self`? Yuck!

I have, under duress. It was a result of using syntactic sugar wrapping a PHP website to make a Python API; it was convenient to pass form variables into a generic handler method of a class. The problem? The website, fully outside of my control, had a page which had an input named "self" which resulted in two values for that argument. Rather than refactor the class and dozens of scripts that depended on it, I renamed 'self' to 's_lf' in that one function and called it a day.

Also, python has class methods. The convention is to use 'cls' in that context, to avoid confusing the parameter (a class) with an instance of that class.

for my own code, I use 'me' instead of 'self'. Just makes more sense to me; e.g. me.variable that is, the one referenced right here ('me') is being referenced, not from anywhere else. It's also shorter; and I think more descriptive. But that's just me.
me.variable sounds Irish to me, at least compared to my.variable.
And my.variable sounds Perlish.
That's because Perl was intended (Larry Wall was trained as a Linguist) to somewhat mimic natural English.
That's a fascinating tidbit. You wouldn't exactly guess it from reading Perl.
>Larry Wall was trained as a Linguist

Having an undergraduate degree doesn't make somebody a linguist. Let me know when he has contributed to the field through scholarly work. Doing Bible translations doesn't count.

> me.variable sounds Irish to me

Only if you're used to the derogatory, clichéd "American TV" version of Irish.

Except the binding always implies the object is mutated on changes, which make any form of reasoning or concurrent programming difficult.
Why? There are plenty of OO libraries where method calls do not change the object.
There is but it's mostly not how OO programs are built. And even assuming you have most of the core of your program behaving properly and clearly with non-mutable objects. What do you gain from having objects at this point?
You can always mutate objects using functions that return new objects.

If you use methods of an instance you know the object's state may or may not change. If you set attributes, you know for sure they changed. If you use a function that returns a new objects you know you paid the object allocation tax.

I should have written almost always.

You can but it's not common. It's like saying you can write functional programing in C. You can but the whole ecosystem is not doing it.

If you set attributes, you know for sure that they changed at this particular place in the program but later in another position in the program, can you reason about the value of the object? You don't even who mutated it. And don't tell me "you can get its state", I'm talking about reasoning about the program here.

There is a reason why concurrent programming is more difficult than necessary with objects.

My point is that this implicit management of the state in "OOP" brings a lot of problems

If I were to design an idiot-proof OOP model that takes this into account, I'd force sequential processing of mutating methods - if the running method starts a mutation and there is another method doing the same, we roll it back until the previous method finishes.

Performance would suck, but that's life. You can't have your cake and eat it too.

Just to discuss one point:

> And why are some classes (int, str, float) allowed to be lower case

Also, boolean. These are primitive data types. For instance, in Java there's a difference between int and Integer. I'd assume that Python special-cases these because they are primitive. But I haven't been through the Python internals, so it's only a guess.

They are far less primitive than Java's - they are classes too, but can't be extended.
(comment deleted)
I'm not sure which language did you mean there, but in Python those classes can be extended.

Also, the main reasons for special cases are historical; also, they predate PEP8 by a long time.

Sorry. That's something that was true in 2.x but not anymore. There used to be UserString, UserList, UserDict classes for serving as base classes. We still have those, but, at least now, we can extend the fundamental classes directly.

They are a bit more convenient than the built-ins.

We can inherit from `list`, `dict` etc., but objects of those classes are still not as flexible as "normal" objects. For example, we can't add an attribute to a list using `l = []; l.a = 1`.
I believe that makes them the same as any other class defined with `__slots__`:

    >>> class Foo:
    ...   __slots__ = ('a', 'b')
    ...
    >>> f = Foo()
    >>> f.a = 1
    >>> f.b = 2
    >>> f.c = 3
        Traceback (most recent call last):
          File "<stdin>", line 1, in <module>
        AttributeError: 'Foo' object has no attribute 'c'
It's just for historical/ backwards compatibility reasons. These types have been part of Python from before it had classes. Today, they are classes and can be subclassed, etc, but too much code relies on their existing names to change them to match the modern convention.
> What are emphatically not pretty or useful are Python’s leading underscores to loosely enforce encapsulation.

Python will not prevent anyone from doing something dumb. It'll just force them to acknowledge that by forcing them to use a convention. As a library writer, I'm free to change or remove anything that starts with an underscore because, if someone else is depending on it, frankly, they had it coming. I can assume everyone who uses my library is a responsible adult and I can treat them as that.

> And why are some classes (int, str, float) allowed to be lower case

Because they are part of the language like `else` or `def`. Only Guido can do that. ;-)

> As a library writer, I'm free to change or remove anything that starts with an underscore because, if someone else is depending on it, frankly, they had it coming. I can assume everyone who uses my library is a responsible adult and I can treat them as that.

Totally agree, as an user, I feel anxious whenever I have to use underscored names. It's great that Python still allows me to do it and there were few times when it was useful, but when it stops working I know it's 100% on me.

If we look in `collections`, we can see `defaultdict` sitting side-by-side with `OrderedDict` and `Counter`. I've long since resigned myself to the fact that naming consistency is not Python's strong suit :)
The one that bugs me the most is:

    from datetime import datetime
> I can assume everyone who uses my library is a responsible adult and I can treat them as that.

What is great about python is that it acknowledges that even adults can forget things. Underscores are an affordance.

> Nor do I find charming the belligerent lack of any magical syntactic sugar for `self`.

And what would that look like exactly? The Java-like approach, with any non-declared variables being the current object's attributes, wouldn't work because Python doesn't use declarations and can have named objects in any scope. The alternative is to use a "magic" variable appearing out of thin air, like Javascript's "this" -- but this is basically what "self" does, only explicitly.

Any other ideas?

C++ et al's "this" keyword seems perfectly reasonable to me--the whole point of being a member method is that you have some sort of implicit state. Otherwise, why not just make them top-level functions, if you have to take the object as a parameter anyway?
It seems to me that you're conflating two unrelated things. Yes, I agree -- there are perfectly legitimate reasons to implement module level functions instead of class and instance methods; but avoiding typing four extra characters just to be more like some other arbitrary language is not one of them.

Python has a different "philosophy" than "C++ et al". Literally everything is an object, and everything has attributes; some of those attributes are objects that implement the Callable protocol. And it's a syntactic sugar that some of those callables are implicitly passed certain predefined arguments; the default is passing the instance of the object itself. The exact same thing could have been implemented without it, like this:

    foo.some_callable_attribute(foo, another_argument)
Since methods are usually called more often than they are declared, it makes sense to make this first argument the default.
Conversely, I think the beauty is that they _are_ the same as top-level functions, just in a special namespace. This means that you can call "instance methods" directly from a class, i.e. the following are equivalent:

    (1) + 2
    (1).__add__(2)
    int.__add__(1, 2)
This comports with the "explicit is better than implicit" policy in Python. The only "magic" part is when dot-notation is used to call an instance method, which is just syntactic sugar. Another example of this philosophy is that operator overloading is simply reduced to implementing a method with a specific name.

I think a "magic" this keyword can create a lot of nasty edge cases that can be difficult to reason about; the way "this" is used in JavaScript is notoriously complex in ways that it might not be in a statically typed language like C++. What should "this" evaluate to outside of an instance method? What about in a class definition inside an instance method? What if an instance method is called directly instead of on an instance? All of these situations require making their own "rules", whereas in Python the correct answers can be easily reasoned about by starting from first principles.

Decorating with "@", as Ruby does? I.e., "a = 42" assigns to a local variable "a", and "@a = 42" assigns to the instance's field "a". Clearly visible, and no magic variables.
Python OO is a bolt on. That's why it doesn't have syntactical support for magic functions or self. Nothing prevents you from implementing your own alternate object system. Python 2 had two of them.

As for the linters, just disable the rules you don't like.

> What are emphatically not pretty or useful are Python’s leading underscores to loosely enforce encapsulation.

IMO Go's use of upper/lower case is even worse than Python's use of underscores. What would be better? Explicitly labeling everything as `public` or `private`, C++/Java style?

> Does Python force you to pass [self] as an argument to make some kind of clever point?

I think the idea is that `a.foo(b)` should act similarly to `type(a).foo(a, b)`.

However, what happens when you define a method and miss off the `self` parameter is crazy. Inside a class, surely `def f(a, b): print(a, b)` should be a method that can be called with two arguments.

> And why are some classes (int, str, float) allowed to be lower case

Historical reasons. AFAIK in early versions of Python these weren't classes at all - the built-in types were completely separate.

> However, what happens when you define a method and miss off the `self` parameter is crazy.

@staticmethod

(although why you want this instead of just a module scope function is unclear). If you want this strange behavior, you should be explicit about it.

Python's actual behavior in this case is far stranger than static methods, which are a standard feature of many object-oriented languages (albeit one that's fairly redundant in a language that allows free functions).
I'm not sure what you're saying here.

If you want `def foo(a, b)` inside a class to be a static method, you need to define it as

    class Foo:
        @staticmethod
        def foo(a, b):
            ...
Now `f = Foo(); f.foo(1, 2)` will work.

However, you need to be explicit about this because it is much more common for this situation to arise because someone forgot a self somewhere than for it to have been intentional. Requiring @staticmethod serves to signal to humans that this odd behavior is intentional (but again I'd say that, stylistically, if you're using a staticmethod, just move it out of the class and into the module namespace, module namespaces in python don't bite).

I just don't think the decorator should be required for static methods. If you write `def foo(a, b)` inside a class, the lack of `self` should be sufficient to make `foo` a static method.

IMO it would make sense for Python to look at `a, b` and assume you wanted a static method with two parameters. Instead it assumes you're using `a` instead of `self` and really wanted an instance method with one parameter.

This requires blessing "self" as special (like this in java etc.). Python made a specific choice not to do that (and there are cases where you don't want to use "self", such as metaclasses, classmethods, and (rarely) nested classes that close over their parent class.

Like I said: most of the time, a missing `self` is an error by the user, and so python requires explicit notification both to the language, and to other people, that you're doing what you mean to do, and not just making a mistake.

I think that's the point: requiring the user to explicitly pass "self" rather than making it a language keyword mostly just gives the user an unnecessary opportunity to make mistakes.
Except in Pythonland, "explicit is better than implicit"

https://www.python.org/dev/peps/pep-0020/

Wouldn't it be more explicit if there was a designated keyword that objects could use to access their own members instead of implicitly assuming that any first argument to a method is a reference to them, only called 'self' by convention?
> Wouldn't it be more explicit if there was a designated keyword that objects could use to access their own members

Not really. You end up implicitly injecting some value into local namespaces instead of having a function that takes an argument.

The implicitness of methods working differently than normal functions > the implicitness of classes passing a first argument to methods.

Should super then also be a method argument?
Honestly, I think it would have made some amount of sense to make super a method defined on the base object so that

    def __init__(self, x, y):
        self.super(x, y)
would be the convention. There may be some infra issue with this (and in general, `super` has to be kind of magical no matter how you handle it). But yes, in general I can vibe with "super is too implicit".
Ruby generally (I think? I haven't seen much of Ruby code) uses "@" instead of "self.", and "@@" instead of "ParticularClassName." (btw, "self.__class__" doesn't cut it), and it seems to produce no namespace pollution.
> IMO Go's use of upper/lower case is even worse than Python's use of underscores.

Especially when serialising a struct to JSON - only public members are serialised, so your JSON ends up with a bunch of keys called Foo, so you have to override serialisation names on all the fields to get them lowercased.

> Are there psychotic devs out there who call it something other than `self`? Yuck!

Sometimes I like to overload operators using `def __add__(lhs, rhs)` and `def __radd__(rhs, lhs)`.

> Are there psychotic devs out there who call it something other than `self`?

I call it "me".

> Nor do I find charming the belligerent lack of any magical syntactic sugar for `self`. Does Python force you to pass it as an argument to make some kind of clever point?

On the class, you can call the method like a normal function (passing the self arg manually). Seems like a nice connection to just raw functions. Also explains how you can add methods after a class has been defined (set a function on it, whose first param is a class instance).

That’s a compelling point.

    zoo = [‘goat4’, ‘tiger7’]
    map(Pet.stroke, zoo)
Backwards compatibility. You remember all those folks complaining about Python 2 vs 3? Turns out even renaming Thread.isalive to is_alive caused a ruckus. Imagine how much yelling you'd hear if you changed float to Float.

I've come to like the self variable name convention. It takes so little effort to type and makes the behavior more clear. Well, some aspects of it, I suppose. I make a fair amount of use of unbound methods.

I think OO caught on because it encourages people to organize their thoughts, and good structure alone brings a tremendous improvement in code.

I think the reaction to it is more the "everything is mutable" approach most OO languages took, which led to things that looked organized but that were a hot mess of side-effects.

With dataclasses (or attrs) you can cut back on that by freezing everything, and still get the clarity of methods that lay out the essential functionality of a type.

In their own example, "get_items" and "save_items" make a bit more sense if they're stashed away in the Client namespace, and you can see that they're essential to what a Client does.

And while Oil's conjecture is probably right, I'm not sure it survives if you add the caveat, "in that same language."

For instance, Python doesn't have a native way of expressing sum types outside of inheritance. That's not to say inheritance, especially an always open style, is a good way of working with a sum type, it's just the only native way.

I think OOP caught on because of IDEs with auto-complete functionality, you type object dot and it offers all the method names for you.
OOP was around long before IDEs were that popular.
When was that?
Depends on how far back you want to go. The term "object oriented programming" was coined back in the 60s. Languages like Smalltalk were introduced in the 70s.
Check out pydantic. Best lib ever. I am a huge fan of attrs.
I found Python OO to be a super elegant and practical way to implement a common API for multiple databases.
One doesn't need OO for that tho, the module system applies just as well to solving that problem.
Module corresponds to a class, not to an object.

Afaik, you can't instantiate a module in python multiple times with different parameters.

In theory you would like to do this, but it is not supported in python, therefore you would use classes as poor man's modules.

    conn = import Database(host=..., user=...)
This is - as the kids put it - a very hot take.
One will appreciate OO once he/she saw a 2000-line code in a single file.
You don't need to use classes to break a big file into several smaller files.
OOP also works well when you want different _behavior_. In your bag of functions you have the same behavior for all objects. If you want different behavior, you're going to have to start adding branches in code that really has no business making those branch decisions.
Nonsense. Everything been a dictionary is incredibly important.
I disagree that it's mostly pointless because if you're practicing Test-driven development OO provides a great way to abstract agents and their actions.

I've seen a lot of python code that looks like the example from the blog but that's simply just a narrow example and does not reflect all the potential of OO, not only in Python but in any programming language.

The thing I see in common in such poorly designed OO code is that it is usually not covered with unit tests that are actually useful and informative. Sometimes it's just not even tested at all.

At the end it comes down to how the developer designs their software, as Fred Brooks argued in "No Silver Bullet" (https://en.wikipedia.org/wiki/No_Silver_Bullet)

Brooks goes on to argue that there is a difference between "good" designers and "great" designers. He postulates that as programming is a creative process, some designers are inherently better than others. He suggests that there is as much as a tenfold difference between an ordinary designer and a great one. He then advocates treating star designers equally well as star managers, providing them not just with equal remuneration, but also all the perks of higher status: large office, staff, travel funds, etc.

Section from "No Silver Bullet"

Object-oriented programming.

Many students of the art hold out more hope for object-oriented programming than for any of the other technical fads of the day.

I am among them.

Mark Sherman of Dartmouth notes that we must be careful to distin- guish two separate ideas that go under that name: abstract data types and hierarchical types, also called classes.

The concept of the abstract data type is that an object's type should be defined by a name, a set of proper values, and a set of proper operations, rather than its storage structure, which should be hidden.

Examples are Ada packages (with private types) or Modula's modules.

Hierarchical types, such as Simula-67's classes, allow the definition of general interfaces that can be further refined by providing subordinate types.

The two concepts are orthogonal — there may be hierarchies without hiding and hiding without hierarchies.

Both concepts represent real advances in the art of building software.

Each removes one more accidental difficulty from the process, allowing the designer to express the essence of his design without having to express large amounts of syntactic material that add no new information content.

For both abstract types and hierarchical types, the result is to remove a higher-order sort of accidental difficulty and allow a higher-order expression of design.

Nevertheless, such advances can do no more than to remove all the accidental difficulties from the expression of the design.

The complexity of the design itself is essential; and such attacks make no change whatever in that.

An order-of-magnitude gain can be made by object-oriented programming only if the unnecessary underbrush of type specification remaining today in our programming language is itself responsible for nine- tenths of the work involved in designing a program product.

I doubt it.

I write a lot of Python code. Objects are absent in most of what I write. But the 10% or so of the time I use objects, it is because objects are the best way I can think of to structure the program.

So I don't think objects in Python are useless at all. I think Python is designed in a way that allows them to be used effectively if desired, or avoided all together when there's no apparent benefit.

I maintain a couple of Django projects for a customer of mine. I wrote almost 50% of the code base. Those two projects use very few classes, probably only the models. Everything else is a bag of functions organized in modules.

It's very similar to an Elixir /Phoenix project I'm working on for another customer. Modules and functions and a couple of dozens of GenServers that act as objects to maintain status where we need it.

And yet, I easily follow the OO model of Ruby of Rails. I feel it right in that context, probably because the rails of RoR are more well defined than the ones of Django. Of course when I script in Ruby I bet rarely write a class. A few functions, actually methods of Object, are all I need there.

I feel the major reason OO doesn't fit well in Python is because Python was designed to harness "duck typing," which enables polymorphism with less code. Furthermore, attrs / dataclass does away with a lot of the Object boilerplate that most developers would prefer not to write when moving quickly.

To top it all off, context of use matters. For scripts without tests or limited extensibility, the value of OO is mostly in clarity to the reader, and here attrs / dataclass can be very concise and effective.

That said, when the object jungle gets very large, Python gets a lot more tricky. Look at matplotlib or try to write a billing system with a variety of account and transaction types.

Nevertheless it's nice for a post like this to call out the existence of the bijection between OO and 'non-OO' Python programming styles. The issue is worthy of contemplation for any early Python programmer once one gets the feet wet.

I'm not sure I follow. Python's duck typing explicitly relies on its object-oriented features. I'm not sure that it's even possible to implement duck typing in a language that isn't object-oriented.
I am not a language expert, but Clojure (inspired by CLOS IIRC) does method resolution based on an arbitrary function that can ask any "is it a duck?" question of the arguments:

https://clojure.org/about/runtime_polymorphism

It's common for code to "choose" which method to call based on the class of the arguments, but it's also common to choose which method based on whether a particular key is in the hashmap, etc.

I suppose that this is edging dangerously close to starting a semantic argument, but I'd personally argue that ad-hoc polymorphism is a distinct concept from duck typing that just happens to have a lot of overlap in its use cases. One key distinction is that Clojure multimethods need to be explicitly declared somewhere, whereas duck typing is 100% implicit.
And in everyday Clojure usage the equivalent of Python attribute access is key access on maps, which is even more directly duck typed.
When I talk about duck typing, I'm talking more about getattr and hasattr (explicit or implicit) rather than the everything-is-an-object structure under the hood. For example, you can invoke "foo()" on any thing no matter the type, and it'll work so long as you have "foo()" declared for the instance. You can't do that in many "OO-heavy" languages like C++ or Java. The advantage here is that in Python you don't have to explicit interfaces and base classes in order to achieve polymorphism.

You make a good point though-- the "duck typing" stuff is actually a consequence of Python using objects. Interesting: the "duck typing" paradigm should be able to be ported to other languages like Java then...

I don't know if one has really made any case at all about OOP, neither for nor against, if one hasn't considered cases that involve any sort of conditional branching.

Because the key problem that OOP is supposed to solve is not lumping bits of data together. The problem that OOP is supposed to solve is using polymorphism to limit the proliferation of repetitive if-statements that need to be maintained every time the value they're switching on acquires a new interesting case to consider.

In practice, I've found that kind of use case for polymorphic dispatch to not be especially common — meanwhile, most OO languages strongly encourage making everything objects. There's absolutely a time and place for a good abstract interface, but it's always struck me that classes are overused in general.

That's why I really appreciate the go/rust model, where you can tack interfaces and methods onto structs if you want to, but there's no pressure to do so.

The main reason why OO languages typically make everything objects is that having only one kind of type greatly simplifies the programming language and the experience of programming in it.

There are only three common languages that are OO with a few non-object types: C++, Objective-C and Java. This feature is universally recognized as a serious wart in all three of them. It creates all sorts of little edge cases that you need to learn to program around.

I'm curious to know which "serious warts" in C++ would be fixed by making everything an object.
Indeed... in Java this is reasonably clear, but I've never found I have a problem in C++. I suppose if you're inheriting from your template argument and you template on an int? Seems awfully contrived and useless though.
This is basically how I use Python :) Truly, recently I tried to remember how to create a class method so that I can have different constructors and I forgot! Got back go just structures and functions.

Most OO in most OO languages is just polymorphism and inheritance is merely a roundabout way to construct or traverse the method graph for polymorphic objects. It's often much easier to do polymorphism directly and only if you really need it: quite often you can fare well without polymorphism at all and simply write branching instructions.

The useful OO must be about changing state in a controlled manner, but this remains largely unaddressed.

When writing Python, I usually try to follow this item from the C++ Core Guidelines (replace "struct" with "dataclass"):

> C.2: Use class if the class has an invariant; use struct if the data members can vary independently

> An invariant is a logical condition for the members of an object that a constructor must establish for the public member functions to assume. After the invariant is established (typically by a constructor) every member function can be called for the object. An invariant can be stated informally (e.g., in a comment) or more formally using Expects.

> If all data members can vary independently of each other, no invariant is possible.

https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines...

So the example manages to evade any need for encapsulation. It's viable because the internal hidden state is so cheap to compute (the full URL) that it can afford to reconstruct it within every call.

But imagine if constructing the url was a more expensive operation. Perhaps it has to read from disk or even make a database call. Now you really need to cache that as internal state. But doing that means external manipulation of root_url or url_layout will break things. Those operations need to be protected now. So do you make "set_url_layout" and "set_root_url" functions? And hope people know they have to call those and not manipulate the attributes directly? Probably you'd feel safer if you can put that state into a protected data structure that isn't so easily visible. It makes it clear those are not for external manipulation and you should only be interacting with the data through its "public interface".

Of course this brings about all the evils of impure functions, mutated state etc. But if you are going hardcore functional in the first place then it becomes rather obvious OO is not going to fit well there, so its a bit of a redundant argument then.

I don't think the OP makes very good points, but one thing to point out is that Python only has access-control by convention. This creates a wrinkle for any argument based on using "protected data structures"
We use python extensively, and I think python's "access control by convention" is pretty great.

The reason for access control is not to protect from malicious programmers -- it is protect from programmers who don't know better and from honest mistakes.

So all you need is a simple rule ("don't access private methods") and now everyone knows which fields are public (and thus are OK to use) and which fields can disappear any moment.

For extra enforcement, you can add pylint to your CI so it flags all the protected accesses. But really, a good team culture is often enough.

> So the example manages to evade any need for encapsulation.

In Scheme and many other lisps there is no difference between “objects” and “records” and “fields” and “methods”.

Defining a record with a number of fields also defines accessor functions, such that:

  (define-record point
    (fields (mutable x) (mutable y)))
defines the following procedures:

  make-point
  point?
  point-x
  point-y
  point-set-x!
  point-set-y!
All these names can optionally be changed to something else.

Encapsulation is simply enforced by not exporting any of these procedures from the module wherein they are defined.

Any arbitrarily more complicated functions can be built on top of these which can be exported.

As such, there is no meaningful difference any more between an object and a record, there is no special “dot notation” for field access and method calls, and normal procedure calls are used. This follows the general lisp design principle of avoiding special syntax as much as possible and using ordinary procedure calls for everything. There is no special “subscript notation” for `vector[i]` either, and `(vector-ref vector i)` is used instead.

Hi, what I'd really like is to test my conjecture - would you (or anyone reading) be able to email me a (< 500 line, ideally not super-deep-library-code) bit of code (constructed or otherwise) that you think is "so innately OO friendly that the conjecture won't hold".

Given some examples, I'll do a follow-up blog post.

You could take the example as-is with the new requirement that the remote end point may require up front authentication. How the authentication works depends on the service, and it needs to be done on a per-URL basis. The authentication protocol may return some kind of token or key needed to use with future requests. However what this is and exactly how it is used is service dependent (for example, it might be supplied verbatim in an auth header or it might be a signing key and it might even be salted with some credentials etc).

So challenge that is introduced is that you now have hidden state that is dependent on the public state but which cannot be computed by users of the client or the parent class. In fact, code in the parent class can't even know ahead of time what state might be needed to be retained.

I am guessing you will probably be able to propose that authentication be supported by some kind of pluggable authentication interface, but it will still be difficult to deal with the hidden state without introducing assumptions about the auth protocol or the type / nature of the state retained and without having that protrude into the parent interfaces.

You can just define them as functions that are part of the class.

Something like:

@dataclass class AuthMethod: credentials: typing.Any cache: typing.Any apply_to_request: typing.Callable[[typing.Any, typing.Any, Request], None]

@dataclass class AuthMapping: method: AuthMethod matches: typing.Callable[[str], bool]

class Client: root_url: str url_format: str auth_methods: typing.List[AuthMethod]

def auth_request(client: Client, req: Request): applies = filter(client.auth_methods, lambda x: x.matches(req)) if len(applies) > 1: raise Exception("multiple auth methods found") elif len(applies) == 1: method = applies[0] method.apply_to_request(method.credentials, method.cache, req)

Then you would statically define your service's auth methods by URL, so users can just do:

    my_client = Client(auth_methods=MyServicesAuthMethods)
or you can create a helper function like:

def create_client_a(base_url: str): return Client(base_url, auth_methods=[...])

Alternately I think you can use closures, but I'm not totally positive about the scoping.

AuthMethod = typing.Callable[[Request], None]

def auth_method_a(username, password) -> AuthMethod: my_token = None

    def __auth_method_a(req: Request):
        if my_token is None:
            my_token = do_login(username, password)
        req.headers['token'] = my_token
    return __auth_method
That creates a standard interface (a function that accepts a Request), without assuming anything about the underlying protocol.
Pointing to someone's bad code example and saying "This is why all OO is pointless", is truly a lazy effort.

Good OOP is good. Bad OOP is bad. That's like every other piece of coding. Some excellent examples of great OO code that I've worked with have to do with having an abstract class to define a data api, and then being able to switch providers seamlessly because the internal interface is the same, and all you need to do is write a vendor-specific inherited class.

> Good OOP is good

That's not the point - the rub is that good OOP is HARD. People get sold on it with examples like your data API, where the abstractions are clear, and reasonably static, whereas for most problems, and for most developers, it's genuinely difficult to get this right.

Hi Can you please link to some examples of python great OO code?
How long has the author used Python? OO, while responsible for much of the footguns, is also responsible for an enormous amount of Python's expressive power. Being able iterate or key into arbitrary objects as if they were lists or dictionaries is enormously powerful, but all of that structure is OO based. It's a huge part of what "pythonic" means.

> If you've taken the pure-FP/hexagonal-architecture pill

Writing software is not something you take a conceptual "pill" for. You learn new tools that you add to your toolchest and use at your own discretion. Tools which purport to be able to displace a whole arena of otherwise stable, mature, and high productivity tools better be at least comparable if not significantly better to encourage upgrade. FP as a tool definitely comes in handy for quite a few use cases. FP as an ideology? Not so much.

Regarding the latter, I'm getting a little sick of the FP chauvinism that that pretends that pre-existing software and how it was written is obviously inferior to the new, purely FP way without any real persuasive evidence. Code that's written in an overly FP oriented way is no less immune to the same codebase diseases that code written in an overly OO oriented way is. It's the map-territory problem all over again.

That’s because the ideologists operate based on intuition rather then identifying the exact reason why one paradigm is better. OO is bad because it promotes writing logic with free variables and context. Logic that relies on free variables and context is inherently less modular. Lack of modularity is the root of all structural technical debt.

While FP doesn’t force someone to eliminate context or free variables it certainly does not promote writing logic that includes it by default. This generally leads to more modular code. Hence the reason why FP just "feels" better to practitioners and leads to people promoting it without clear reasoning. This in turn makes the promotion of FP seem "ideological."

Maximizing modularity in code requires greater restrictions then the ones FP provides, but FP in general is heading in the right direction.