Why Arc Isn't Especially Object-Oriented (2002)
paulgraham.com
paulgraham.com
> Object-oriented programming is popular in big companies, because it suits the way they write software. At big companies, software tends to be written by large (and frequently changing) teams of mediocre programmers.
And from the linked analysis by Jonathan Rees:
> This is related to Lisp being oriented to the solitary hacker and discipline-imposing languages being oriented to social packs, another point you mention.
I would guess that most of the world-changing programming work that can be done is work that requires collaborating with other, unknown people. (But not all! HN itself is a good counterexample, being the work of very few developers and still having a significant influence on the world.) You'll write something at a company, get reorged away (or leave for another job), and someone else will launch it. You'll write some open-source library and years later someone will make something cool with it. You'll contribute to an existing large-scale project, because that gives you leverage you couldn't get from your own project - perhaps because of an inability to replicate the technical accomplishments of the project, but more likely an inability to replicate the network effects of it already having users. You'll build a prototype of something that proves something is doable, but gets significantly rewritten before it goes into production.
If you're interested in these problems, it's in your interest to work in a language that lends itself to other unknown people picking up your work and running with it. If you want to make single-hacker scale projects that will not grow much beyond you, there's nothing wrong with that - but I think you have to be either lucky or able to commit time for the next several years if you want your project to get traction.
This fabricated extreme dichotomy is killing the discussion. Every time. And it's sad.
We the techies are supposed to be objective and act on a meritocratic basis. I get very disappointed every time I see us just bickering over imaginary strawmen.
The only thing is you have to be willing to start imposing some structure at some point, and the language has to make that easy, too. It can't be solely suited for the single hacker; it can't have been designed under the assumption that a language for use by large teams of people dispersed over time and space is one designed for "mediocre programmers," and so the things such a language values must be categorically avoided.
I don't know why people find this an offensive statement. EVERY craft on this planet has a pretty small (in terms of percentage) group of people who just grok it and "get it" fantastically well, and everybody else in the profession is pretty average. That's nothing to be offended at: the bell curve distribution is in full effect there. Perfectly normal, no?
And I would argue with you that some languages -- Go being the most modern example -- are specifically designed to be an easy onboard for people with certain backgrouns (C/C++/Java). That Java has been designed to be an easy onboard for people with C/C++ experience is pretty much a documented historical fact at this point as well.
Large companies optimize for quantity, not quality. There's nothing shameful in admitting: "we need a horde of not-amazing programmers who we can treat as interchangeable nuts and bolts"... yet everybody is denying it, when reality clearly shows that's their exact attitude... really makes me wonder why people keep insisting on keeping that circus alive and well (I also heard managers speak of people like that, in expensive saunas and restaurants -- and they were talking quite loudly). Most corporations don't need craftsmen and artisans; they need prentices who build houses with already-made plans.
I am asking for the umpteenth time -- why is this such a bad thing to admit? We cannot all be amazing programmers in each and every way. We have to stop denying this reality.
> I myself like using languages with a balance between the two extremes
Same, that's why I code Elixir: it's FP alright but not "pure" -- has side effects but the edges are clearly documented. And we can't get anything done without side effects anyway. So it's like 95% FP and 5% everything else, in the right places. Perfect.
> I think the dichotomy is from 'pg in this case.
I don't know enough about @pg to claim anything but whatever he said, that's no excuse for everybody else to start gravitating into camps. When people ask me "do you hate Java or do you hate C#?", you know what I say?
"I hate the people who hate programmers based on what they work with."
Also I do not think this is how you build a great company. It's one way to build a company, but it's not a very good way for the long term.
As for how to build companies, I wouldn't know, not a big business owner. But I am guessing from certain point and on they just want to commoditize stuff and make their management easier and quicker. It all starts with good intentions on paper and always ends up with horrific and dyfunctional teams. I have only seen 3 exceptions for 17 years of career of which 9.5 were spent in offices.
A functional style is not the only way to write programs, but it’s often much easier to reason about behavior when you’re not chasing side effects.
And OO implementations often have a LOT of side effects.
If you're worried about side effects and want pure functional programming... Arc is not the language for you.
The idea is to try to keep state in values (rather than global variables) and to update those values in a functional,typically immutable, maybe a sort of careful mutable way. Then on the outside there is some global state of things that are treated in an imperative way, eg setting up loggin, or a global variable for “the state of the world”, or reading user inputs, processing, and outputting.
IO is something that Haskell has to make a big deal about because it is lazy and “pure.” One can think of it a bit like async in JavaScript: (most) IO things are async and so an async function is one that does IO, just like any function returning IO a in Haskell. I think in most programs structured with a functional core, having IO in the core will typically mean having logging or debug printing which I think is not really bad and not worthy of eg polluting type signatures everywhere. Most meaningful IO should go in the outside, topmost bits of the program because that’s where it belongs (rather than because your language makes it so painful that you don’t want it any other way).
I suppose my point is that I don’t think “pure functional programming” is something one should want throughout a language.
Yeah, same here. Haskell struck me as too strict and clunky to get basic stuff done. Its focus doesn't seem to be productivity. Hence I gravitated towards Elixir (and lately trying Clojure). It allows me to get stuff done in a small number of lines, is amazingly well documented and has powerful primitives.
The pursuit of academic purity quickly turned me off of several languages, Haskell included.
That could apply to pretty much any programming paradigm.
I wouldn't use object oriented programming for small programs. Why bother with types when you can express things in a couple hundred lines of python with functions? Why write classes to munge some data that could be better expressed as a series of map/fold/etc. on lists of tuples?
However, for keeping large codebases approachable and maintainable, OOP is the best paradigm I have personally seen.
Hm? Types don't require OO. Even your 100 line program can benefit from type checking.
Approachable code must be structured in a meaningful way. That doesn't require OO either.
Besides, all of the three languages that you've mentioned are turning more functional and less OO with each year...
JS still has OO (and recently got "classes" as sugar), but it's not used in the crazy deep-OO-hierarchy style at all.
Java, that used to cherish that style also abandons it nowadays (with closures, streams, etc.).
(Traits of types -- and thus multiple inheritance -- make much more sense if you so want to model the real world.)
"OOP is necessary" is a lie that is worth billions of dollars. Inheritance ends up being leaky and non-isolated. Every time.
Off the top of my head:
Subsets, sub-anything-else, not really inheritance
Definitions of the form an X is a Y with the following extra properties, I think don’t really count as inheritance either. Example: A ring is a group with ..., but if you wanna to prove anything interesting about a ring, you can’t kust use the group properties or you would have proven a theorem about a group, and so the only useful thing about the group structure is that you know what it is. Any useful theorems will need other useful definitions (eg homomorphism) and these don’t get inherited and so the isomorphism theorem for groups doesn’t become the isomorphism theorem for rings “for free”. There’s also the fact that a field is a ring plus an extra property but most ring things become basically useless because much ring weirdness becomes trivial in a field and what is not trivial in a field is typically impossible in a ring, so knowing about rings doesn’t tell you much about fields.
Example: a topological space is a set plus a subset of its power set such that ... but all the interesting things in topology typically have little to do with those sets and more to do with eg functions (but not just any set function) and the added properties.
I suppose the theme is that one adds structure to (ie inherits from?) something because the new structure bans a lot of the weirdness of the bigger thing and allows for more interesting things to be done. The structure of the bigger thing is therefore not so useful because what is known about it is less relevant once more structure is imposed.
In applied maths, the Fourier and Laplace transforms are part of a more general family of integral transforms yet this knowledge is basically useless in their application and it would not make much difference if they were not related.
Fourier series fall into part of a bigger theory called Sturm-Liouville theory but in many of the places one uses a Fourier series this bigger picture is not relevant whereas the specific details about Fourier series are. This is not to say that Sturm-Liouville theory is not useful.
I love that inheritance lets me customize 3rd party code and integrate with it smoothly; nothing pisses me off more than a "sealed" class that you can't inherit from.
I'm not sure what this means? In your modernized UI framework, how would you want to be able to make UI tweaks to built in OS/framework components?
I've seen a few other system for customization and they all tended to be worse than just being able to subclass internal classes and override methods. Now I admit this can be terribly brittle but often the alternative is no way to customize at all.
I don’t really see why this would be better, to me the two approaches seem isomorphic...
nsTableView ::
{ renderCell :: a -> NSView -- this is the function that renders a cell given an `a`
, header :: NSView
, footer :: NSView
}
-> [a]
-> NSTableView a
I.e. pass in a function that renders a cell for a given `a`, a header and a footer NSView and whatever else, a list of `a`s and get back a NSTableView which displays that list of `a`s.React calls this pattern higher order components iirc.
1. Overriden methods have access to the internal state of the parent class, which is almost universally a bad idea.
2. Since functions are values, you can freely mix and match different view configurations without having to create a subclass for every permutation.
#2 is a good point. But there are some limitations as well. You don't get to augment the default implementation unless it is also exposed somewhere (passed into the function).
Both methods have a lot in common with the obvious differences that you mention. A class with no public methods and a empty virtual function is equivalent to a class calling a higher order function in the same place. If that virtual function has a implementation, you can get the same functionality with a higher order function by passing in the base implementation as a parameter. For full equivalence, you could also pass the object instance as well. Add in protected methods and/or state and you can get that with a second class that implements an internal API and again pass that to the higher-order function.
Ultimately you can achieve all kinds of equivalence this way. I think there is some advantage to using the terminology in the language (private, protected, virtual, abstract, this/base, etc) to describe the more complex situations. Having a simple function call is useful for more simple situations. This is the classic struggle.
"Smoothly". For my 8 years with Java the only base classes that haven't been retroactively modified and thus breaking my inherited classes... were the ones in the stdlib. Everything else could and has been pulled under my feet, many times.
Everyone and their moms and dogs was like "hey, I think I can do better because I feel great this week!" and then 10 of us had to repair an object hierarchy of 200+ classes. I mean, everybody knows the CTO can't make a mistake, right?
And don't give me the "not in a disciplined team!" argument. Even disciplined teams make mistakes because often the author has no idea how many other colleagues use their base class.
I know the idea sounds too good to ever believe that it can't be true -- but yeah, it's not true.
> nothing pisses me off more than a "sealed" class that you can't inherit from
The "sealed" classes are meant to be semantic boundaries. Get an instance of it in your object and be done with it. What's so triggering about that?
> The "sealed" classes are meant to be semantic boundaries.
The author of the "sealed" class definitely imagined all possible use cases and their code has absolutely no bugs, right?
...What?
You can compose an instance of the sealed class in yours. You lose all brittleness from inheritance in the process. I've been coding OOP most of my life and I am working with FP for 2 years now -- composition never introduced a bug. Sure it's anecdotal but after a trend repeats itself 5+ times, you start observing patterns and making conclusions.
Additionally, a number of FP languages offer Java-like and Go-like interfaces for an extra peace of mind.
But I can't subclass a sealed class to fix a bug or add a feature and pass it back into the framework to use. And yes, I have needed to do this otherwise I would never notice it was sealed in the first place.
https://softwareengineering.stackexchange.com/questions/2472...
http://wcook.blogspot.com/2011/04/paul-graham-on-objects-in-...
"Objects are just collections of first-class functions". " the lambda-calculus was the first object-oriented language: all its data is represented behaviorally as objects"
> There is a kind of mania for object-oriented programming at the moment, but some of the smartest programmers I know are some of the least excited about it.
Is that supposed to convince me? "Oh, well, if he knows some smart people who are against it, maybe I should stop liking it, too, because I definitely want to fit in with the smart people Paul Graham knows."
It's such a common thing in Silicon Valley, it kills me. "Always be the dumbest person in the room. Surround yourself with people smarter than you." Like, do I have to ditch any old friends that don't make the cut? What does it mean for someone to be "smarter than me"? Do they have to have a higher SAT score? Do they have to play an instrument better than I do? Do they have to be more financially successful? Can I still learn things from people who are less successful than me?
I just really don't like the mentality of deeming someone "smart" and then using their personal preferences as an inherent argument. That's the fallacy of the argument from authority.
If you don't like OOP, great, carefully explain its shortcomings to me. And perhaps he goes on to do that. I haven't finished reading yet. I just wanted to stop after that first sentence and say that as an opener, I hate it.
It doesn't necessarily have to be attributed merely to raw intellect, but denying it isn't in line with reality.
Having worked at many different companies you can definitely see how certain cultures, problem areas and technologies attract people with distinct intellectual bents and how that impacts development velocity.
How much these talented people can leverage their skills is still dependent on the organization, but pretending those differences aren't out there or that you can't hire for these profiles is naive.
Not trolling, I am genuinely wondering what would you do. It's not always appropriate to go on huge tangents explaining nuances, especially in casual conversational settings.
You seem to be bothered by the fact that people use shortcut words which carry their own subjective context with them. But that's what natural language is, most of the time -- namely a dictionary compression where the dictionaries of the separate people vary.
Oh, that I fully agree with. It's a very copy-paste, generic and bland advice that doesn't tell you anything.
It's further made worse by the very obvious observation that everybody is smart in one way and dumb in another. That's what makes communication between humans fun and interesting in the first place.
So yeah, fully agreed.
Please note that I fully agree with this part; I've met such programmers and then actively avoided them.
But there's always the other side of the coin. You can reinvent your own mini-OOP for a specific scenario you need (recent example: utilize different data parsers depending on a HTTP response header and a top-level XML attribute in the response). And you can make sure you don't suffer from some of the OOP's undeniable problems -- like "brittle superclass".
Ever since I worked with Elixir I had to do something like the above 5 times in total and was VERY happy with my own half-arsed OOP implementation because it allowed me to keep a strict control of things while still giving me the flexibility I wanted for the particular task at hand.
Replacements for OOP are just another fad, in twenty years from now people will come up with new fancy object-oriented languages, maybe functional ones with immutable objects, or with some other paradigm that replaces traits, mixins, interfaces, etc.
OOP is good for domains that naturally give rise to a clearcut inheritance structure, such as stateful GUI frameworks or ontologies that directly mirror real-world objects. It's not a magic pink unicorn, but that's true about all other approaches.
If you want everything but small, compact executables, you can already have that today in CommonLisp. It can be used to write functional programs or object-oriented styles and has almost all other programming language concepts you can imagine. Still not many people use it. The choice of the latest programming languages has almost nothing to do with their features and more to do with the availability of tools, libraries, and programmers.
These can -- and have been -- influenced by corporations. Many times.
It's a vicious cycle.
Stateful GUI frameworks are not the only way to build UI frameworks... It'd be pretty easy to argue that people are building stateful UI frameworks because they're predisposed to think about them in terms of OOP.
And so far as I can tell, inheritance ontologies never directly mirror any real world objects. Real world objects are purely compositional with some specialized emergent context-sensitive behaviors.
Most architectures around React do this, for example. The reasons why virtual DOM technology has been developed on the web, was to hide away the stateful nature of the older, OOP-influence DOM API.
React begs to differ.
> or ontologies that directly mirror real-world objects.
Such as?
It seems to me that Graham answers this question in the preceeding paragraph:
>> I've done a lot of things (e.g. making hash tables full of closures) that would have required object-oriented techniques to do in wimpier languages, but I have never had to use CLOS.
He simply writes ad-hoc implementation of an object system, instead of using one provided with his favourite language. Seems like a bad case of NIH syndrome.
Could be. Or maybe he just accepts OOP as a necessary tool in a small percentage of cases and opts for the lesser of the two evils -- rolling his own mini-OOP system as opposed to accepting all the baggage that comes with a first-class OOP language (like side effects and bloated code). Remember the saying "when you code with an OOP language, you not only get the banana, but the gorilla and the whole jungle".
I know I would defend OOP to my dying breath just a short 2-3 years ago. But when I started working with FP languages I discovered that OOP is vastly overrated.
I am not as naive as to think that I will change your mind. Just offering another perspective.
(My reading of the "wimpier languages" bit was that it was probably a dig at Java, where [prior to Java 8], you had to explicitly write an inner class to emulate a closure...)