I Don't Use Classes
spin.atomicobject.com
spin.atomicobject.com
> I’m terrible at making good abstractions with classes.
Then don’t. Or only do it after everything is done and you have explored the domain. Don’t try to be clever from the start. Unless you have understood everything, it will backfire.
> But as other classes extend and override those methods to address their version of the problem space, they usually end up having some other responsibilities in addition to the original task.
Inheritance gone wrong. If a derived type behaves differently, it should not be a derived type. See Liskov substitution principle. Just because a hash map is somewhat like a list of key/value pairs doesn’t mean it should derive from one.
It could also be a problem of missing separation of concerns/responsibilities, which is entirely unrelated to OOP.
> Secondly, I’ve noticed that classes have a tendency to grow large.
Yes, I did see many big classes. They were always the result of... missing separation of concerns/responsibilities. The fabled god objects.
The section about state management appears to be very much React-specific, too. So, not about classes.
I find some problems are solved better in an OO way such as interacting with a relational database.
Others like data processing, are better done in a functional pattern.
I look for the right tool for the job rather than some idealistic purity
I've switched to functional interactions with a database, (Elixir/Ecto) and really, it's much better. In particular the explicit and declarative nature of the database interactions combines the convenience of an ORM with "not hiding important gotchas from you". I'm not arguing for pure functional systems (which I can see going very poorly with relational databases).
There are some things that objects are good at like caching state as a model. But even here the FP systems typically do it better via FP actors and (again, impure) message passing, which enforces no-shared memory, and at least in erlang virtual machine systems, couples segregated state with limiting blast radius for your systems failure domains, which is 150% the "right thing to do". Of course if we're getting pedantic, this is even close to OO the way that Alan Kay imagined it, than what the OP and GP talk about, and I don't consider that to be OO in the contemporary, colloquial sense of "everything that happened after Bjarne Soustroup invented C++"
I highly recommend this video for a more philosophical take on how to do "impure FP" without OO that is informed by a decade of industry experience: https://www.youtube.com/watch?v=yTkzNHF6rMs which is by Gary Bernhardt (of WAT fame)
> I look for the right tool for the job rather than some idealistic purity
This is all about right tool for the job. IMO, FP code is easier to read, easier to reason about, easier to maintain. I think it's a shame that FP gets this reputation for being 'about ideological purity'. I like using "working programmer's FP", and the FP that I use are built on designs that are driven by real, customer-use-case driven concerns and real-world constraints, and how to make life easier for programmers and operators.
Why? The very best "OO" interactions I have with a database are via Linq which is a monad [1]. What it actually does is simply build the plan for what to do in the database and actually do the query at the last possible opportunity. Exactly how it would work in a purely functional setting.
[1 ]https://github.com/louthy/language-ext/wiki/Thinking-Functio...
Linq is functional, but functional-impure.
Many of the methods on those organizational classes are implemented in a functional manner.
I rely on the class/instance pattern for working with a database. static class methods for actions that work on multiple rows, instance methods for single row interactions.
This works especially well in typescript where I can set private/protected/public
That said, I rarely use inheritance except when it's obvious - no nested class hierarchies or other such traps.
Thanks for the video, I'll give it a watch.
I think classes without internal state, can be large and still simple to understand. But if many mutable fields and internal constraints, then things can get complicated. Better keep such classes smaller
If an existing services does not match exactly what you need to do, you write a new service and reuse the same repositories.
I think this really hits the nail on the head. Classes are the ultimate DRY tool but it seems like much of our industry is realizing that it's rare for any project outside of small self-contained libraries to reach the point where you know enough about your problem domain to ever start meaningfully factoring code out like this.
Instead having "here are the different shapes of state you might see floating around" and "here are some functions that can do things with certain shapes" becomes really powerful when you need to be nimble with features/requirements. Encapsulation is beautiful when the scope is specified completely up front but it I've found it leads to more complicated code and mental overhead because you create a world where the least cost path to doing what you want does a run around with types.
The problem with classes in languages that use them as a main abstraction (Java, Ruby, etc) is that people reach for them for all abstraction and software design as their Day 1 tool.
All program state and behavior is now immediately fragmented from Day 1 in arbitrary buckets and my experience some moss quickly grows on them and things are never changed for the life of the program.
Just seems your post doesn't offer much insight beyond the classic OOP advice I've heard for years when people realize it's not that great: oh no, you're doing it wrong, don't do the bad things, just do the good things and ensure everyone else is doing them too. I don't see how that's actionable nor useful.
Limiting class use as much as possible and using functional module pattern as TFA does, on the hand hand, is actionable advice for these common issues with classes.
State management is hard. Changing how you refer to the state isn't going to make it any easier, just different.
With a class, however, you normally make the data members private; that is, only the member functions of the class can change them. If the data gets mangled, you now have a very small set of places to look. That difference matters when debug time comes around.
> only the member functions of the class can change them. If the data gets mangled, you now have a very small set of places to look.
But probably anything could have called those mutating methods, so you still need to consider the entire codebase.
If A has B in its prototype chain, are all values of A able to be transparently treated as values of B? Plausibly, this works, but there's nothing that really enforces it. Prototype inheritance is just a way of not needing to repeat code.
I agree it's a bit better than class inheritance, but most of the time all you need and want is composition.
Yes, favor composition over inheritance.
FWIW, I really like Eric Elliott's take on this stuff: https://medium.com/javascript-scene/common-misconceptions-ab...
In an object, all the functions implicitly take that state (or explicitly don't if you mark them static).
So instead of namespace::function(arg1), you now have namespace::function(obj, arg1), even when your function has nothing to do with obj. From the outside, you then have to wonder which members of obj might be being read or written inside the function. If lots of those methods call other methods, which may or may not modify the state of the object, you can end up in a situation where it's hard to reason about the functions because you have to read a lot of code to work out what the inputs and outputs really are.
This is the problem in the Java-style "everything must be a member of a class" approach. People have a tendency to make everything into members of objects.
A function that only takes the values it needs is much easier to reason about.
I think that you are right in that there isn't much practical difference between the two approaches when well-written (in particular when staticness/mutability/constness are clear). However, I find poorly written OO (e.g all methods are instance methods, local variables stored as members) to be really tricky to reason about, and all too common. I haven't had that problem with poorly-written non-OO code.
Perhaps Java would be cleaner if it had free functions. But this isn't so different than a big class that has a pile of static methods.
C++ also addresses the "you have to wonder what members might be read or written inside the function" by encouraging the use of "const" as much as possible. You get really nice invariants preventing surprise state changes in receiver objects and other parameters through judicious use of "const", even if everything is a class or object.
It is if the code was reviewed. If, OTOH, it just grew until something needs to be done (like the object with 60 public methods that I'm currently trying to refactor), you can have a devil of a time unpicking it.
The same could be true where free functions are preferred, but it seems that most programmers seem to have grasped the idea that global variables are hard.
> by encouraging the use of "const" as much as possible.
I'm a big fan of const-correctness. I haven't written C++ in years, but I always used to love putting 'const' everywhere.
There are only two ways in which code review can keep your codebase clean in this situation: Stop doing any other development until it's all nice, or invent time travel.
It also wasn’t supposed to be advice. (Not sure what you mean by TFA.)
By the definitions I'm familiar with, functions would be abstractions of program segments, and variables would be abstractions of program data.
With that understanding, the closest you could get to writing a program without creating abstractions would be to write in assembly—but even if you opt to use as little abstraction as possible, you may just get further from your original recommendation to "Create the literal thing you want to create":
I can see a couple ways of interpreting that statement.
1) Imagine what you want to create already existed: what it would literally be is a block of machine code, so when you write your program you should make it as close to that hypothetical literal existence as possible.
2) What you want to create doesn't exist materially, so its only literal existence is a set of high-level domain-specific concepts (i.e. its existence is a spec), in which case the most literal way to represent it would be through a set of matching high-level concepts defined through a programming language, i.e. a set of matching abstractions.
Under the second interpretation at least, it requires heavy use of abstraction in order to implement the spec as literally as possible.
It's tempting to design a class hierarchy from first principles: everything in the game is an entity, from which players and NPCs are derived. The NPC base class has several derived versions (allies, monsters); the monster class is specialized into animals and dragons, which in turn....
You can often paint yourself into a corner this way, where you suddenly don't want the dragon class to reuse part of the MonsterBase. They're suggesting making the classes stand-alone with duplicated code, at least until you figure out what these classes actually have in common and what is incidental overlap.
But when your public facing methods are big blobs, they tend to have lots of params that cause fairly different thing to happen, and it gets much harder to have testing confidence around that than if you had a bigger set of smaller public-facing operations.
It's a basic philosophical difference in what a unit test should be doing. It goes back over a decade.
I'm a bit in between the way described there, though, since I'll often happily leave other objects mocked as long as I know that my contract successfully called that object. I don't need to test all the way through their own implementation - there should be sufficient testing on that object to drive a refactor of it. I just want to support a refactor of me.
My view is basically: "we're gonna have to restructure some of this code as future requirements come in, so let's make sure our tests ensure that the things we really want to happen do happen" (like an update being sent to the database or other backend service layer).
I have similar hesitancy about applying a lot of design patterns early. Most of the places I've worked have been in the experimentation and rapid feature development stages of the business, with all development being internal development (rather than providing an SDK to others), so being able to change the code is more critical than being able to treat the code as more of a framework.
Seriously, just hire a good senior software engineer
The trouble is that "extends" conflates three different things, and many languages don't offer an easy way to do those things separately:
1. Ad-hoc polymorphism: this thing offers the same interface as this other thing, but with a different implementation
2. Composition: this thing contains this other thing, and uses it to implement its own functionality
3. Delegation: this thing implements this interface by forwarding it to this thing that it contains (possibly overriding some parts)
So you end up with things that need to share implementation accidentally exposing a common interface, or vice versa. Thankfully we're seeing more and more languages offering the option to do delegation more explicitly (Kotlin/Rust-style delegation, Haskell/Rust-style derivation), and to do ad-hoc polymorphism without needing to share implementation (Java-style interfaces, Haskell typeclasses / Rust traits).
1. Polymorphism
class Example {
method be-nice () {
'be nice'
}
}
class Bill-and-Ted is Example {
method be-nice () {
'be excellent to each other'
}
}
2. Composition role Salaried {
has $.salary;
}
role Employee {
has $.title;
}
class Manager does Employee does Salaried {}
3. Delegation class Price {
has $.cost;
}
class Product {
has Price $.inner handles( 'price' => 'cost' );
}
Any call to the `.price()` method on `Product` will delegate to the `.cost()` method on the inner object.I don't understand your viewpoint at all.
The best part about coding for me is getting a review with a lot of interesting comments about how it could be done differently. It jogs my brain down paths I would never have seen by myself.
It's like living in a house: as long as it doesn't fall down and doesn't cost unreasonable amounts to maintain, I don't really care about how well-formed the mortar is, or that the drywall is hung perfectly according to company standards. I just want to live in the house and not worry about it.
* Somebody maintains that code. If it is only you you have a bus-factor of one if none of it is actually reviewed. If it is not you you are imposing a single persons idea of what the business problem is and how it should be solved on the maintainer, which they might not agree with
* Personal development depends on realizing when you are wrong or have used the wrong tool. If nobody challenges your decisions you will probably not realize that as often (or at all) and therefore not improve as much.
* Solving business problems means solving problems for a business. Usually businesses evolve over time, so the business problems might change. Who will understand the code in that case? How do you know the code is understandable to other people if nobody else reviewed it?
And as for the house example: before buying that house I would definitely hire somebody else to check it, and that is legally required in some countries. I don't trust anyone to rate their own work.
In this case the person who hired your consultancy bought the house, and they should definitely have an expert that did not build it check it.
---
Just a FYI: I'm also a consultant, but work on a project where we have discussions about how to structure the code and do code reviews and so on.
Agree that getting a technical review from a third party isn't a bad idea.
FWIW this doesn't change my main point: the business owner cares about outcomes, and not code structure (a good outcome is dependent on the code being good enough to maintain e.g. the house not falling down). There's a simple test for this BTW: write lited, beautiful code that doesn't solve the business problem you were hired to solve and see how popular you are with your client.
> writing code is having it solve real business problems
Sure, but writing good code is about solving future problem the business is going to have too. A problem has more than a single dimension.
If your code will never evolve, if it's a definitive solution to their problems, the structure doesn't matter that's true, but sadly, that's almost never the case.
The structure matters to the outcome; but the outcome problems of bad structure tend to manifest after paid-by-engagement contractors have left.
Is it true? If I read book or blog, I improve my skills. Sure, feedback helps but I don't think that it's a hard requirement.
I didn't quite realize this until, after 5 years of building things on my own, I had a co-founder. I feel like I gained 10 years of experience just from working with him and having to explain my solutions or why I didn't consider this other one, or having to realize that I was in fact wrong. That's a hard pill to swallow when you've only worked with books.
Craftsmanship is more than just learning new things. Sometimes you need to have your entire model of what's best swapped out for new ideas, like society realizing a republic is better than feudalism, than never evolving past your original position.
That said, I think the person you're responding to meant outside feedback as in opinions from peers, which I agree is very powerful, but not strictly necessary.
2) standard practices, idioms, consistency.
You can't use tabs if the entire company uses spaces or use Python if it's a Java shop.
3) Because in most cases, an extra set of eyes can help provide real, material constructive feedback.
I have little trust that any standalone developer is going to just write 'solid code'. Who has that level of self-awareness to recognize they are 'that good', let alone actually be good without help?
It's a basic matter of professionalism to work with others and to be good team members, without it, we're not going to get much done.
For smaller projects with very clear requirements, it matters less.
* defects - The application has certain defects or it doesn't and you can prove it with tests. If there is a defect I will prioritize it, or solve it immediately, or mark it as won't fix.
* performance - Does the application execute fast enough in all stages and interactions? This is a simple yes or no, but performance testing is often volatile so this needs to be tested and measured as well so that it can be observed over time keeping slippage in mind.
* ease of use - Does the application work out of the box? If not its broken. If it requires a bunch of manual configuration then nobody will use unless its forced on them. For this I take all my frustrations with corporate software from my past jobs and I intentionally design against that when I write software and encourage my users to complain to me.
* documentation - Does the documentation stand on its own? Is it complete, well organized, and simple enough that a high school freshman can read it? If you cannot write you aren't as great a developer as you think you are.
Other programmers care. A one/two man workshop doesn't need order or processes to get things done. A ten man workshop cannot function without it. The same is true with coding - it becomes a tool for programmers to communicate intent, it's becomes important to be ordered, clean and understandable by every other dev.
> it's becomes important to be ordered, clean and understandable by every other dev
Code style is the wrong tool for that job. Better are code reviews and written documentation.
There are minor code style preferences and others that affect maintainability. Just because it works now it doesn’t mean the code is done.
I’ve found all kind of bugs just refactoring code because poor code obfuscates them.
Your way of building things will lead to debt and bug reports
Why create a function that shortens/clarifies nothing and is used once? If you want to explain some code, use a comment, don’t make spaghetti
It'd be like telling a construction worker taking a water break: "That's the nice thing about the office jobs. No need to worry about hydration because I'm not out in the hot sun all day." I have a feeling this kind of observation would not be well received.
I did work somewhere during the dot-com years and we had an entire meeting about double spacing. Whether we press enter TWICE after an if-statement bracket, or once. I hated every second of it.
It really depends on the industry your work with, code base size and number of contributors.
Yeah, but they usually have loaded guns to our head so we tend to comply.
Jeez.
I wonder, you've never been critiqued, but how many times have you gotten a project and had to unravel badly written code?
I think the macro-style issues people debate are mostly about code that is being maintained and reworked over and over again in a product with a long life.
> I'd go nuts if someone told me I had to use more classes, or a certain language, or tabs over spaces, or whatever.
Those issues all have root causes you must have dealt with.
Tabs vs. spaces type stuff is mostly about keeping diffs clean in a repo.
Language choice is just a matter of components being able to work together, and being able to hire people who can work on it.
And choices like "more classes" are usually revolving around testability of code.
> I don't know how you guys put up with it
Absolutely, maintaining a product over a long period of time can be exhausting.
I think there are benefits to it, though. It's extremely gratifying when you've written something that someone else builds on and they actually get the structure and their new code fits in very closely to how you intended.
The whole thing kind of becomes your baby, albeit a baby that you're thinking, "one of these days I'm going to cut her head off and stitch a new one on and she'll be way cuter."
I guess, some people just better left alone as long as they can find someone ready to pay them.
The downside is that I can't really write big projects. My biggest projects are few dozens of thousands of LoC and typical project is few thousands of LoC. They are pretty useful to end-users, though, but participating in some huge project with millions LoC probably would be an interesting experience that I won't encounter.
There's a chance you will learn
- how to handle criticism professionally;
- that you actually write more buggy code than you think;
- that there are a lot of critiques that are not subjective that you simply currently cannot imagine (because if you could, you wouldn't get it wrong in the first place, right?); and
- there are some subjective opinions which you didn't know about but agree with once you do.
type MyType is range min .. max
There's already a built-in for positive integers, which is defined as subtype Positive is Integer range 1 .. Integer'Last;
Note the subtype there. Ada recognizes that a positive integer is a type of integer, but not the other way around. And it enforces that in the type checking: You can pass any Positive into a function that accepts Integer, but you can't just pass an Integer into a function that accepts Positive. This happens even though they're not classes and this isn't OOP. Ada does have object-oriented constructs, but they are a later addition to the language. I have never used Ada professionally, but my understanding (based on book learning) is that it tends to be used conservatively.It's similar in OCaml. Despite the O standing for "object-oriented", creating classes isn't necessarily considered idiomatic. The other tools in the chest tend to be conceptually simpler, and therefore to be preferred when they will get the job done.
"define your own operations" is a requirement I'm having a hard time making sense of. To me, that is just another way of saying, "define functions", which is a feature of every language I've used except for one really ancient dialect of BASIC.
Every so often, I wonder if I should spend some time with Pascal.
I'm going to stop you right there, because plenty of languages (especially functional ones) have ways of declaring custom complex types without using classes.
I'm not too experienced with this though, so this is pretty much the extent of my knowledge on this topic.
struct PositiveInteger(u32);
Then give it a constructor (which in rust is just a regular static function) that checks the non-zero variant. You can define method on this type, and make it implement traits (which are kind of like interfaces).The best bit: there is zero runtime cost to this, the memory-representation of this type is identical to that of the underlying u32.
Rust-style enums which can contain data, and also have method implemented on them are even better. Doesn't mean classes (structs in Rust) aren't useful, but once you use a language that allows you to define other kinds of custom types, they seem very restrictive when they're the only available tool.
Having the functions that operate on the struct attached directly to the struct declaration, vs having some functions that the first parameter is the struct on which the function operates, doesn't seem like a particularly meaningful distinction to me. OK, you like C-style programming in favor of C++-style programming, congrats. It's still a class either way.
Enums are are the better example of non-class types. For example, you can have:
enum StringOrInt {
String(String),
Int(u32),
}
And you can go ahead and implement methods on that type. Classes have "AND-state", not "OR-state". But a Type in general can have either kind of state.With design by contract you can put in whatever fancy constraints you want on function parameters and return values, and those will be enforced.
As far as objects go, they're much more useful for me as just a means of passing state. Rather than using a bunch of global variables or having to pass in a ton of function arguments, I can just use an object which contains all the state I need.
Of course, having lots of state can usher in its own set of problems, and there's something to be said for trying to make your code as stateless as possible. But sometimes you need state, and maybe even a lot of it.
I'm not an Ada expert, but it has excellent support for range-restricted integer types.[0]
Ada's 'discriminated types' are also fun. They let you create members which only exist when they're applicable. [1]
> Do they also allow you to define your own operations on those types
Looks like Ada supports operator overloading, yes. [2]
[0] https://www.adaic.org/resources/add_content/standards/05rm/h...
[1] https://www.adaic.org/resources/add_content/standards/05rat/...
[2] https://www.adaic.org/resources/add_content/standards/05aarm...
interface DuckTypedObject {
quack: true
bark?: false
eyes: 'beady'
}
This will require that any object used as a DuckTypedObject must have the `quack` and `eyes` properties and may optionally have a `bark` of `false`, but doesn't otherwise prescribe what the object actually has to be.However it is worth keeping the following in mind. The compiler will check that in your code that these properties are present and assigned correctly. However at runtime nothing is guaranteed especially when dealing with the DOM.
One of the things that I don't like about TypeScript (I have written a fair bit of it) is that you believe you have type safety when it is really type hinting.
function someFunction(obj?:DuckType) {
//Snip other logic
someOtherFunction(obj);
}
function someOtherFunction(obj:DuckType) {
}
The type checker catch that. So you still have to do: function someFunction(obj?:DuckType) {
obj = obj || { /* set some object properties */
//Snip other logic
someOtherFunction(obj);
}
function someOtherFunction(obj:DuckType) {
}
I have found plenty of examples where people haven't set a default value because the compiler hasn't flagged anything wrong with the code and you get an uncaught reference error.[1] https://news.ycombinator.com/item?id=21476261 [2] https://lexi-lambda.github.io/blog/2019/11/05/parse-don-t-va...
I dislike this example. The numeric systems of every programming language I've ever used has been (more or less) terrible, precisely because there are extremely common and simple arithmetic types, just like this, which it's terrible at representing. Half of the "modern" languages I've seen just provide the thinnest possible wrapper around the hardware registers ("Int32"!).
(What if I need to accept an integer in the range 5 < x < 10 instead? Am I supposed to define a new class for every range?)
Instead of saying we need a system of user-definable classes so every user can fix the numerics on their own, for each program they write, I'd say we should fix our numeric systems to support these common cases, and then re-evaluate whether we really need classes.
Are there non-numeric analogues to this type of value restriction? Maybe. It doesn't seem like a very common one, but it is an interesting one. Perhaps what we really want is "define type B = type A + restriction { invariant X }". I can't think of any examples offhand in my own work, but that could be because I haven't had that language feature so I wasn't looking for places to apply it.
One being the idea of types as describing shape of data. In the best of cases perhaps some semantics tied to the data (how the bits are to be interpreted)
Than there is the the other view, the Curry-Howard one. Where types describes proofs and invariants of the program it self, and how interesting properties of program compositions can be ensured.
It seems much time is wasted when people holding one perspective debates with people holding the other.
Perhaps we should have separate words for these concepts.
Watch out, you might make topmind mad! https://wiki.c2.com/?TopMind
The big benefit of objects, and therefore classes (or something like them), is polymorphism via dynamic dispatch; it seems a shame to throw the baby out with the bathwater because you don’t like inheritance or mutable state, both of which are independent of the choice to use classes.
Event when using interfaces many times it’s seems possible to arrange things such that parametric polymorphism would be able to use a static type. There are some boiling proposals for future versions that will help with exactly this, and indeed some of the latest changes to the languages have included allowing more static dispatching in polymorphic constructs like disposal and enumeration.
I’m beginning to suspect that dynamic dispatch could be entirely dropped without losing to much expressiveness.
Have no experience with Rust but isn’t that kind of the conclusion from that community?
From using it personally, I've found that I can usually get by with generics and the static dispatch that they provide, but having the option to use trait bounds can be a real boon when you need the flexibility to call on shared pieces of functionality between separate complicated systems.
[0] https://doc.rust-lang.org/1.30.0/book/2018-edition/ch17-02-t...
It's a feature that, when it fits your problem, can be extremely useful. I have rarely had it fit a problem. The ones it fit, though, it was nearly magic.
both of these predominantly statically-dispatched langs added ways to do dynamic dispatch later on, which seems to go against "dynamic dispatch could be entirely dropped without losing to much expressiveness". i prefer static-by-default as well, but it looks like it's hard to do without sometimes.
(of course you can always roll your own vtable-ish thing, so technically you could drop DD from the language without loss of expressiveness, but then you'd likely get multiple incompatible libraries for that...)
and i'd guess that some sort of dynamic dispatch is unavoidable if you want to do some kind of plugin architecture
simple example copied from the linked article:
data Obj = forall a. (Show a) => Obj a
xs :: [Obj]
xs = [Obj 1, Obj "foo", Obj 'c']
doShow :: [Obj] -> String
doShow [] = ""
doShow ((Obj x):xs) = show x ++ doShow xs
we can have a "heterogenous" list of `Obj`s, like a java `ArrayList<IPrintable>`. each `Obj` is basically a pointer to the actual value of some type T and a pointer to T's Show dictionary (vtable). Which is afaik analogous to how OOP languages do it* encapsulation
* message passing
* late binding
Things like inheritance tend to muddy the waters. If you aren't benefiting from the above 3 things, then you are probably either using objects wrong or you are applying them to a problem for which encapsulated, late-bound message passing isn't a good solution.
As a corollary, state management and OOP are in my opinion orthogonal. You can write and program with immutable objects just as you can with mutable ones.
I do agree with some other comments that OOP in college isn't taught very well. Most people leave their first OOP class not really knowing anything about OOP or why it's useful. My hunch is that OOP is something that should be taught later in a program, not earlier.
* they are encapsulated. You don't know how the data is stored internally.
* There is message passing involved (at the JVM level). The methods are virtual and you don't know exactly which method is being called. There can also be multiple implementations using different data structures.
* Late binding is again as defined by the JVM (interface table method lookup)
In contrast, a static function is no different than a global variable. When you call it, you know exactly (sans JVM linking) which function you are calling.
doStuff(&fooStruct, ...) vs. fooClass.doStuff(...)
And how would you easily create that state?
https://wiki.c2.com/?ClosuresAndObjectsAreEquivalent
The venerable master Qc Na was walking with his student, Anton. Hoping to prompt the master into a discussion, Anton said "Master, I have heard that objects are a very good thing - is this true?" Qc Na looked pityingly at his student and replied, "Foolish pupil - objects are merely a poor man's closures."
Chastised, Anton took his leave from his master and returned to his cell, intent on studying closures. He carefully read the entire "Lambda: The Ultimate..." series of papers and its cousins, and implemented a small Scheme interpreter with a closure-based object system. He learned much, and looked forward to informing his master of his progress.
On his next walk with Qc Na, Anton attempted to impress his master by saying "Master, I have diligently studied the matter, and now understand that objects are truly a poor man's closures." Qc Na responded by hitting Anton with his stick, saying "When will you learn? Closures are a poor man's object." At that moment, Anton became enlightened.
I feel like they're but one tool, and necessary in limited cases.
For example I recently encountered an API that had to intake 100+ key/value pairs and manipulate these values, and send them to various places.
I tried to do the mappings and data manipulations and everything in one long piece of code functionally but it led to some serious spaghetti. When I separated things out into classes, it was much cleaner code, with a more well defined domains. It was definitely not perfect but ended up being much cleaner. This is a simple example but classes can be useful. It's arguable that the API could have been better written and that's true but sometimes you have to work with what you're given.
Otherwise I would venture to say most code doesn't require classes.
Functionality like mapping the keys to new names or uppercasing certain specific values or sending the data to certain places.
You could have the functions and parameters all in one long piece of code, or you could put them into domains which makes dealing with it, reading it, understanding it, and working with it easier, in my opinion.
Personally I take the loosest possibly view, mostly just "associating data and methods to operate on that data fairly tightly together, where they all get passed around as a unit". Some ability to have "x.y()" result in multiple possible implementations of "y" is necessary, or you just have a weird way of spelling function calls. That's about all I'll require to call something "OO" nowadays.
Not sure how other languages do it, but in my daily work the only time I use inheritance is with an ORM , to get the magic to work you need to extend the base Model class , add a few lines of configuration code and you are done, for more advanced uses I think you have to override some methods.
But except the ORM code I don't think I used inheritance in a long time, though I used interfaces in some other cases.
Basically, GUI frameworks are the only area I know where OOP - real, authentic long inheritance chains with encapsulation and extensibility - has worked well.
Could there be some insane case where the OOP of the GUIO widget is problematic, maybe , like when you want your app to have a window in the shape of a circle ... but I think is fair you get easy to use library for 99% of the cases and for 1% of the cases you might have to get your hands dirty and go outside the standard ways and maybe look under the hood.
If we consider a game like Minecraft, I would use something like a base class for blocks and a different base class for mobs, then if you add a new feature you put it in base class and all the objects will get it for free. I am not sure how Minecraft is architected but I loved how all entities including the player are the same, if you can do something to a player(push, drawn, burn, catapult, activate circuits , etc) all mobs would work the same.
I think GUIs work well because is a very well known and studied problem where with game engines you have a generic problem and then each game is bending the engine to try to use it for different type of games that the engine author did not consider at the beginning.
My conclusion is that some OOP is a tool you can use, you should not avoid it because a dude in a blog said so,the same with GOTO - I think I only used GOTO once , it was more efficient and more readable to use goto there then trying to workaround using it by creating variables and then adding checks (the problem was to efficiently break from inside 2 or more nested loops efficiently)
Classes, on the other hand, are not structs with functions. More like black boxes whose contents can be swapped out at any moment. OOP is about hiding implementation and infinite extensibility.
When you have 100+ parameters to deal with it helps in multiple ways to separate those out into domains and classes are a useful tool for that.
they even had to dance around how to have a main entry point in class only languages like java
the early 2k were really religion blinded around a class god, without teaching the real values and uses of classes in the context of problem solving
OOP is bad because inheritance is not as good as interfaces.
OOP is bad because typically it puts state changes inside mutable, shared objects, which makes concurrency difficult.
Java has interfaces and generics -- use those instead of inheritance.
C++ has templates -- use those instead of inheritance.
Design and implement interfaces that operate on immutable, preferably linear data types, and concurrency will be easier.
Essentially, what I'm describing, is just functional programming where f(a, x) becomes a.f(x). I find the latter easier to read and lends itself to a fluent, lightweight style.
In general, I don't really think the discussion of OOP vs FP or whatever is that meaningful. What is meaningful is mutable vs immutable. Every great developer I've met has agreed with me that mutation should be avoided as much as humanly possible: really the only valid reason to introduce mutation in your program is for performance reasons. And yet, we as an industry still are by and large writing mutation heavy code for no reason at all. Things have gotten a lot better in the last 10 years, but we still have a long way to go.
Edit: I'm mostly talking about Scala in this post, btw. I also want to note that I do think deep class hierarchies are a good solution to some problems. The two examples that immediately come to mind are UI APIs and collection libraries. Both these problems are very naturally solved by OO, the essence of which is the deep (as much as necessary) class hierarchies. Very few problems I've encountered besides the above actually require these subtyping.
An great alternative approach is row polymorphism, ala OCaml. I've not had any professional experience with OCaml or row polymorphism, so I'm not sure how well it actually works in practice.
Maybe I'm bikeshedding, but I think this is a nontrivial part of why people like OOP languages. Being able to read right to left, without nested parens, makes code comprehension easier.
I think the mathematical notation for function application has held programming languages back. I realize that there's a huge benefit to uniformity of syntax across languages, and that syntax is ultimately much less important than semantics, but I wish we could settle on something better than f(x). I really appreciate languages that are willing to do something different (e.g. haskell and lisp).
f(g(h(x)))
x.h.g.f
x |> f |> g |> h
I think most people prefer the second one to all the rest.
x |> h |> g |> f
Which is identical to the second, except for a more eloquent choice of the pipeline symbol?
I dislike that.
When I see a class, I expect it exists because there is a central state around which the methods revolve, and are meant to expose or change it.
When I see a function, I expect it to be side effect free.
Sure you can use any paradigm to make any program, but I use paradigms, just like I use design patterns, to communicate intent. Solving the problem is half the battle. In a way, coding style is documentation. I think we should build boring API, and only use cleverness to find solutions, or make the API easier to use and more elegant.
I worked once on a code base that broke a lot of implicit expectations. Like, it could add objects in a registry when you instantiated some classes, get_stuff could set other stuff, do_stuff would not do stuff but actually_do_stuff would, etc.
It was a nightmare to work with, because anything could happen anywhere. You had to look at the code every single time and keep the whole system in your head, which had 900k lines of highly technical code.
In java, inheiretance is directional in a single way, but if you instantiate a singleton component, you can end up affecting the state of every other class that uses that same component. This is not wrong per se, it is a property of OOP in itself.
If this rubs you the wrong way, possibly you should be working in purely, monadic functions.
Having a single reference is useful, but using instantiation as a way to get it has proven a bad design decision.
If done properly as described, the functions accept state. This means they adjust state by adjusting input/output parameters, not by affecting some internal state. This is basically Dependency Injection extended to state. This is also how classes are implemented (see python's self parameter to all class functions).
While in Python, when you see a class, you just know the methods are mostly about "self".
It helps making sense of the code immensely.
For effectiveness, I think it reduces ancillary class members, and encourages (necessitates!) dependency injection.
For example, changing "delta t" in a game object update is trivial: update the one delta-t you have, and pass it into everything that needs it. With classes, there may be a temptation to have a delta-t member variable, which costs many updates. Stupid example, but a mistake which is not possible with Dependency Injection.
But really, proper scoping in typed languages (and naming in dynamic ones) can allude to input vs output parameters. In C/C++ this is passing by ref vs value. (In Google C++ style guide, they recommend use of pointers for "adjusted" or "output" parameters.) In MS C++ from back in the day, I think they had ways to explicitly specify parameters as input, output, or input+output. Does someone have an example of this?
It would also be nice to prevent functions that are not in this group to make changes to that part of the state.
Then you could prefix all those functions with a name, so it's clear what's their scope.
Finally, in some cases it would be great if you could create multiple independent "instances" of the state, so you could manage more than one at a time without risking conflicts between the various copies.
I work on a project where I framed up both patterns, and ultimately decided that the hodgepodge^ pattern was preferable to repeating long lists of arguments.
^ kinda like the mixin pattern but I think it's been taken too far
These functions accept state and other dependencies
That "dependencies" term is very loaded. You need to define if you call "dependencies" what the functions in your module take as argument or you're talking about those other modules your module calls. When I see a function, I expect it to be side effect free.
Why? though I think it's a commendable goal it's not a reasonable expectation from most languages.In contrast, I cannot image being able to get far in Java without using classes. The concept of using classes is central to that language.
Well you literally can't write a Java program without writing a class.
This is why (at least in the Ruby world), it's generally considered best practice to avoid using inheritance and just use mixins instead (whenever possible).
Completely avoiding classes because you are using them incorrectly is kind of like throwing the baby out with the dirty bathwater. Don't throw away your tools just because you are using them incorrectly!
My notion of composition with classes would be:
class Dog(object):
def __init__():
self.wagger = Wagger(); #composing the Wagger class
in python mixins look like this: class Dog(object, WaggerMixin):
...etc...The argument now is different. There is another flaw with classes and that is mutation of internal variables with getters and setters. Classes typically allow you to do this and promote this behavior, structs do not typically allow you to do this and are not used this way, typically.
It makes it so that a class is more than just a data type, it is a mini program with it's own internal state and api that you are passing around in your program. It ups the complexity of your program by 10 fold.
Any time something threatens to be a mutable class-level variable, I refactor that out of the design before proceeding. Most often this refactoring is to separate the data into a struct-like class and the behavioral methods into a static class, with what would have been that mutable class-level variable instead passed in to each behavioral function that needs it.
When I was studying OOP at university, there was an entire course on UML diagrams and that taught people how to structure OOP software. Good OOP design tends to produce elegant-looking UML diagrams with relatively few interconnections between components.
I don't always physically draw UML diagrams, but when I visualize a project in my head, it looks very much like a UML class diagram. I can't imagine how it's possible to reason about complex software in any other way TBH.
Sadly, if I had to draw a UML class diagram for most real projects which exist today, the relationship lines between UML classes would be crossing all over the place and it would literally look like a tangled mess (definitely more like a graph than a tree). The people who designed the software had no vision for it, they just kept adding stuff without ever actually understanding how it all fits together or thinking about how to minimize complexity and make it more elegant by minimizing the number of components and the interconnections between them.
I think that, in a way, functional programming is more forgiving when it comes to allowing people to keep adding stuff on top without having to visualize the system as a whole. It doesn't mean that FP adds any net value over OOP. I would argue that being able to clearly visualize the whole product and the interaction of data and logic is essential regardless of what paradigm you follow.
Objects, are like little minature software programs, inside of a larger programs.
Think fractals, that is, smaller things inside of larger ones...
So why have a minature software program inside of a bigger one?
The short answer is boundaries.
If the larger program is permitted to modify the internal state of an object, then that's really no different than having one big program where all of the variables are global, and any line of code can make any change to any variable at any time.
Is that OK? For small programs it is, but the larger the program, and the more programmers that work on it, the more it must be segmented into boundaries, and these boundaries enforced (aka, encapsulation) in order to prevent bugs.
That, and the ability to divide code into logical groups which can be individually tested will yield a great deal of clarity and information about a codebase, when done by a competent programmer or programming team.
At 100,000+ lines of code (and sometimes a lot less), you will trip over your own code unless you segregate it into seperately manageable sub-systems with clearly defined boundaries. "Divide and conquer" as the old saying goes.
On the other hand, programmers inexperienced with Object-Oriented development can make code more obscure, unreadable, and harder to work with...
It's a double-edged sword...
Use wisely.
Why do you think classes are the best way?
1: Basic, Logo, C, Pascal. Logo and Basic I learned 2 fairly different dialects of even (Atari/Apple and GW/Quick respectively).
Unfortunately it's not very popular; the only supporting languages I know of are Common Lisp, Julia & Perl 6.
I've designed and implemented several scripting languages [0] over the years, but they all have this pattern in common, I've never felt tempted to implement classes.
[1] https://en.wikipedia.org/wiki/Multiple_dispatch#Python
[2] https://stackoverflow.com/questions/26303353/can-multiple-di...
It's wearing OOP right on its sleeve in the name of the language, but its users inevitably say "Oh, we never (or hardly ever) use that part".
It turns out modules are indeed a better solution for, well, modularization.
OCaml is multi-paradigm in the most broad sense of the word I've experienced, just with functional and immutable as the default rather than the other way around. And it has a pretty great OOP implementation too.
Suggested reading for those wondering why the community deemed the O in OCaml mostly unnecessary and how rest of the language helps achieve the same goals in a different/better way: https://discuss.ocaml.org/t/objects-use-cases-in-ocaml/2282
The response to this is not to throw out all chisels.
The response is to learn to use various tools and once you know how to do this, how to apply different tools to different types of problems.
There is no shame in overusing a tool for a period of time. The novice carpenter must be able to play to learn limitations of tools but also to learn which inventive ways to use tools are actually helpful.
Master carpenter will understand that playtime is necessary step in getting to mastery and will not call out novices for their overuse of the chisel but instead will encourage play with the intent to speed up education.
Sounds like they need to pay more attention to the Open-Closed principle :)
It's not really clear to me how a random grab-bag of functions is better than a random grab-bag of methods. If the functions in the too-large module can be broken up into small units without damaging cohesion, why can't the same be done for the class with two many methods?
Our approach for managing this complexity is to simply say that each class in the domain model aligns to the bounded context of a particular business unit, process, or activity. So, instead of modeling a bunch of different types within some namespace, the type itself represents the entire extent of that business concern, including any business facts as instance properties. Complex types are modeled as nested classes within each context. Nothing is shared between contexts except for trivial domain-wide concerns in the outer scope. This allows for very easy serialization of business state in and out of a database (among many other things). Consider the power that a method override might have in this realm if you wanted to construct a hierarchy of such contexts.
An example of one of these concrete types might be SelfCheckoutContext (if you were working in a retail business domain). This type could be derived from a wider CheckoutContext which could be simultaneously shared other concrete contexts like HumanCheckoutContext.
In our approach, there is also a lot of stuff that exists outside of each context (Views, Services, Repositories, etc). We basically treat these items as a platform layer for the contexts to operate on top of.
Bounded contexts are IMO the most powerful abstraction out there for accurately constructing a very complex business system. They directly get at the root of the problem, which is aligning the implementation with the same grain as the business. They also permit complete isolation of various business units which might have unique perspectives into what would otherwise be shared (and increasingly-convoluted) models. In our latest implementations, models are never shared between contexts. There might be a few models defined in the outer domain scope for things we believe apply universally to the application. Any cross-context communication is achieved via simple mappers.
But the class based architecture doesn't necessary have to be as bad as described in the article.
Inheritance is somewhat of the old school of OOP, modern OOP rarely use inheritance, it uses composition, just like a functional code base. In my projects my classes are always marked as final. Can't even remember the last time I inherited a another class, maybe if I interacted with some legacy code.
It is important to separate your classes, what classes can have state & what classes can not have state. Classes with state should be small & classes without can be large, because stateless classes can easily be reused.
Classes have the main advantage that you easily inject dependencies in the constructor (composition), e.g. a repository depending on the database driver.
You can do that with functions as well, e.g currying, but I find it a bit more messy if your language is not specialized against that use case & with a class you "curry" all class methods at once. If you don't inject your depencies in your functions you will have a much harder time to reuse that function & test it.
I agree with that class based GUI code rarely works, this is because GUI (if it is HTML or OpenGL) does not translate well to a class with hierarchies. GUI code have tendency to more be procedural.
My guess is that this is a similar case in Typescript that the author is describing.
So, this approach is, instead of having IFooService with two methods, IFooService.doOneThing and IFooService.doAnotherThing you just have a package "fooservice" with functional interfaces DoOneThing and DoAnotherThing. This seems to still play nice with most of the tooling, and often, keeps the code simpler and makes it easier to refactor. DoOneThingImpl and DoAnotherThingImpl can easily share code, but that aspect shouldn't matter at all to the caller.
Another way of thinking about it: don't worry too much about a perfect interface for layering your business logic initially. Start with just some functions, then as you spot patterns you may recognize there's a state pattern that does things better. I don't think this is really specific to Typescript.
Consider this simple aspect: one of the key differences between a class method and a separate funcion is implicit (and sometimes explicit, though that's not much better) self/this parameter that allows access to all object fields. Essentially you're opting in to grow the input of every method call by all the data held by an object. That's a mistake on many levels:
- it is one of the most important "best practice" advice to only pass the data that function needs to do the job, with classes you're breaking that advice immediately
- ease of access to additional data doesn't leave room to think if it's actually good idea to do so, factor in laziness and inexperienced contributors and you get methods that grow in complexity much more than they should
- testing anything becomes torture, as unlike with simple function class methods now depend not only on incoming arguments but also on state of the object itself, i've seen tests with dozens of lines of setup for single line of test
Prefer simple pure functions, prefer builtin data types, prefer immutability, only pass arguments that function needs to do the job.
Why would users of that function bother to know what data it needs to do its job? It seems you'll end up polluting your whole code with the implementation details of one of its components. What happens of you want to change those implementation details?
Yes, it’s a spectrum and there’s a lot of space in between those alternatives, I simply believe being closer to the first option is better than to the second.
Pure functions are conductive to transparency, objects, methods and class hierarchies are conductive to obscurity.
The thing is, it doesn't prevent you from creating classes or methods that are open for inheritance, nor does it prevent you from allowing mutable state, but it does use the defaults and the language design to encourage limitations that are usually good.
Perhaps they could take these concepts a step further by making classes data classes by default, having a keyword for it to not be a data class.
I avoid inheritance because inheritance increases complexity. By complexity I simply mean it in terms of complicate, or to make many. I am not speaking to anything related to challenges, easiness, or hardships. Inheritance is a practice of a concept called polyinstantiation, which allows easy dynamic replication of an object in memory so that many child or cloned objects are independently extensible.
I generally don't do inheritance apart from a few weird cases because composition over inheritance works way better.
This is a common pattern I've noticed in discussions about OOP. There is a tendency to reduce it to "inheritance," which turns the conversation into a straw man: "here is why inheritance is bad, therefore OOP is bad".
Same goes for identifying classes with inheritance. Their primary feature is grouping of methods and fields into (potentially) encapsulated units, and being a mechanism for abstraction: classes are more general than their instances and serve as a general pattern; upon construction they are 'configured' through parameters to their constructors and particular forms are arrived at.
Well, looking again, I do use a lot of structs (or their moral equivalent, using 'class' to define a struct with some protected state) but don't use inheritance much.
The problem with OOP is that it assumes you need lots of these parts. Whereas really, you might have a few or possibly a handful. Use, class-like constructs to represent these, and use normal functions within these, with ideally some kind of polymorphism that also doesn't require classes.
For small programs, don't even bother with this. Just use pure functions for key logic, and unit test those, and delay testing anything with state for as long as possible. When you do need to, leverage interfaces and stubs/fakes (not mocks) to test them.
That's what I do now anyway.
It takes a long time for a lot of developers to realize anything you can do with classes you can do without. And if you structure your code without classes, it tends to be more refactorable because moving chunks of code in and out of different contexts is a breeze.
The only things I find potentially useful about classes are:
- portability (but you can get that with modules, caveat that not every language does modules well)
- auto-complete in a noun->verb context
The author writes (as an example of his problems relying on classes):
Well, some tables when updating need to do more than just “update” a record, such as insert a log into a side table.
I do not understand exactly if there is a problem here, or anyway I do not understand what the Functional alternative would be.
Assuming this is a problem, I suppose we have three choices:
a) You have to provide implementation for two distinct methods: - Update(...) and LoggedUpdate(...) (the latter calls the former and then logs the result, most probably).
b) You decide that update() implicitly logs maybe based on a global configuration or similar mechanism.
c) You provide a boolean flag as parte of the method signature update(log).
What would be the proper Functional approach here?
(Caveat - I have no experience with Functional programming)
Use classes, don't use classes. It doesn't matter.
BTW, I finally responded to your comment from the other day; sorry about the delay!
> BTW, I finally responded to your comment from the other day; sorry about the delay!
Cheers! I'll go check that out :-)
Yeah, you would have to spell it out and you couldn't do compile-time optimizations, but you could get all the dynamic flexibility you need?
Really, what I was saying above is just that if your problem is easier to describe as a bunch of interacting "objects" (like a game or a factory sim) then use OOP, but if it's easier to describe as a set of functions and data structures then don't use OOP. :-)
(Actually existing implementations of Pascal usually added a function pointer type of some kind, but I'm talking about the language standard.)
I think the crucial question is whether the objects have different behavior. If all the objects implement a particular operation in the same way, or in one of two or three ways, there's no advantage to OOP. If their behavior varies in an open, expandable way — like, you might have an arbitrarily large number of monsters whose movement strategy varies arbitrarily — then OOP, or function pointers, can be helpful. If their behavior needs to vary in orthogonal and arbitrarily combinable ways, OOP may not be expressive enough, and maybe you should look at an entity-component-system architecture.
I agree, if your code doesn't need objects, there's no advantage to OOP. and ECS is like extra-OOP. :-) (I replied to your reply in the other thread, BTW.)
What I was saying about Pascal is that instead you'd have to have a function that dispatched on the receiver type, not the message type. I thought you were saying that the Oberon OS was doing that, even though the Oberon language supported function pointers.
https://www.quora.com/Is-senior-full-stack-engineer-Ilya-Suz...
Be sure to read the comments, it's pretty interesting.
The whole OOP framework is a bit muddled and to make it work you end up in this web of contradictions. You suspect there is a better way but until you find it, you use Java or C# because that’s what your company/tools use
And no, I don't mean DI frameworks like Spring.
I mean simply the dynamic (i.e. runtime) composition of program behavior, typically by passing collaborating objects into the constructor. (Functional programming languages have something similar with higher-order functions.)
Contrary to prior claims, objects aren't great at representing real world entities: to do that you would also need to add a notion of time and concurrency to those objects (e.g. actors, processes, services). However, they are a great way to represent the components and capabilities of such entities.
class Stats
sum Num = 0
sum_squared Num = 0
count Num = 0
acceptNum(x)
sum += x
sum_squared += x ^ 2
count += 1
mean()
return sum / count
variance()
# I think this is right...
return (sum_squared / count) - (sum / count) ^ 2
But that could just as well be: coro Stats
sum = sum_squared = count = 0
result = nothing
loop
msg = yield result
switch msg.name
case acceptNum
sum += msg.x
...
case mean
result = sum / count
...
We don't do objects like this. We really want a file object, for instance, to be something like: coro FileReader
# Not opened yet
raw_handle = popen(...)
# Reading
...
if msg.name == read
yield read(raw_handle)
...
# Data exhausted
...
if msg.name == read
yield eof
...
# Handle closure
...
if msg.name == read
throw ReadOnClosedFile
...
(I think you could handle inheritance with a scheme like this, presumably a "subclass" would simply yield to the "superclass".)What's nice about that is the state is explicit in the structure of the coroutine. Ideally, we'd have some sort of protocol types that reflect that we're accepting different kinds of messages at different stages in an object lifecycle. In this example, after a file closes, it's a type error to try and read from it.
Instead, we generally munge it with properties, depending on lots of checks instead of structure.
We can solve state through more classes, of course. The Builder pattern handles a specific case of this quite nicely, you have one mutable construct that then returns an immutable construct.
But to use that more universally would lead to a profusion of classes.
If it’s your library and your responsibility then I will try to read it your way. Comprehensive testing and consistency in style are more important than any specific syntax or abstraction.
When writing Java code I prefer to use public members in my data classes and only use getters/setters sparingly. Also, I prefer to have the logic in until classes if I can. I suppose I'm not a huge OOP fan.
Of course I have to follow whatever patterns my teammates follow and hardly anyone writes Java like that... But my personal projects I think have much cleaner and easier to understand code with this approach.
There are types of classes that are hidden and cannot be re-instantiated.
To me, those are modules and the main execution environment.
Whenever i've been caught out when coding its always one or both of these things, so i always wrap these in a class otherwise when it comes to reusing something multiple times you're caught short.
So, i always use classes for base functionality.
But i don't throw them around like sugar candy.
https://www.youtube.com/watch?v=o9pEzgHorH0
It's about Python, but the observation can apply to several languages. Pithy quote:
"I hate code and I want as little of it as possible in our product."
I know a girl who
wrote some code
she'll make a linked list
she'll add some nodes
But she don't use classes
and she don't use C
she don't use Haskell
or any of these
she uses assembly
assembly
assembly
Using namespaces with state passed to the relevant function ends up being basically just as easy, and sometimes easier as you end up using more closures and can access enclosed variables directly.
Classes in languages designed for OO work a lot better.
I myself use classes just fine. I love CLOS and I'm having a lot of fun with OOP on Forth as of late.
Say if you practiced procedural programming in a very heavy way and typed functional programming in a major way along with OOP then you would have a better picture.
If you practiced OOP exclusively for the majority of your career I would argue your view point is flawed and biased. Over the years, as a programmer with a lot of heavy experience in all of the paradigms mentioned above, I have found that OOP is in fact not an apples and oranges comparison to other programming styles but is in fact a highly flawed paradigm.
I think OOP people should be aware that there is significant backlash against OOP among smart people who are very familiar with the paradigm and they should take the time to understand why.
The division is real and given the invention of languages like GO and RUST that do not have classes I would think really hard about the topic.
If you are using GO like you use OO, you've missed the entire point.
You literally said go removed virtual methods. This is not what pike is talking about. Pike is talking about putting a function onto your struct. This is legal in go as functions are first class but rob is saying it should be done sparingly because data and behavior need to be distinct concepts.
It is not me who failed to parse your writing, but you who failed to understand pike and my comment.
OK, cool.
[The Pure Function Pipeline Data Flow v3.0 with Warehouse / Workshop Model](https://github.com/linpengcheng/PurefunctionPipelineDataflow)
1. Perfectly defeat other messy and complex software engineering methodologies in a simple and unified way.
2. Realize the unification of software and hardware on the logical model.
3. Achieve a leap in software production theory from the era of manual workshops to the era of standardized production in large industries.
4. It's the basics and the only way to `Software Design/Develop Automation (SDA)`, SDA is an innovative and revolutionary approach to develop large-scale software, just like `Electronic Design Automation (EDA)`, because it systematically simulates an integrated circuit system.