is_blocked(user, other_user)
seems like it’s still object-oriented to me. Oh sure it has terrible syntax, but we’re still designing our application with encapsulation and if this is CL and “is-blocked” is a generic then it’s how you would write this in an CLOS-style OOP — it would just be written (is-blocked user other-user)On the other hand:
other_user.id in user.blocks
is what I would imagine a non-OOP version might look like, because it’s not dynamic and not encapsulated. If we were to write it: in(other_user.id, user.blocks)
or: (in (other-user :id) (user :blocks))
I also still think these functions are better than the object-oriented ones: they’re faster, and it’s clear to the reader if you can get the ids without the object (say by pushing this into your query) you will save a lot of runtime and programmer-time.I also think if you make decisions based on what is popular (e.g. for startups), instead of actually thinking about it, you are subscribing to dogma. Maybe sometimes not-thinking is good, but it seems possible that what you think of as a “start up” has largely only existed in the short time period that object languages were in vogue. Why do you think this is causal? Do you think if go (for example) becomes popular we won’t have startups any more?
Message passing allows much more fine-grained access controls to the implementation, and in many cases that's highly desirable, especially as the implementation gets more complex and the purposes of accessing the data more specific, and excess coupling more problematic.
Or put another way, I think agreeing that message-passing is useful is not an endorsement of OOPL: After all, Erlang supports message-passing.
It's the same in programs: You have primitives, but you generally hide them behind higher level APIs, or in the case of OO you encapsulate data and behaviour together in one place to (hopefully) make it easier for a human to conceptualise what's going on. OO is no silver bullet; it's just one way among many of organising your interfaces and data, and to make combinable components that increase the expressivity and thus power of your codebase.
In the previous example, client code was looking inside data "user" to check if a particular thing exists in its "blocks" data, but it would be better to work at a higher conceptual level, such as a function/method/message called "has this user blocked user X? (returns yes or no)". Then when someone reads that code later, they can at-a-glance see what the program is doing conceptually, rather than scanning around to figure out what the code is doing with "blocks" and work out what it's attempting to accomplish.
In OO, the object exposes what services it offers.
But in your code fragment, there is no way for a new implementation of `user` to override `is_blocked` - in most languages.
(C++ has ADL/Koenig resolution https://en.wikipedia.org/wiki/Argument-dependent_name_lookup which actually does let you do this generically at compile time.)
In modular, especially statically typed, functional programming, modules identify what services they offer, and what data items those services consume, and the concerns of specification of data types and provision of services are decoupled.
I'm pretty sure the language being referenced was Common Lisp, which supports multiple dispatch (in CLOS). In a multiple-dispatch OOP system methods can be specialized based on any argument—this is somewhat like ADL in C++ but more generalized. Ergo, a new implementation of 'user' can override or extend more generic versions of 'is_blocked'.
I think this is really the most valuable part of OOP; it makes code read in the order that functions are applied, which is both natural and enables type driven autocompletion.
What bothers me about OOP is inheritance, subtyping and the emphasis on mutable state, which greatly complicate call chains, the type system and temporal reasoning.
I think the main reason what people get wrong about OOP is that they tend to think everything needs to be models with inheritence, which is completely wrong. Inheritence is BAD!
The rule should be:
1. Avoid inheritance
2. Use interfaces instead
3. If interfaces are not enough, use composition
4. If composition is not enough go through all other similar design patterns that are not based on inheritance.
5. If you really need inheritance, be very careful about which function you make non-final and non-private.
I mean Erlang is an actual OOP language according to the original creator of OOP's concept of message passing between objects. But each object internally is quite functional.
It's just the Java crowd's deep inheritance enterprise code that makes every developer who survived that want to vomit.
IMO, that seems like too formulaic an approach without enough thinking in that step.
Also, you mention design patterns, but the classic design pattern book nicknamed GoF (Gang of Four, i.e. by Gamma, Vlissides et al), has this guideline prominently on an early page (before the main body of text):
"Prefer composition over inheritance"
, or similar words (from memory).
And that is the only sentence on that page, right in the middle, which could only have been done for great emphasis.
But you say:
3. If interfaces are not enough, use composition
And although you do say to avoid inheritance, aren't interfaces somewhat similar to inheritance, so shouldn't the same GoF guideline apply?
Asking for myself.
"Program to an interface, not an implementation"
and this actually comes before the other one about composition.
Inheritance (i.e. implementation inheritance) creates tight coupling between classes, whereas interfaces exist to prevent that happening, ... they are quite different constructs.
One thing I do appreciate about objects is the natural namespaces and the way methods naturally tend to be located with their type in the actual code.
The thing about OO is that as far as I can tell, it's natural. Whatever OO is criticized, people point out that the standard OO example is "car" but the typical OO object in practice is "network credentials permissions". So what? The usefulness of OO is exactly that allows, partially, some totally fungly thing like "network credentials permissions" to be treated, sort of, like a thing, we sort of, understand, a fricken car.
And further thing is, the fp thing seems to be something like "we don't do objects at all but we do really elaborate Turing complete types, please don't ask us what the difference is, you wouldn't understand. Don't ask us whether types have private data. Especially don't ask if integers have private bits..."
A connection client is suitable to be written as a class. It didn't make sense for outsider to see what the internal connection state is and manipulate it randomly. You want to just call .connect() and happily use methods on it.
But makes a math method be a class with multi constructor parameter just don't make sense, why not just make it a static method or a bare function(If the language supports it) with multi parameter?
And the most worst problem(Though it is not related to this topic). Someone writes pattern from OO language in other language. Even the language itself has the capability to express it directly and gracefully. (personally I think this is why OO haters exists...)
Another poster mentioned that any paradigm can be misused. Yes but OO has a sort-of "least bad to miss-use" quality.
You can have a date object or a query object or Haskell-function object, and they are all conceptually terrible but they still allow people who only know OO to work with all kinds of things. OO is created to "glue" anything together with anything else. The equivalent with functional programming is "FP is created to "glue" only other functional elements together with other functional elements".
And OO definitely offends the sensibility of those who things done correctly and well - which includes lots of programmers. The "least bad" is indeed an enemy of the good. But you're not going to get all the world's systems redone correctly as the new paradigm so if you want to end OO, you'd need an alternative that could also least bad in many circumstances.
You have exactly the same issue with any paradigm. I am seeing it now with functional programming as people go all obsessive into converting everything into it.
Is OO good for everything? No. Is FP good for everything? No. And yet, unless you've attempted to use them for everything, you're unlikely to understand why they ain't good for everything.
The point I will make is that "a car is an object you can do things with" is actually probably not how you think about a car. Personally I think of my car and the "objects" around me as merely tools I can use to achieve goals.
If I need to go to the shop I don't think to myself: I need to go to the car object and call the door_open(DRIVER_SIDE) method and then call the add_driver(self) method and then the door_close(DRIVER_SIDE) method and then the turn_key() method...
When I need to go to the shop I come up with a shopping list (or more likely get my girlfriend to send it to me) and then I think about my destination and how I can get there. I think of using my car to go there.
So simply put, I think the object model has no relation to how we actually think about objects around us.
The object model (At least alan kay's variant) may be a good description of how humans talk to each other, and I think it has its place for sure, but it's also nothing like the object model which gets sold to people these days.
To me "network credentials permissions" sounds like just some data which describes the permissions required to access another piece of data called a network credential. That's how I think about it, I am not sure it's accurate to say that my method of thinking about it is any more or less natural than thinking of "network credentials permissions" as a thing but personally I would say that considering "network credentials permissions" to be a thing seems just outright wrong.
I can't imagine how this isn't exactly me or someone else thinks about a car. I might then think about what I accomplish with a car. And sure, "car" and "bicycle" are "transportation means I can steer" and feed "transport means" and "destination" into an algorithm and arrive a location. Sure, "transport means" is a higher logical level "car" but both of these go into human thought processes.
If I need to go to the shop I don't think to myself: I need to go to the car object and call the door_open(DRIVER_SIDE) method and then call the add_driver(self) method and then the door_close(DRIVER_SIDE) method and then the turn_key() method...
The only reason you don't think you think about that, is because your self-conception apparently delegates the process to your reflexes. But if you have explain to someone how this happens, you reflexively dive and give some English equivalent to your narative above.
I think it's more likely that you think this is how you think about the car because you think it makes sense to think about a car that way. In reality if I recall correctly when we have a goal of "going somewhere" we don't see "objects" we see "obstacles and tools which overcome obstacles". Specifically you don't see what a car is (a lump of metal which moves with you inside) but rather you see what a car means (a tool which can be used to overcome the obstacle of distance).
Although honestly I can't come up with what to look for on the net to source this information and I currently don't have time to do it.
> The only reason you don't think you think about that, is because your self-conception apparently delegates the process to your reflexes. But if you have explain to someone how this happens, you reflexively dive and give some English equivalent to your narative above.
Even if that is the case, I don't see how an English explanation can map onto an object model. It more closely approximates a procedural model.
To point directly at the problem: Does the car contain me or am I riding the car? How do you describe this concept using objects? When you think of the car as a tool rather than a thing it becomes obvious: The car is a function which transforms some data <me> by changing the position from one place to another. With this approach I am no longer stuck trying to work out if I the car object has to have a add_driver() method or if I should have an ride_car() method.
Both and as someone who has implemented vehicle logic in a game before you usually want to model both.
You’ve done the classic programmer thing of creating an abstraction over the grisly details with ride_car which is great if you’re just representing the fact abstractly but if not someone still needs to care about the details and model them. An abstract version is great then for example if you're planning but it doesn't remove the need to build the details out.
To use a car, we're performing a bunch of actions involving modifying the state of a car. That seems to be quite analogous to the OOP way of thinking.
You're right that we don't consciously think of car travel in this way, but I don't see that as inconsistent with OOP either. Our thinking about cars is highly abstract, as though it's an abstract class/interface with a single go_somewhere(destination) method, which fits quite well with OOP thinking.
I'm not dogmatic on OOP vs FP, but let me take the side of FP here, we could model this as the car being a data structure with its position (x, y, etc), and a representation of what opening the door is (width of the car + length of the door, an anonymous function, or a named function, that can be made polymorphic) and now our open_door(car, world) can take both and the world doesn't need to know about what is a car and the car doesn't need to know about the world either, it just serves as a mapping, and the function open_door checks the positions of car in the world, transforms the car according to its procedure and checks it still fits the world and if not it produces an annoying beep.
So what you're saying is I'm going to have to downcast to a concrete class to figure out if my object is actually suitable for going to the store or my bathroom (my house isn't big enough to drive a car through).
Lines up with most OOP systems I've worked in.
* did not call putFeetInCar before adding driver
* did not call putShoesOnFeet before calling putFeetInCar
* did not call gripKeyInHand or set up HandKeyAllocator
* did not set up HandKeyAllocatorManager or use KeyDashboardMeldConnector to insert key into KeyHoleDoodadInterfaceXConnector
* did not do correct incantation of prayer to Cthulu to ensure AxelAlignmentProtocol is properly specified
And then guess what? Suddenly you're extremely concerned with the internal state of those objects. Because understanding it is essential if you want to have any hope of actually using them. And the great irony is that this completely undermines one of the main benefits of OOP: private state that the outside world doesn't have to care about.
> did not call putShoesOnFeet before calling putFeetInCar
That's bad architecture, not an OOP flaw. These should've been private methods called and error-handled properly from driver.prepareForDriving, or even implemented as a DriverPreparedForDriving subtype.
> did not set up HandKeyAllocatorManager or use KeyDashboardMeldConnector to insert key into KeyHoleDoodadInterfaceXConnector
All these sound like bad OOP (or Java) to me, a HandKeyAllocatorManager is not a thing in the domain. I'd rather build my logic around objects like HandsContents, KeyKeyholeCompatibility or SeatedDriver.
For me, for example describing a process where one person gives the car to another person to go somewhere it feel very easy to say
person1.press car_key.button
car.open_door
person1.explains person2
person1.gives car_key to: person2
person2.enters car
engine.starts
car.moves
Of course OO is not satisfying completely this but in general I see that nothing happens without someone/object/matter doing something on something else.So I except that something happens into/to the world because an object.did something to a subject. Else nothing happens. thus it does not feel natural to think first about an action that requires a force and the subject to be aplied to. Always an object is exercising an energy/force onto something else.
To summarize I think if one is a person who thinks more about energy than matter then fp feels natural. If one is a person who things more about matter then oo is more natural.
How so? If it was natural, it wouldn't take 30 years of programming to arrive to it (and in a bad way).
>The usefulness of OO is exactly that allows, partially, some totally fungly thing like "network credentials permissions" to be treated, sort of, like a thing, we sort of, understand, a fricken car.
Cars have instance variables and methods? You car.drive() your car?
You can do that with OO too, of course, except that encapsulation means you typically do it in the class and if you want to do other transformations, you either need to amend the class, expose the internal data (directly or through getters/setters), or inherit from the class. In a functional or procedural language you can also provide encapsulation/data hiding if you wish.
I don't think either approach is more natural than the other and they're both more or less equivalent, its just that different languages encourage different default approaches.
Personally, after twenty years of programming, I find that I value immutable data over all in most cases (ie unless I'm writing performance-sensitive C++ code) and as long as I have that and a reasonable way of managing it, I don't really mind if I'm representing things as immutable objects or something else.
I'm going to half-agree but rephrase: it's a kind of programming where even a novice programmer can iteratively approach the correct solution by adding more and more code. That's good from a management point of view: you can always feel like you are getting closer and closer to the goal because you get more and more code and fulfil more and more of the requirements. But I wouldn't go so far as saying this is productive, especially not in the long term. For one thing, you'll end up with a lot of OO code which is the worse thing for productivity.
> Would you rather type out this: Or this
That's not the important distinction between OO and functional. The important distinction here is: when you call the function, is it defined by user? Is it defined on some more abstract base_class which also applies to "groups" and "corporations"? If you want to change it, what do you have to do, and which types and behaviors do you affect when you change it? Subclass polymorphism is is almost invariably a net negative in my experience.
The syntax of OO is great. We realized this especially since it grew to fame at the same time as the clever IDE with autocompletion. But your example would look like the first in e.g. Rust, which isn't an OO language by any stretch of the imagination.
I really dont see why either of these would be the case.
You could imagine a language that isn't OO but where you could define the . operator to dispatch certain functions with the same syntax as OO.
Evaluating programming techniques based on what startups use doesn't sound like a good idea to me because 1) Codebases in startups usually aren't the best examples of quality and cleanliness and 2) optimizing for productivity above anything else has the potential of failing in the long term in a lot of settings (including startups).
I agree positional arguments can be bad in a case like this. OTOH you can just look at .is_blocked_by? as infix syntax here which just works in many languages. The syntax problem in the example can also be solved eg by using named arguments. (But to reiterate record.function(x) syntax doesn't need an OO system either, or even language support if you have macros)
This is plain wrong. The problem of mapping records (presumably you mean database records which are actually tuples but lets call them records) in a relational database to objects in an object hierarchy is the source of the "object-relational mismatch" wherein the fundamentally graph-based structure of an object model is intrinsically incompatible with the related sets-of-tuples structure of a relational database.
To try to solve this problem, an inordinate amount of time has been put over many years to develop various ORMs (Object-Relational Mappers) which try to hide this intrinsic incompatibility under many layers of abstraction.
A whole new industry came out of the simple fact that people who had been lead into thinking that OOP was the only solution to any problem wanted to use relational databases to back their objects.
These ORMs all come with innumerable caveats and shortcomings. Their abstractions constantly leak and their performance is a constant firefight between making code idomatic OOP and making code actually perform in a reasonable way.
To claim that objects map neatly to database records is completely ignorant of even the history of how ORMs came about.
Your comparison of `user.blocked_by? other_user` to `is_blocked(user, other_user)` is a complete red herring anyway. Nothing stops a non-OO type system from letting people specify that a function applies to a type such that you can write `foo.is_blocked_by(bar)`. Nothing also stops you from writing code more explicitly and clearly such as: `foo in user_blocklist_of(bar)` or even mixing both concepts and writing `foo in bar.blocklist()`. In summary, these are all questions of syntax and style and have nothing to do with OOP.
> The reason it's good is that it is productive.
This implies that somehow all other methods of programming are less productive?
When I have the rare occasion where I need to write some kind of web application I reach for three things: Flask, SQLAlchemy Core and sqlite3. I design the database then design the application around it. By actually directly interacting with sqlite3 using a query generator like SQLAlchemy Core I get control over and the ability to use the features of my database of choice. I am no longer stuck trying to fix query performance issues by randomly mangling an object model until the resulting database interactions act like I want them to.
This to me seems far more productive than any of my experiences dealing with ORM performance issues and abstraction leaks.
> Who has blocked whom is more natural to talk about when there is a primary object subjecting the other object to a test.
I think there's nothing about this particular example which puts particular emphasis on either side of the block as the "subject" and the other side as the "object".
- a blocks b - b is blocked by a
There's nothing about either of these which stands out as the correct mental model.
In reality it's not a subject-object relationship it's just a relationship between two subjects.
In a database this would be stored as a table containing a 1:many relationship between user and user.
By attaching this relationship strictly to one side of the relationship you've actually ignored the case where it may be useful take the perspective of the other side of the relationship.
If you end up factoring that into your object model you will presumably end up having two functions: `.is_blocked_by` and `.is_blocking`. These will likely have to have two separate implementations (although I'm sure you can get the ORM to handle that for you). (Also, don't forget the performance impact that you're likely dealing with WHOLE user records at once and relying on some lazy loading specification which you probably defined too loosely to be useful to avoid loading the entire "other user" from the database just to check if you're blocking them.)
I think now it should be clear that the single separate function approach is actually far more representative of the reality of the relationship.
Personally, as someone who mainly uses Clojure, I would rather type:
(blocked-by? user other-user)
With that said, it really doesn't matter which of these options I have to type, because as usual, the syntax is less important than the semantics and all of these options can work with or without OOP. Now, OOP isn't bad per se, its just a tool. It depends on how you use it.I do buy into the Clojure notion though that there is a problem with most OOP languages and that is that they conflate multiple concerns and features into objects. Give me the features to mix and match as I please, instead. That is, if I want to keep my data and my functions together, great, but if I don't, also great. If I want to do polymorphism with objects, great, if I want to do it without, also great. Let me choose, based on the problem I'm solving.
I tend to favour a functional-first approach, but will use OOP as it makes sense or when it makes the API easier. To me, immutable state (where possible) is more important than an avoidance of OOP. I also write a lot of C++ and I won't shy away from creating some classes where it makes sense.
This hasn't been my experience at all working with Elixir. A typical Phoenix app will have hundreds to thousands of funtions related to business logic and none of them bundle state.
It works fine and debugging is much easier than it was for me previously with JS or Ruby because of the pure functions.
Objects map neatly to records Well, some data structure maps even more neatly to records, no? {:id 1, :name 'bob'}
Most successful startups use OOP and there is a clear reason for that: It's more productive I prefer not to waste time arguing with this, but programming is not about "successful startups".
FP data items (also, often called “objects”, but with different meaning) do so at least as well as OOP objects. Better in many ways.
> which pushes global state persistence and all the hairiness of locks to a good RDMS like Postgres.
OOP obscures whether an entity is an immutable data item extracted from an RDBMS or a mutable local thing or a mutable thing which mutating also effects an external resource like a database, but there is nothing about FP which prevents, compared to OOP, pushing “global state persistence and all the hairiness of locks to a good RDMS like Postgres.” In fact, the opposite; there's a reason no one talks about a Functional-Relational Impedance Mismatch.
> It's naturally normalized
No, OOP is not “naturally normalized”.
user `blocked_by` other_userUser is derived from human, that from lifeform, that from cell,dnk,(...) each of those is derived from molecule, that from atom and then goes into quantum physics parts. And is_blocked is implemented on higgs bozom, which has method is_blocked and in case of error throws it from there. Surely is_blocked is also implemented on all other levels, each adding just a small information to the result. /s
OO is elegant. Even C++ operators overloading is great (MFC CString is a nice example). The problem is that people who think what they are doing based on what they have learned in school or some "pattern" (with not enough mileage - I am talking about 15+ years that you have spent doing OO monstrosities before you reached the enlightenment) don't understand that you don't need higgs bosom in user object. And that someone searching for an issue in this architectural beauty represented by user object is going to start scratching his head when is_blocked is called and catches higgs_bosom::exception. Even more when encounters god_factory.
Once someone figures out that there is a need to understand the universe to be able to handle a bug in otherwise simple is_blocked function the obvious and rational reaction is screw OO. But the problem is not in OO but rather in people using it to express their philosophy of life that needs to be infinitely ad absurdum reusable,...
I have seen this everywhere, from most prominent libraries for C++ to java where this is seen at its worse. I dont dislike OO. I dislike people making lasagnas. Unscrupulously patternizing everything and everywhere for the sake of doing it.
And I havent even started with patterns, refactoring once you figure out that scope has changed,... will rather use an example: https://github.com/EnterpriseQualityCoding/FizzBuzzEnterpris...
At the end. I love OO. But I prefer a language where you cant inherit. Cant throw exception. As this prevents people from over complicating the code for no reason and helps me to not read their whole epic architectural poetry to use a simple function.
Face it. Most programmers cant code. They are terrible at doing it. And the more powerful tool that they get, the more they will abuse it and not even know they are doing it in most cases.
Good OO requires large refractors on the regular, and with a good design, refractors like this are low cost high return, but failing to do them results in mud eventually.
I used to shy away from it "because nobody can do OO so it's better if we just don't".
Writing everything in procedural and functional is quite productive, but it too requires adherence to rules and avoidance of mutable state.
Once I realised, there are no shortcuts, you just have to learn what works and what gets you into trouble later I tried OO again, only now I use it sparingly and tend to mix procedural, functional, and OO depend on the task at hand. One of the amazing things about PHP is that you can do this easily and clearly. Things like go, java, or even JS these days tend to force you into one way or the other.
Earlier on I aimed for plain objects / structs for information, grouped state passing but I realised these often lead to smells that are hard to clean up. Now I limit their usage to the edges and opt for objects instead. Passing implementations along with the state really cleans things up in coherent ways.
I think one of the scariest things about good OO is that it looks like bad code to me. It looks "not dry". It looks, untidy, lots of state sharing and member variables to track things. From a functional perspective that stuff gets you into trouble. But from OO perspective its what works. The trick is keeping classes small enough to be aware of all the member variables when reading any member function. You shouldn't get blindsided by random state changes elsewhere, that is a sign there is more than one thing being done by that class.
I’m a relatively junior dev and I believe this is a pattern I use a lot. Would you be willing to elaborate on what you mean by it and what some common pitfalls are?
Plain objects have the benefit of OO in that you have a bunch of normal friendly objects that map well to a database record. But they tend to be treated more like variables. They work well for groups of functions that need a lot of the same parameters but you don't quite want to lock in an object boundary.
Often I'll start with a plain object or plain functions, then once I can see the seams I'll form objects. Sometimes you can see these up front as obvious, I still try to hold off. The longer you wait the better and more concise your objects will be. You want you objects to look more like behaviors than records. (Though there is heavy cross over, plain objects / structs make good records and you can easily consume them in objects.)
The big pitfalls:
Mutation, avoid it where possible, mutation is a sign you need an object. Immutable structs are bliss to use, they're easy to reason about. Mutable structs are basically impossible to reason about, never know what's in there.
Using an object to pass a single variable to a function.
Using an object to pass a single variable / subset to a sub function call.
Basically, try to terminate plain objects at boundary edges like you would nullable variables. There isn't anything wrong with passing an object around for one variable, its just a bit messy and creates a bigger dependency than required. It makes it hard to refactor your plain object out later because you can't tell what is actually depended on down in the stack.
Aside from Bjarne Stroustrup, is there any computer science major figure, great dev, etc., that thinks there is such a thing as "good OO"?
Though I don't really understand the question. Whether OO is good or not is a different question, and I'd posit the more important one.
Alan Kay? Alan Kay has had the worst to say about OOP as its known ("but there's a different good paradigm, which real-world OOP devs in C++/Java/etc might not use, but Alan Kay had in mind and Smalltalk offers" is not really a rebuttal").
>Though I don't really understand the question. Whether OO is good or not is a different question, and I'd posit the more important one.
And we can't divine the answer to that question.
But we can see what the luminaries have to say about it.
I'd rather follow the good examples/advice (of those that did major practical work in CS) that take some "objective" (how?) answer to the matter as gospel.
That there are plenty of books teaching OO programming?
Sure.
Is there any major CS figure / language developer etc that thinks OO is a good idea in 2021 was the question.
Not whether some O'Reilly or Manning or whatever author has some book about it...
And even the cool kids that got two releases yesterday, Rust and Julia, do support OOP, so I guess we can include their core design teams as well.
Anders, Goetz, and Torgensen on recent years have all talked about the need to add functional features into the languages that started as OOP (and is what they have been doing with C#, TypeScript, and Java since 1.6 or so). One also suspects they made them OOP because they did them on comission and the goal (of Sun, MS, etc.) was to attract devs with what was considered hot at the time...
But neither Rust nor Julia support inheritance, so if their style can even be called OOP, it's not the OOP of the kind that Joe talked about where you "get a gorilla holding the banana and the entire jungle", and which most devs know from C++, Java, and yes, Python and Ruby.
So yes their style can in certain cases be called OOP.
Just like being a multi-paradigm language, with support for OOP, doesn't magically make it non-OOP.
myMethod(myObj, myParams) == myObj.myMethod(myParams)
Perl also does this (but with -> rather than .), and also allows interchanging the syntax.
object:method(arguments...)
object.method(object, arguments...)
object["method"](object, arguments...)
Lua objects are tables (associative arrays), the dot and colon operators are forms of table lookup, and methods are ordinary functions stored in the table (or more commonly its associated meta-table) which happen to take the object as their first argument. The colon operator, which can only be used as part of a function call, passes its left-hand operand to the function along with the other arguments. if (user.blockingId == otherUser)...
Even if there is some scary logic you would argue you need to hide, I'd go further to say you have a UserLocks module so you can do: if (UserLocks.getUserBlockedId(user) == ....
Kind of a hard example to work with because I'm not sure why a user would block exactly one other user, but yep.
Object oriented programming is good. Not in absolutely every situation, but for most business applications it's better than the alternatives. The reason it's good is that it is productive. Objects map neatly to records, which pushes global state persistence and all the hairiness of locks to a good RDMS like Postgres. It's naturally normalized, but can be denormalized without much work, and in exchange for bundling state with functions you get easier introspection which is especially helpful in debugging.
These conversations are better done with examples. Say you want to build a social network around videos, like YouTube. Say a user can block another user and, thus, stop them from commenting on their videos.
Would you rather type out this:
Or this When doing the check? Personally, seeing the the is_blocked? method on a user makes it easy for me to understand. Who has blocked whom is more natural to talk about when there is a primary object subjecting the other object to a test. For all the anti-OO stuff out there, it all boils down to the same thing in the end. For business logic, your functions need to know so much about the data that they are operating on that it's pointless to try to avoid bundling the state. I'm never going to pass a cheeseburger into my is_blocked function so why on earth would I avoid bundling state and operation together? It's like a map of a city trying to avoid listing shops since "maps should be about roads and geography, not shops." A pointless dogma that doesn't actually help programs get built. Most successful startups use OOP and there is a clear reason for that: It's more productive.Now, do I use OOP when I'm doing data science stuff? Mostly no. I don't need it there. But where it works, it's magic.