Goodbye, shitty Car extends Vehicle object-orientation tutorial (2011)
web.archive.org
web.archive.org
This correctly identifies one of the worst problems with how people construct OO programs when they get out of college. They are infected with the idiotic desire to make neat taxonomies. They don't know why. They can't even explain the rules that make one taxonomy good and another one bad. They just feel some immense pressure to arrange things in trees. I've been like that when initially learning Java and it took a while to recover from it. It's like fucking brain damage from bad examples.
Inheritance is not about creating taxonomies.
Namespaces are not about creating taxonomies.
Both are practical solutions to practical problems that occur when writing code.
Inheritance is a result of several insights:
1. It's convenient to be able to sketch out a protocol which a set of objects will follow - without having to work out every damn detail about its implementation.
2. Most of the protocol can be implemented once and then reused.
3. Often you want to use the default protocol implementation with some changes. Instead of re-implementing the rest of the protocol, it's highly convenient to be able to specify only the differences.
#3 is the core reason why inheritance was developed.
And #3 is also an area where Functional Programming falls completely flat.
There is simply no easy way in any FP language that I know to capture such a simple mechanism as: "Reuse 90% of this piece of code but alter just this 10%".
Specialization is a very, very powerful aspect of OOP, one that's extremely easy to explain and which comes in handy to model a surprisingly wide variety of problems.
The sad reality is that since so many people are only familiar with the strawman version of OOP rather than the real thing intelligently discussing the advantages and disadvantages of these paradigms is very hard.
For example, partial evaluation is limited by the fact that parameters need to be ordered a certain way and you can't realistically cover all combinations.
Higher order functions don't enable any kind of useful specialization at all. And of course, all modern OOP languages also support higher order functions, so they are the ones giving you more tools.
As for higher order functions you would write a function that does the 90% and accepts a function parameter for the other 10%.
https://docs.racket-lang.org/reference/procedures.html#%28de... (god links into the racket docs are ugly...)
Surely you see how this doesn't scale, right?
You would need to first write your code, then abstract every single piece of it as a call to a function passed in parameters. Now your function is accepting five different functions in parameters, and calls them.
This is a terrible and impossible to scale way to emulate class specialization.
Honest question: why does the Common Lisp Object System not do this for you?
Yes, CLOS offers all of that.
I just didn't quite consider it to be in the bucket of FP languages I had in mind (which to me means: statically typed, and some derivative of Haskell or OCaml).
Haskell’s typeclasses and OCaml modules solve this problem neatly. It is not as easy as in Java, because you need to spend a few minutes thinking how to abstractly specify the interface you expect, but really if your object hierarchy is supposed to satisfy Liskov’s substitution principle, you need to do that just the same.
Neither do, really.
Imagine you have an existing typeclass with three functions. How do you create your own typeclass that reuses two of these three functions but your own instance reimplements the third one?
Try it. It's pretty much impossible, or at least not without a lot of boilerplate and forwarding.
class Parent {
public void first() {};
public void second() {};
public void third() {};
}
class Child extends Parent {
@Override
public void third() {}; //change implementation of third function, reusing the first two.
} module Parent {
let first = () => ();
let second = () => ();
let third = () => ();
};
module Child {
include Parent; //add parent funcs to namespace
//@Override
let third = () => ();
};
I make regular use of this pattern production. module Foo = struct
let first = 1
let second = 2
let third = 3
end
module Bar = struct
include Foo (* Include contents of [Foo] *)
let third = 4 (* Override [third] *)
end module Foo = struct
let first() = 1
let second() = first() + 1
end
module Bar = struct
include Foo
let first() = 0
end
Bar.second () will still be 2 because of static binding. Thus you cannot use this approach to replace 'first' by itself without creating inconsistencies. data Numberable = Numberable {
first :: IO ()
,second :: IO ()
,third :: IO ()
}
parent = Numberable {first = return (), second = return (), third = return ()}
child = parent { third = print 'third' }
And if you wanted to DI into a function: data Env = { numberable :: Numberable }
doStuff :: (MonadIO m, MonadReader Env m) => Int -> Int -> m ()
doStuff num1 num2 = do
myNumberable <- reader numberable
print (show num1)
print (show num2)
liftIO (third myNumberable)> Reuse 90% of this piece of code but alter just this 10%
There is no general solution to this, in any programming language, without some foresight (unless you admit some crazy features like arbitrary code re-writing or source-level patches). If you have, say, a single function of 100 lines, and 10 lines need to be changed, you're going to have to edit that code to enable the change, and if that 100 lines is in a third-party library or code you otherwise don't control, you're going to have a hard time. However, if you control the code and are able to make changes to it to support new behaviors, FP languages give you plenty of power here, just as OOP languages do. There's not a lot of objective difference here in power and plenty of differences in taste and familiarity, so please be careful about repeating "you can't do X in language Y".
complex_function small_part = do
a <- complex_thing_a
b <- small_part a other_variables_in_scope
more_complex thing
original = complex_function orig_version
mine = complex_function my_version
There are some issues if you have a lot of parameters where you might want a record to pass them around, but specialization is easier to use than inheritance in all the cases I've seen.ML modules have modules and functors, which can exactly mimic this and in fact can be even more flexible because they are structurally rather than nominally typed.
Clojure can get some of this via multimethods, but it's not quite the same thing.
Haskell has Backpack. This is fairly new, but takes an approach similar to ML modules. (Interestingly enough to your point elsewhere in this thread, this is a good example of how type classes were not sufficient for this use case).
Scala has it, but that's cheating :). Although it does have a really cool thing which is that every object instance is itself an importable package, which leads to some really nice code reuse at times.
Stepping back, I actually think "Reuse 90% of this piece of code but alter just this 10%" is actually best captured just with splitting things up into functions/procedures in imperative and functional programming languages, rather than building a large class hierarchy, but that's another story altogether.
EDIT: I suppose what you're talking about specifically is overriding an already implemented method, which you can get via module name shadowing and re-exporting of a parent module as Hermitian909 points out elsewhere.
I also didn’t learn about the Liskov Substitution Principle in college or in my first few years in an OOP. Had to find it on my own and start teaching others. But I was in good company because Josh Bloch also didn’t understand it and a whole generation of devs aped his solutions.
People think of inheritance as additive but if you look at the code they write it’s often subtractive. Mammal is not a contract. Every fork in the tree is carving out more negative space than it adds.
Mammal is really a pretty terrible root. Some mammals are nocturnal. Some fly. One lays eggs. A few have no hair and we think hairy humans are gross. Some can hold their breath for hours and live four hundred years. Others measure life in days.
But not a single one is all of these things. If we talk about what mammals can do we include it all. But you can’t ask these things of most of them. You don’t even organize them by these traits (what’s in the tropical or night exhibit at a large zoo? Why are the seals and penguins together?)
interface Mammal { fun milk() }
If I start from a concrete 'FooDatabase' (and wait until later to interface it), I might start putting things like open(), close(), flush(), etc. into my logic code. Then when I interface it, it will just be unnecessary indirection.
If I start from the other end and think "I just need somewhere to put my Foos", then I'll pass in a Consumer<Foo> and be done with it.
> #3 is the core reason why inheritance was developed.
No, absolutely not. As sibling comments say, composition is sufficient for that. I can only see exactly one reason why inheritance is necessary: to allow superclasses to call methods defined in subclasses, also known as late binding.
Secondly, OO composition does not solve the problem I described in #3. If object X has 9 methods I want to reuse in object Y, making X a component of Y still requires me to re-implement those 9 methods in Y, even if they are just pass-throughs.
That is also not universal. In Ruby, for instance, one can write:
class Y
def method_missing(m, *args, &block)
@x.send(m, *args, &block)
end
endIn Java, or C++, or Python, how can we allow superclass methods to call methods on a subclass (a form of late binding) without inheritance? I don't think it's possible. So unless I am much mistaken (and please correct me if I am wrong), inheritance is convenient for reducing boilerplate but necessary for late binding (in many languages).
Have I made a mistake in my analysis somewhere?
However, it turns out it was in SIMULA 67, taken from a 1965 proposal by Tony Hoare for record handling in Algol, which I haven't been able to find yet. That is, it was proposed in a context where objects had fields, but not methods, and thus not protocols either. SIMULA 67 had overriding of superclass methods if they were marked “virtual”, as in C++.
Turning back to Window and Number, an alternative using composition instead of inheritance puts the shared and unshared code in different objects. The Window class becomes one class only, with a field indicating what its contents are, to which it delegates paint messages and handling of input events; Number becomes a wrapper that expects its contents to implement < and ==, perhaps, and implements the other four methods on top of them.
How does this differ from the approach using inheritance? It's a great deal more hassle to change your mind about which methods are delegated to the wrapped object, but much easier to be sure that other refactorings are correct, because it's much easier to tell which methods could potentially be “overridden by a subclass”. It affords the possibility of changing the contents of a Window over time—particularly useful in languages like Python where reloading code after a modification creates new classes rather than modifying the existing ones. It costs an extra allocation and an extra method call on every delegated method (fatal in the case of SmallInteger, normally a subclass of Number, but SmallInteger is already a collection of hacks for efficiency; giving it an independent implementation of the Number protocol is reasonable).
Perhaps we should regard inheritance as an efficiency and convenience hack for cases where memory is tight or we're initially exploring an OO design, one we should remove later to improve maintainability once it's more or less clear how to divide up the responsibilities.
Smalltalk-80 used inheritance for its collections library, in a way that does not respect LSP, perhaps understandably because Barbara Liskov was on the other coast, and CLU was a very different language from Smalltalk. Some more recent languages like Java modeled their collections after Smalltalk’s. I don't think it's entirely fair to ding Josh Bloch for this.
Rigorously modeling inheritance (with overriding, open recursion, and covariant self type) turns out to be quite challenging; I recommend Abadí and Cardelli's A Theory of Objects to those who are interested.
The most interesting thing I'm looking at right now with regard to software design is Jackson's Alloy model checker, which does a kind of abstract-interpretation exhaustive test of your high-level design to verify that in examples to a certain size, your desired properties hold. This is of course a different level of abstraction from the factoring of the implementation to eliminate duplication, but it can tell you which contemplated protocols are fatally flawed.
I think if they took a single lecture to teach these specific points of OO, we'd get more effective developers straight out of school.
> 1. It's convenient to be able to sketch out a protocol which a set of objects will follow - without having to work out every damn detail about its implementation.
> 2. Most of the protocol can be implemented once and then reused.
> 3. Often you want to use the default protocol implementation with some changes. Instead of re-implementing the rest of the protocol, it's highly convenient to be able to specify only the differences.
> #3 is the core reason why inheritance was developed.
Those insights lead to what was taught in my intro to cs class: abstraction. Inheritance can be used to implement abstraction, but so can many other concepts such as duck typing or aspect oriented programming.
We make class hierarchies in order to simplify the code
In fact, much of the abstractions of programming were invented for this reason, and it's unfortunate that a lot of courses don't really teach the why. They tend to start off on the right track with something like "use a loop to repeat a sequence of operations X times", but then get caught up in preaching about abstraction with functions when it's far more intuitive and sensical to approach it from the perspective of "repeat a sequence of operations, with perhaps some small differences in parameters". For the same reason, I think OOP should be taught only after basic arrays (grouping homogenous data together), structures (grouping heterogenous data together) and functions, since OOP is really just another level of organisation that can --- not must, unlike what a lot of people seem to believe --- be applied to code once it becomes complex enough.
The dogmatic attitude towards OOP and treating it like a goal leads to ridiculous explosions in complexity for even the simplest of applications; what can be done by someone who hasn't been indoctrinated in a few lines of code can turn into hundreds of lines instantiating dozens of objects. "Design patterns" helps to amplify that too.
> You can’t refactor ducks.
> Ducks don’t implement protocols.
> You can’t create a new species in order to separate some concerns (e.g. file I/O and word splitting).
It's odd he makes these complaints, because the comment he's responding has this caveat:
> unless they are talking about writing a clone of The Sims or something
In the context of a game or simulation, such classes can absolutely do all these things. And those seem entirely reasonable ways to teach someone about programming, while being more accessible and engaging than "let's talk about how to write an IO stack." *
> Penguins don’t implement the “fly” method that can be found in birds.
That's an important issue, though, which an OO tutorial needs to deal with. It points at two major problems:
1. The circle-ellipse problem[1].
2. That nominative types have all the inherent problems that taxonomy has.
And since one solution is "duck typing"[2] it's worth noting that it's entirely clear what it means, despite being named after ducks.
[1]: https://en.wikipedia.org/wiki/Circle-ellipse_problem
[2]: https://en.wikipedia.org/wiki/Duck_typing
* IO stacks are good to learn, but to do them in OO you have to understand IO first, and though it's very satisfying when you get tricky IO code right, it's not really something you can show to your friends.
It's still very OOP, but not in the sense of representing real-world taxonomies.
I know there's an argument that this real-world metaphor is good for beginners, but at least in my personal experience I don't think that's the case. I remember when I was a kid, my dad tried to teach me coding with a lot of object oriented examples with like Cats and Dogs and stuff. He was really well meaning, but it never really made sense to me. I only kind of "got it" when I did something nobody would recommend anymore, and learned QBasic, and wrote a bunch of ghastly code with goto's all over the place and practically everything being a global. Awful code, but 10 year old me was able to reason about that a lot easier than polymorphism/encapsulation/scoping/etc.
An aside: It's okay to learn that way, when you're 10.
A lot of my own big programming revelations have come from ignoring best practices and accidentally trying to implement or construct them from first principles once my problem becomes unmanageable.
"Doing it wrong" is an even better teacher than "doing it right all of the time", because the mistakes you make can lead you to a deeper understanding of why things are done a certain way.
(And yes, as a kid, I was hurt by all those "Cat : public Animal" examples too. It took me many years before I figured out why they didn't work in practice for me.)
Maybe it's possible to be less misleading, but I still think it's a mistake to try to write a tutorial that tries to present basic concepts in the fashion that production code is written. There's just so much stuff you do in prod that is not relevant to the concepts you're trying to convey.
> I only kind of "got it" when I did something nobody would recommend anymore, and learned QBasic, and wrote a bunch of ghastly code with goto's all over the place...
I learned on a C64 when I was a kid, too. I'd absolutely recommend learning structured imperative programming before OOP.
1. OOP was never about "representing real-world taxonomies."
2. Real-world taxonomies are often not very good at representing real-word taxonomies.
Programmers are highly susceptible to scientism, and it's ruined whatever chance OOP had of being a tool rather than an ideology.
Unity has the notion of components (inheriting from MonoBehaviour => Behaviour => Component => Object), which can be multiply attached to GameObjects (inheriting from Object), that you can switch between with gameObject.GetComponent<ComponentClass>().
COM has the notion of multiple interfaces to the same "controlling unknown" object (analogous to GameObject), that you can switch between with QueryInterface.
https://docs.microsoft.com/en-us/windows/win32/com/aggregati...
https://flylib.com/books/en/3.357.1.89/1/
http://www.369o.com/data/books/atl/0321159624/ch06lev1sec5.h...
https://en.wikipedia.org/wiki/Object_composition#Aggregation
They're basically the same idea, but slightly different in the details and implementation.
COM is essentially C++ pure virtual interfaces implemented by one or more concrete classes using multiple inheritance (like the way Java lets you multiply inherit interfaces, but only singly inherit classes). Different COM interfaces can refer to the same shared C++ implementation object, or separate delegate objects, and "tear off" objects created on demand.
Unity's component model is implemented in C#, but isn't an essential language feature of C#, just a way of using it to implement aggregation.
http://t-machine.org/index.php/2007/09/03/entity-systems-are...
ECS builds on top of some of the ideas of COM and GameObject/Component, but loses much of the baggage, takes it much further in a different direction, and has a lot of other advantages, like storing the data in separate contiguous cache-friendly buffers.
https://en.wikipedia.org/wiki/Entity_component_system
>In naïve ECS implementations, every system iterates through the complete list of all entities, and selects only those entities that are needed. The total iteration cost for all systems becomes too costly if the number of systems grows or the number of entities is large. In other ECS architectures, every component type is stored in a separate list, so whatever systems operate on a given type of component are only iterating over objects they care about by default. In this common ECS architecture, the described disadvantage actually becomes a major performance advantage, by more efficiently leveraging the CPU instruction and data caches.
http://t-machine.org/index.php/2014/03/08/data-structures-fo...
>Net effect: Contiguous memory is King
>If you store your data contiguously in RAM, it’ll be fast onto the Bus, the CPU will pre-fetch it, and it’ll remain in cache long enough for the CPU(s) to use it with no extra delays.
>NB: this is independent of the programming-language you’re using. In C/C++ you can directly control the data flow, and manually optimize CPU-caching – but whatever language you use, it’ll be compiled down to something similar. Careful selection and use of data-structures will improve CPU/cache performance in almost all languages
Also:
Entity–component–system (ECS) back and forth (skypjack.github.io):
https://news.ycombinator.com/item?id=19166910
Entity Component System Framework that is CPU cache friendly:
https://stackoverflow.com/questions/23473783/entity-componen...
Designing a cache-friendly entity component system:
https://cerulean-skies.com/index.php?article=1
Unity is rolling out a new officially supported entity component system that's a totally different stack than GameObject/Component. (But they interoperate, of course.)
https://docs.unity3d.com/Packages/com.unity.entities@0.1/man...
One (but not the only) fundamental difference between COM and GameObject/Component, is that you query for COM interfaces with an interface GUID, so you can only have one interface on an object for each GUID. But Unity lets you slap as many of the same component class on an object as you like. The singular Component GetComponent<ComponentClass>() method will just return the first one, but the plural Component[] gameObject.GetComponents<ComponentClass>() returns an array of all the components of the given class.
For example, it is useful to attach several different Colliders to the same GameObject (each with its own position/size/shape/etc). They all just work together as you would expect. A RaycastHit will identify which Collider was hit, as well as the sibling Transform of the GameObject it was attached to.
Another fundamental difference is that with GameObject/Component, each GameObject and component is always a completely separate independent object, but COM allows the implementation class to implement any number of different interfaces with the same instance (using C++ multiple inheritance, so each interface implementation has its own vtable pointing back to the same implementation class's different methods), or delegate to other sub-objects using "aggregation", or even make sub-objects lazily on demand with "tear-off interfaces".
> Here’s an example that I think would be better to use instead: the `Visible` hierarchy in [Pygmusic][], which is a kind of software drum machine. A `Timer` is a horizontal strip on the screen with a stripe racing across it. A `NumericHalo` is a spreading ripple on the screen that fades.
That's how someone experienced in OOP is going to do it. The problem is that this depends on experience and knowledge of how such a system works.
I don't think this would be significantly better than the Vehicle-Car examples, because your target audience is seeing this odd layout and you aren't likely able to give them reasons why it should be that way.
To fully explain yourself would require information you can't reasonably impart in a tutorial. You know why it's done that way, but the underlying rules are obvious and intuitive to you because you worked on them long enough.
These rules manifest in a trained developer as an intuition that is developed by the feedback of doing things and failing or succeeding. Maybe they could be expressed mathematically, but it'd probably be very specialized math like advanced linguistics.
> In good OO programming, we don’t make class hierarchies in order to satisfy our inner Linnaeus. We make class hierarchies in order to simplify the code by allowing different parts of it to be changed independently of each other, and to eliminate duplication
Maybe we should really stop talking about "objects" and "classes" entirely?
Non-OOP structured programming doesn't have this problem; it's hard to grasp exactly when two subroutines are overly coupled, but it's easy to explain that sharing state and having action at a distance is a bad idea. And, generally, it's just not claiming any subroutine represents a real life "object". The only hierarchy imposed is the hierarchy of control flow.
Real-world examples can be used to point that out. Whales are mammals to biologists, but they're essentially fish to the economy. A bat is a mammal and an airplane is a vehicle, but both of them go into the same bag as birds if your main concern is "things that can fly" vs. "things that can't". Square is a special form of rectangle to mathematicians, but it's the other way around if your main concern is how much information you need to draw it. "Knowledge is knowing that a tomato is a fruit. Wisdom is not putting it in a fruit salad." Etc.
Once you crack the student's belief in there being one proper taxonomy for everything (a belief that strangely seems common even in adults in general population), you can then effectively show them that one of the main tasks of a programmer is finding the most useful way of categorizing things they're dealing with - and once that's done, the programming languages tend to have tools that let the programmer exploit such categorizations to minimize complexity.
Sure, but the point of TFA is that even then it still doesn't (necessarily) make sense to give them an OO hierarchy similar to their real-world taxonomy.
It might make sense of course, but it might not - you can't tell without looking at the code. The point here is that duck/bird analogies imply that the real-world relationship between ducks and birds tells you something about whether your Duck class should inherit from a Bird class, when in fact it doesn't.
> > unless they are talking about writing a clone of The Sims or something
Although I formatted it as a quote, I wasn't quoting or responding to somebody else in that part. That part about the Sims was part of the somewhat flippant principle I was proposing. The litany about ducks was just to explain the reasons for it. It wasn't a rebuttal.
We regret the inconvenience.
And everyone should deliver a litany about ducks at some point in their life.
It's actually implemented in C++ of course, but it has a virtual machine that executes a noodly visual programming language called "SimAntics". And by noodly, I mean optimized for spaghetti code, containing intertwined loops, floppy, and droopy...
https://medium.com/@donhopkins/the-sims-pie-menus-49ca02a74d...
Subject Oriented Programming
This is described by some people as object — not Object Oriented Programming, but Subject Oriented Programming.
Objectifying People
People are objects, so when you click on another person, the items that you get in the menu depend on your mood, and your relationship with that person.
(From the transcript of this demo video:)
https://www.youtube.com/watch?v=-exdu4ETscs
The Sims, Pie Menus, Edith Editing, and SimAntics Visual Programming Demo
This is a demonstration of the pie menus, architectural editing tools, and Edith visual programming tools that I developed for The Sims with Will Wright at Maxis and Electronic Arts.
http://radar.oreilly.com/2009/03/interview-will-wright-sims-...
Will Wright: Well, one of the first challenges was could we develop a really robust model of human behavior test so that we could put these little characters in the elements in any situation they would behave reasonably. A Sims user is kind of controlling the environment. They can put the Sims in a wide variety of potential environments and then the Sims have to act reasonable. So we kind of had to develop a very — well, on the surface, it looks like an object-oriented programming model, but in fact, it’s what’s technically called a subject-oriented programming model. But I won’t get into that detail. So developing this robust kind of behavior system — in fact, it was environmentally distributed intelligence is the way we solved it.
But on the design side, there was a lot of thought about how much autonomy do these characters have versus how much reliance do they have on the player directing their actions. And there was also a whole dimension of thought around how much are we going to let the player read in to the simulation. In other words, how much of the simulation are we going to offer them to play of imagination versus make very clear and overt?
https://www.gamedev.net/forums/topic/697362-need-some-advice...
All8Up Moderator
Posted June 15, 2018
From the way the embedded code is shown, I would say the first thing to do is reverse the logic. What I mean is that what you posted suggests that the code is along the lines of "I'm trying to interact with you" and "I need to figure out what you are and what I can do to you". Conceptually of course is the most sensible solution, unfortunately in practice it is also the solution which fails the most often. Rather than this, I've always used subject oriented programming for interactions. This reverses the logic such that rather than the 'doer' figuring things out the subject of the interaction performs the work. Basically this means that the input system would simply set a flag on the input component saying "this entity wishes to interact". The interaction system can then do pre-cull for subject entities which are in range, which ones can be used from the doer's position, etc. Finally, you end up with a door that runs the rule checks: "You are in range, you have my key, it is Monday after noon, you are wearing a purple shirt... Ok, guess I will open now."
As to how you implement these things in the ECS itself, I suspect everyone does it differently. Personally I use a handle based approach where the interaction component simply contains a handle which refers to usually a script which checks the interaction rules. The script gets the doer and subject ID's so it can check states and make the decision. Then, likely in another script, an action is performed.
Overall this probably sounds ass backwards but it is a well proven solution to removing massive 'if/else' checks. It is also great for expansion packs and such since all the new logic is contained in the subject and you don't have to patch the doer code to understand the new interaction. Just as an example, The Sims used(still uses I assume) this subject oriented approach which allows DLC to be dropped in and mostly just work.
Hope this makes sense.
http://ivizlab.sfu.ca/arya/Papers/SW/SOP.pdf
Subject-Oriented Programming (A Critique of Pure Objects). William Harrison and Harold Ossher.
Abstract
Object-Oriented technology is often described in terms of an interwoven troika of themes: encapsulation, polymorphism, and inheritance. But these themes are firmly tied with the concept of identity. If object-oriented technology is to be successfully scaled from the development of independent applications to development of integrated suites of applications, it must relax its emphasis on the object. The technology must recognize more directly that a multiplicity of subjective views delocalizes the concept of object, and must emphasize more the binding concept of identity to tie them together.
This paper explores this shift to a style of object oriented technology that emphasizes the subjective views: Subject-Oriented Programming.
https://en.wikipedia.org/wiki/Subject-oriented_programming
In computing, subject-oriented programming is an object-oriented software paradigm in which the state (fields) and behavior (methods) of objects are not seen as intrinsic to the objects themselves, but are provided by various subjective perceptions (“subjects”) of the objects. The term and concepts were first published in September 1993 in a conference paper[1] which was later recognized as being one of the three most influential papers to be presented at the conference between 1986 and 1996. As illustrated in that paper, an analogy is made with the contrast between the philosophical views of Plato and Kant with respect to the characteristics of “real” objects, but applied to software ones. For example, while we may all perceive a tree as having a measurable height, weight, leaf-mass, etc., from the point of view of a bird, a tree may also have measures of relative value for food or nesting purposes, or from the point of view of a tax-assessor, it may have a certain taxable value in a given year. Neither the bird’s nor the tax-assessor’s additional state information need be seen as intrinsic to the tree, but are added by the perceptions of the bird and tax-assessor, and from Kant’s analysis, the same may be true even of characteristics we think of as intrinsic.
The Kantian thing is kind of a mindfuck. I may need to think about that for a while. It reminds me of the work Phil Agre did on literary criticism of AI.
Kind of like JavaScript with "this" and "that", or like the subject and predicate of a sentence.
There was also a sparse "relationship matrix" where you could associate data with pairs of objects and numeric keys, so it could remember that a person had a "slept in" relationship with a bed that increases every time they sleep in it, which affects how much they are attracted to the bed's "sleep in" action advertisement, so after they sleep in one bed a few times, when they become tired, that bed becomes more attractive than the couch and other people's beds, so they learn to sleep in the same bed every night.
Wow cool, do you have a link to Phil Agre's AI LitCrit please?
Have you read Chip Morningstar's "How To Deconstruct Almost Anything: My Postmodern Adventure"? "Academics get paid for being clever, not for being right." -- Donald Norman
The fact that "Car extends Vehicle" is sometimes a subpar modelization has nothing to do with OOP or inheritance and everything to do with the fact that the English language is not specific enough to describe what a Vehicle is well.
Same goes with "Duck extends Bird". Yes, there are a few birds that don't fly. This problem has nothing to do with programming languages, inheritance, or OOP.
I still find these two examples, "Duck extends Bird" and "Car extends Vehicle" as extremely powerful to teach beginners what OOP is about.
Like all metaphors, they tend to leak if you push them too far, but they do a splendid job at introducing beginners to the concept of OOP, which is going to serve them well for probably most of their professional life.
No, it's a perfectly fine place in the analogy, to have a teachable moment. Since not all birds fly, just don't make flight part of your base class. I mean? Or get clever and have flighted and flightless bird classes inherit from Bird? If you want to be that much of a pain about it, you can; the paradigm supports it.
These are introductory metaphors that help people get up to speed with a complex concept. You refine your understanding of these as you become more expert on the subject.
I think that the taxonomy thing is kind of harmful though in that, as far as I can tell anyway, it only seems to be now that a lot of programmers are coming around to the idea that modelling things are taxonomies is less useful than other techniques.
Some code sins are pretty easy to cleanup: if someone writes a function that's doing too many things, it's not too hard to break it up, or if someone names things badly, usually it's enough just to rename it to better concepts. But deep inheritance hierarchies are often a massive pain to clean up, if you can even clean it up at all (outside packages might come to depend on your weird structure for instance).
From my experience a lot of people get stuck with these introductory metaphors and never progress further. That's why I think they are harmful.
I've even seen OO tutorials which make it explicit to give the bad advice that all your objects should match real world _things_.
This does not match my experience of OO programming or what the actual challenges or rewards are:
> We make class hierarchies in order to simplify the code by allowing different parts of it to be changed independently of each other, and to eliminate duplication (which comes to the same thing). Without any context as to what the code needs to accomplish, you can’t make a judgment about whether a particular design decision is good or bad.
YES.
https://www.wired.com/2011/02/cussing-in-commits-which-progr...
Or Miss Mary had a Steamboat. You know exactly what you’re doing and you ain’t foolin anyone.
I once knew a baptist preacher who was a serious student of theology and moral philosophy. He liked to discuss the distinction between theology and law, particularly when framing behaviour as the imperative expression of faith, and not faith as the expression itself. Needless to say, the parishioners loathed him and he was hounded out for exposing their constant hypocrisy. Also possibly for declaiming in classical Greek on a Sunday morning; he was truly a latter-day Socrates.
My wife and I recently re-watched that unfortunately truncated classic, HBO/BBC/Rai's Rome, and were once again thoroughly entertained by the multitude of colourful epithets. Sadly, exclaiming "by Juno's cunt!" during a contentious meeting of the audit sub-committee or weekly retro would require more explanation than I'd care to give in context. And yet, a devout Christian should have no problem with this, at least on a theological level, since no scripture is violated.
Some Christians aren't in favor of cursing in general, even if it's not blasphemous. My best understanding is that they feel that it expresses a lack of gratitude for the blessings they have—that, at least for a moment, you are forgetting that every moment of life is a miracle, allowing yourself to become enslaved to your wrath, which is of course a sin in standard versions of Christianity. (This does of course pose some difficulties to reconcile with the stories of cursing the fig tree and the money changers, which the Christians resolve in various creative ways.)