[HN Gopher] Pitfalls of object oriented programming (2009) [pdf]
___________________________________________________________________
Pitfalls of object oriented programming (2009) [pdf]
Author : davikr
Score : 73 points
Date : 2023-12-27 12:19 UTC (1 days ago)
(HTM) web link (harmful.cat-v.org)
(TXT) w3m dump (harmful.cat-v.org)
| hurril wrote:
| I think this description of what OO is, is flawed and makes
| people talk past each other:
|
| > * What is OO programming? - a programming paradigm that uses
| "objects" - data structures consisting of datafields and methods
| together with their interactions - to design applications and
| computer programs. (Wikipedia)
|
| It isn't that they are together, it's that the core entity of the
| language used to express solutions centers around identity over
| state or values. As in: two objects are different even when the
| fields are all equal, and also: two objects can be the same even
| with different field values.
|
| If you are going to object to this, I ask that you first
| contemplate whether or not you have programmed in any other way
| first, because as a fish, reasoning about what water is can be
| difficult.
| kragen wrote:
| i am going to object to this; i've programmed in, among other
| things, prolog, ocaml, basic-80, c, the unadorned l-calculus,
| bicicleta, and qfitzah, the last two of which are languages i
| designed and implemented myself and which have no mutability.
| moreover i don't think anyone would claim that basic-80 or c is
| object-oriented
|
| so i feel secure in claiming that i am more a walrus than a
| fish and therefore qualified to opine about water
|
| i do agree that identity and hidden mutability are important
| aspects of the oo view of the world, and that the author of
| these slides is off-base about what oo is. but i think that the
| _essence_ of oo is that computation is done by sending messages
| to objects, but sending the same message to different objects
| invokes different code. this feature is present without
| mutability in bicicleta, and in abadi and cardelli 's untyped
| s-calculus, which it's based on; the s-calculus was invented to
| formalize the essence of object-oriented typing
|
| the original intuition behind oo was to decompose a computer
| system into smaller computer systems, like a computer network;
| each object in the system was in effect a tiny computer, with
| its own private code and state. this was inspired by alan kay's
| undergraduate degree in biology, which mostly studies organisms
| made out of cells, which are basically tiny organisms. and in
| that formulation, obviously hidden mutability is the default,
| because you can't prevent a computer from changing its internal
| state as a result of a packet you send it; and mutability
| generally means identity is important
|
| but even more central to this formulation is the idea that
| different objects can behave differently in the same
| circumstances
|
| obviously none of this has anything to do with memory layout!
| except very indirectly
| bruce343434 wrote:
| This is it. OOP is about building sub programs with their own
| state and behaviours, yet implementing a common interface so
| they can still effectively communicate to each other.
|
| If you boil it down to the essence, this means branching
| logic is encoded via a function pointer. Instead of manually
| switching to the correct behavior using other branching
| mechanisms like if or switch.
|
| This is what polymorphism is about. It's pattern matching.
|
| An interface is like an Enum to which you can add entries
| after it has been defined, and the behaviour for that
| specific variant naturally then has to be encoded inside the
| variant itself.
| Gehinnn wrote:
| Would it be far fetched to say object oriented programming
| is inspired by nature/physics?
|
| And thus, "more natural" than, let's say, procedural
| programming.
|
| Animals can be modeled nicely with objects/classes.
| Physical objects do have internal private state and public
| interfaces. Also in nature, usually, you have multiple
| similar objects that work the same but have different
| internal state.
| bruce343434 wrote:
| That might have been what Alan Kay was going for with his
| "cells of an organism" thing, yes.
|
| But even if you're not inspired by biology, it might just
| come up "naturally". Separating your data into structs,
| then operations on those structs, and not wanting to deal
| with all kinds of bookkeeping all the time, letting
| things manage their own - separation of concerns, aka
| single responsibility principle.
|
| I think automatic memory management might be a big
| example of this. Who wants to bother with this
| malloc/free stuff all the time? Who wants to keep track
| of ownership everywhere? Instead, have a dedicated
| subsystem handle it. That allows a higher signal to noise
| ratio in all the rest of the code that would otherwise be
| polluted with malloc/free. It also removes the entire
| class of bugs that can happen when you inevitably forget
| either of those.
|
| But having memory management in and of itself isn't
| "OOP". It's just an example of one of the principles that
| people tout when they talk of "OOP". Those principles can
| and do apply broadly.
|
| Was Alan Kay thinking of all this? I really don't know.
| All I know is that the dude digs biology.
| meheleventyone wrote:
| I think more that messaging was the fundamental part.
| Modularization and composition is pretty common in most
| paradigms. I actually looked this up the other day for a
| different comment but this is Alan Kay's own words on the
| subject:
|
| https://lists.squeakfoundation.org/pipermail/squeak-
| dev/1998...
|
| It's a concept I really like and use quite a bit in game
| development at the inter/intra-entity communication level
| even if below that I'd pick a more performant substrate.
| mostlylurks wrote:
| > OOP is about building sub programs with their own state
| and behaviours, yet implementing a common interface so they
| can still effectively communicate to each other.
|
| This doesn't seem like a sufficient definition, as closures
| are also sub programs with their own state and behaviors
| that implement a common interface (the function signature),
| and you'd have to stretch the definition of OOP to the
| point of meaninglessness if that would be sufficient to
| qualify as OOP.
| trashburger wrote:
| You nailed it. Object programming was never about the low-
| level details like inheritance; it was always about managing
| state and sharing behavior (just like function programming!).
| Object programming looks at things from the perspective of
| the object; each object has some state and some behavior it
| can respond to when sent messages. Objects with compatible
| behavior can be substituted for each other (this is the
| essence of behaviorism[0]). This is symmetrical to function
| programming's pattern matching where values with compatible
| structure can be substituted as arguments to a function (this
| is also why I firmly hold the belief that Lisp and Smalltalk
| are symmetrical languages). Languages like Haskell even use
| things like "Integer a =>" which are basically object
| programming interfaces!
|
| [0]: https://handbook.selflanguage.org/2017.1/progguid.html#b
| ehav...
| hurril wrote:
| We don't disagree at all by the looks of it. I was objecting
| (lol!) to the description of OO as code and data together.
| _Together_ doesn't carry the necessary weight here because a
| struct and a module of functions in the same file is also
| code and data together.
|
| It's that the receiver of those messages have an identity
| that defines it.
|
| Inheritance and polymorphim is of course central to OO, but
| it's not defining characteristics imho because inheritance is
| "module arithmetic" and polymorphism has plenty of flavours.
|
| (I am in no way trying to have a go at OO either.)
| cmrdporcupine wrote:
| You've hit the nail on the head, and I think the replies below
| aren't getting your original point. Which I think is actually
| your point :-)
|
| The defining feature of OO approaches is in shifting the
| emphasis in sw development into the concept of identity-bags.
| All the other stuff about message passing, encapsulation,
| inheritance etc is just window dressing and implementation
| details of this core concept of identity-classification in
| software systems.
|
| I like how the "Out Of the Tarpit" (2006) paper breaks this
| down into the notion of "intentional" vs "extensional"
| identity, and I think this is a great model.
|
| For them, OO is all about "intentional identity" in that we
| _create_ identities (objects, components) in our systems and
| move attributes and behavior onto them. OO shifts the analysis
| of software to identifying objects, components, services, and
| then makes software development all about the taxonomical
| management of them. In this model of software development, it
| 's all about identifying components and then shuffling the data
| and functionality "into" them and managing the flow between
| them. (FWIW the paper identifies this way of thinking as one of
| the prime culprits in unnecessary complexity in software
| systems, and I agree with them.)
|
| The contrast is "extensional" identity, where identity in the
| software systems are emergent from their attributes. In this
| box we can put the relational data model, and its cousins in
| logic programming (prolog, datalog etc). In those systems, we
| are not concerned with "creating" identities and classifying
| systems/components/objects, but instead declaring the raw
| attributes -- tuples, facts, relations -- only. _Queries_ then
| produce, at runtime, a multitude of results from permutations
| of those attributes. And, as you point out, equality
| /inequality is defined by the value of a tuples attributes, not
| by the object it lies in. This is crucial.
|
| There's a couple generations of software developers that are so
| immersed in the OO/intentional-identity way of thinking that
| they don't see that it's not intrinsic to software development,
| but actually a mental framework that was fostered through the
| 80s and 90s. It was an attempt to manage complexity and
| structure in software systems and also to create a marketplace
| of re-usable components. But I think it's actually _failed_ at
| that.
|
| Most classes, objects, components, microservices end up failing
| in two respects: they end up reflecting the company's org chart
| more than anything else, and .. worse .. the bulk of the code
| and complexity ends up being how one moves data between all
| these (programmer created) systems rather than what the actual
| data is. The relationships (lines) between the boxes becomes
| the focus instead of the stuff was shoved into the box, and the
| whole behavior of the system becomes difficult to reason about.
|
| And when developers encounter systems that _don 't_ work along
| this model -- relational databases, functional programming,
| logic programming -- they often scratch their heads, resist, or
| wrap them up/confine them in OO/component type structures...
| damaging their usefulness.
| kaba0 wrote:
| A great, and often under-appreciated point is - as put by Guy
| Steele - "We find that the notion of side effect and the
| notion of equality (object identity) are mutually
| constraining; to define one is to define the other."
|
| With that in mind, I do believe that objects, as in
| encapsulating some kind of side-effect, is a very good model.
| But it is not a replacement for _everything_ - a more data-
| oriented approach makes much more sense for some other parts
| of a program. Multiple views are absolutely possible within
| the same language /program, and should in general be the
| preferred way.
| cmrdporcupine wrote:
| What I would like to see is a system where object-
| orientation is emergent from a more set-oriented/relational
| model. I haven't put my finger on what exactly this _is_
| yet, but maybe it 's something like "methods" really being
| a kind of predicate-dispatch over matching tuples.
|
| So a "class" (and object identity) is really telling the
| system: when you see attributes X, Y, Z bundled together as
| a result of a query, here is a set of behaviors that you
| can apply on those tuples.
| kaba0 wrote:
| I don't know, relational model seems to be fundamentally
| mathematical/side-effect free.
|
| Like, I think we should think of a `Connection` as an
| object, not `User`, which is just plain data.
| cmrdporcupine wrote:
| I get your point, but ... updates on tuples are
| definitely a "thing" in a relational system... and the
| set of connections and their attributes and state can be
| modeled in relations quite easily... Not sure I see a
| conflict.
| kaba0 wrote:
| Fair enough, though I think a functional approach (you
| create a new set from the old, with one field changed),
| and somehow "committing" it back is fine, and no need for
| side effecting then (besides at the boundary).
|
| Maybe structural typing is somewhat similar to what you
| think of? Probably typescript, maybe go are the most
| well-known for this out of the statically typed ones, but
| clojure is also all about every "object" being a key-
| value map.
| cmrdporcupine wrote:
| Yeah, a functional approach is fine. In reality you can
| think of an "update" to a tuple in a relation as a
| "replace this relation-valued variable with a new one
| where this tuple has been modified." And so mutations to
| base relations ("tables" in SQL parlance) just means
| changing the value of that relation in in the global
| shared, named base relation list. And if you use a
| persistent data structure behind the scene, and do CoW
| etc, you can even probably make this relatively
| efficient...
|
| And I don't think it's so much structural typing, as it
| is defining method/function dispatch off predicate
| matches. More.. structural values? Clumsily:
|
| Disconnect(connection_id) = with project ConnectionState
| CS where CS.Id = connection_id { if CS.state !=
| 'connected Raise('not_connected) else CS.state =
| 'disconnected }
| senderista wrote:
| Mutating a tuple in a relation doesn't really make sense
| _per se_ , because a relation is just a subset of the
| cartesian product of its attribute domains, and only
| value-based semantics apply to sets. What does make sense
| is identifying a primary key for a relation, deleting a
| tuple, and inserting another tuple with the same primary
| key but different values of its other attributes. In
| practice, of course, this is equivalent to in-place
| mutation of identity-objects corresponding to unique
| values of the primary key.
| hurril wrote:
| Indeed, we seem to understand each other. A very well put
| post of you!
|
| I've come to appreciate these difference in later efforts to
| learn functional programming as a way of giving myself more
| tools. But for the longest time, it was very difficult for me
| to "get the boxes right" for what things go where. What is a
| module, an object, a function, a method, etc, coming from the
| object oriented universe.
|
| I still have the urge to "create objects" when I
| conceptualise solutions and _imho_ this isn't always a good
| path.
|
| These days OO exists within my horizon of tools for solving
| tasks. Before, it was the water, if you will. Some problems
| have a want for objects, others don't really.
| cmrdporcupine wrote:
| Cool. Where do you work?
| hurril wrote:
| I am a freelance programmer. I don't wish to out such
| information here.
| abberation wrote:
| cat-v at it again lmao
| Baldbvrhunter wrote:
| The site builder, Uriel [0], was like those of us in his
| circle, strongly opinionated.
|
| Sadly he suicided in 2011 with Sodium Pentothal he bought mail
| order from China. My final Skype message to him was "goodnight,
| don't drink the pentothal!" and Skype used to show it to me
| under his username every time I logged in.
|
| He, and his acerbic nature, are sorely missed.
|
| [0] https://www.reddit.com/user/uriel/
| kaba0 wrote:
| if you mean that ridiculous "gcc/pdf/web browser is ridiculous,
| use tcc/txt file/curl" bullshit page, then I can't take you
| seriously. Feel free to use a pocket calculator instead of your
| current device, I guess, that's not bloated.
| robaye wrote:
| The original author revisited this talk[1][2] in 2017. You can
| also read more of the same ideas from the data-oriented-design
| list[3].
|
| 1. https://www.youtube.com/watch?v=VAT9E-M-PoE
|
| 2.
| https://docs.google.com/presentation/d/1ST3mZgxmxqlpCFkdDhtg...
|
| 3. https://github.com/dbartolini/data-oriented-design
| nemoniac wrote:
| "I invented the term 'object-oriented', and I can tell you C++
| wasn't what I had in mind." ~ Alan Kay, OOPSLA '97
| vendiddy wrote:
| I believe Erlang/Elixir are closer to what he had in mind.
| (Processes with encapsulated state passing messages to each
| other.)
| freetonik wrote:
| And also Smalltalk, I believe. It appeared ~15 years earlier
| than Erlang. The idea of holding encapsulated state and
| receiving messages is its core concept, AFAIK.
| kragen wrote:
| guess who headed the smalltalk team and what team he was
| working on when he invented the term 'object-oriented'
| frou_dh wrote:
| It does make a lot more sense if messaging is asynchronous,
| because the metaphor breaks down immediately if sending any
| message requires the sender to freeze until a reply comes
| back.
| lucidguppy wrote:
| I don't think anyone would "want" CPP. It feels like it's
| multiple layers of "this is how we've always done it"
| interleaved with some really good ideas (STL).
| kaba0 wrote:
| Just because someone coined the term, doesn't mean that they
| get to define it. The industry absolutely _doesn 't_ use OOP in
| any way similar to what Alan Kay does, and the former is the de
| facto correct usage.
| cmrdporcupine wrote:
| It's also worth pointing out that as smart and important of a
| guy as he is, Alan Kay has not been employed as a software
| developer for a very long time.
|
| What OO has become in the industry is a product of practical
| concerns encountered by people working in the field. Can't
| say I like it much, but working in this industry for 25ish
| years, I can understand the pragmatic "whys" of every step of
| language and tool design fairly well.
| freetonik wrote:
| I find "modern" OOP concepts apply pretty well to graphical user
| interfaces. I believe that a good UI has a consistent and
| relatively strict hierarchy, with few "unique snowflake"
| elements. Even such a divisive idea as multiple inheritance often
| makes sense in UI, i.e. there could be an element inheriting low-
| level behavior (HTTP/API call, etc) from a button and a high-
| level behavior (style/presentation, etc) from a visual class.
|
| (In a way, even CSS is kind of OOP, albeit with very confusing
| overriding rules.)
|
| When writing generic backend code in a language like Java, I
| often found fighting against OOP because the business logic is
| not necessarily mappable to "dog inherits from animal". And I
| can't change the business logic, so OOP has to give in. But when
| developing user interfaces, I am usually in more control, I am
| actually designing it, not observing directly from the business
| logic. And OOP forces me to have a more consistent
| implementation, which is beneficial for the user, because
| consistent UI makes it easy for people to get used to it, find
| the underlying logic in it, and start relying on it. If all links
| on all web pages would inherit from normal HTML href link (like
| it was designed in the beginning), we wouldn't suffer from those
| weird JS web apps where you can't open a link in a new page for
| example.
| trashburger wrote:
| Inheritance is usually not the solution; composition and
| delegation is. Unfortunately, the current landscape of popular
| languages have equated "object programming == inheritance" in
| people's minds. The fact that these languages make inheritance
| easy but provide no syntactical or semantic support for
| composition and delegating to inner objects compounds this
| problem.
|
| > there could be an element inheriting low-level behavior
| (HTTP/API call, etc) from a button and a high-level behavior
| (style/presentation, etc) from a visual class.
|
| This becomes very messy very quickly, and you would need to
| clearly separate the concerns to make them non-expressible on
| either side. CSS and JS make this possible by being two
| different languages, one Turing-incomplete (in usual
| scenarios).
| frou_dh wrote:
| I think of inheritance as just being a workaday
| implementation detail to _facilitate code reuse_ in some
| scenarios. Polymorphism ( "many forms") is more related to
| the concept of interfaces, but as you touch on has been
| equated with inheritance in a lot of peoples' minds.
| _a_a_a_ wrote:
| Inheritance should be used for interfaces, not code reuse
| (unless that comes free with it).
| nradov wrote:
| Then how do you avoid duplicate code without using
| inheritance? Other solutions like placing shared
| functions in "helper" classes are far worse from a
| complexity and maintainability standpoint.
| javcasas wrote:
| "helper" classes are a symptom of an object-obsessed
| programming language, a language where functions can't
| live in a module, they must always exist in a class.
| Especially if it doesn't make sense for a class to hold
| them.
|
| You avoid duplicated code by making generic functions,
| putting them in modules, and making your objects accept
| them as parameters.
| magicalhippo wrote:
| In Delphi, this is my preferred way. I use interfaces[1]for
| the public-facing stuff, but I might use inheritance to
| implement the interfaces for code reuse and ease. I of
| course also use delegation a lot, but for some things I
| feel inheritance is a better fit.
|
| Regardless, the inheritance remains an implementation
| detail.
|
| [1]: https://docwiki.embarcadero.com/RADStudio/Alexandria/e
| n/Inte...
| _a_a_a_ wrote:
| > make inheritance easy but provide no syntactical or
| semantic support for composition and delegating to inner
| objects compounds this problem
|
| I admit I would really like a 'delegate function call to
| <object>' construct, but excuse me for being dense, what
| composition are you referring to? Other than inheritance and
| delegation, what else is there[1] , and what would a suitable
| construct supporting it in the language look like? TIA
|
| [1] (there is functional-style composition, but that's
| perfectly well supported in many decent languages like Scala)
| Tmpod wrote:
| > I admit I would really like a 'delegate function call to
| <object>' construct
|
| Yeah, I'd love to see Kotlin's delegation[0] developed
| further and appear in other languages as well.
|
| [0]: I admit I would really like a 'delegate function call
| to <object>' construct
| mike_hearn wrote:
| Kotlin's delegation feature is useful sometimes, but OOP
| inheritance still crops up more often.
|
| One reason is that inheritance is strictly more powerful.
| When you delegate (compose), you can't modify the inner
| working of the object you delegated to, you can only wrap
| it. Often that's not sufficient. You want to actually
| customize the implementation in some way. OOP's
| inheritance is ideal for this, because the superclass can
| clearly define chunks of functionality that subclasses
| are allowed to either (a) replace entirely or (b) wrap or
| (c) provide (abstract methods).
|
| This is a very flexible approach. With mere delegation
| you can only wrap, and only for method calls that
| originate _outside_ the object, and only for public
| methods.
|
| It's also clean. The distinction between what you must
| implement, what you can implement and what is for the
| user is defined in the type system and enforced by the
| compiler. Additionally you separate the interface for
| customizations from the interface for users (protected vs
| public), which keeps documentation and auto complete
| properly focused.
|
| When combined with dependency injection and code
| generation it also allows third party frameworks and
| libraries to enhance the code you write in various ways,
| that remain mostly tucked out of the way but can be
| revealed in an IDE in an instant (and a good IDE will
| show visual hints that there are such customizations in
| effect).
|
| The final advantage is that in some languages the
| implementation is highly optimized. On HN you'd be
| forgiven for thinking nobody writes virtual method calls
| since 20 years ago but in reality virtual method dispatch
| is so common in real programs that it's heavily optimized
| by both CPUs (sophisticated indirect branch caching) and
| language runtimes (PICs in fast VMs like Hotspot or V8).
| Composition is less well optimized.
|
| Inheritance gets an overly bad rap on Hacker News,
| despite how prevalent it is. I think this is because it's
| so flexible you can easily make a mess with it, and some
| people have. Also the decision of Java to make all
| classes and methods open by default means overriding is
| often used as a hacky way to hot-patch libraries in the
| field, rather than as part of any principled overall
| design. Kotlin reverses that decision to some extent and
| so classes and methods are all final by default, unless a
| compiler plugin overrides that.
|
| I also suspect the move to web development has had this
| effect, because in most classical OOP toolkits you are
| allowed or even encouraged to extend the UI by
| subclassing pre-existing node types. So you can make a
| custom button by subclassing Button or whatever. HTML is
| implemented using classical OOP designs, but mostly for
| implementation reasons that isn't exposed to the web
| developer who instead is expected to customize the
| controls purely through CSS and setting properties. If
| you want to make a custom button widget, well that's too
| bad, better learn React or something similar. So people
| just aren't exposed to the situations where OOP is useful
| because browsers weren't designed for UI, and the memory
| of why it was originally developed atrophies.
|
| And some of it is just memeing.
|
| OK, now tell me why I'm wrong.
| _a_a_a_ wrote:
| I don't have much time so pardon the brevity, but I
| believe it's been proven that inheritance and delegation
| have equal expressiveness.
|
| > Often [delegation] not sufficient. You want to actually
| customize the implementation in some way. OOP's
| inheritance is ideal for this
|
| The usual way of customising behaviour without
| inheritance is to pass in an object to actually implement
| the behaviour. var LogToConsole = new
| logger(new WriteToConsole());
|
| vs var LogToFile = new logger(new
| WriteToFile());
|
| Which customises the logger class. Trivial example but I
| use this a lot.
|
| > I think this is because [inheritance is] so flexible
| you can easily make a mess with it, and some people have
|
| agreed
| mike_hearn wrote:
| It's all Turing complete, so of course you can implement
| the same thing as protected methods by using callbacks.
| When I say power here I don't mean in the sense that you
| literally can't do it any other way, I mean it's more
| convenient and natural.
|
| C++/Java/Kotlin style OOP is basically about putting
| common design patterns into language syntax. You could do
| it all with C, but it's easier if the compiler does it
| for you. For example Kotlin has "data classes" and Java
| now has "records" which are in both cases classes, but
| they sacrifice inheritance in order to auto-generate more
| boilerplate for you. That's still OOP though.
| lmm wrote:
| > One reason is that inheritance is strictly more
| powerful. When you delegate (compose), you can't modify
| the inner working of the object you delegated to, you can
| only wrap it.
|
| It's more powerful, sure, but in the same way that goto
| is more powerful than if/for/while. In theory some
| languages give you the tools for a superclass to expose a
| stable interface to its subclasses (ish - even Java
| doesn't give you a way to specify "subclasses may read
| but not write this field", for example), but in practice
| they're rarely used (Wicket is literally the only library
| I've seen to offer an effective inheritance interface);
| the overwhelming majority of the time inheritance ends in
| fragile base classes and the resulting bugs.
|
| Which is perhaps inevitable; the trouble with giving you
| the tools to change the superclass's behaviour in
| unintended ways is that it results in people changing the
| superclass's behaviour in unintended ways. If you have a
| locked-down inheritance model where classes and methods
| are final by default then maybe you avoid the fragility
| problem - but by the same token if subclasses can only
| use extension points that were deliberately exposed then
| you're probably not gaining much flexibility over what
| you could do with composition.
|
| > The final advantage is that in some languages the
| implementation is highly optimized. On HN you'd be
| forgiven for thinking nobody writes virtual method calls
| since 20 years ago but in reality virtual method dispatch
| is so common in real programs that it's heavily optimized
| by both CPUs (sophisticated indirect branch caching) and
| language runtimes (PICs in fast VMs like Hotspot or V8).
| Composition is less well optimized.
|
| Shrug, as the language design consensus shifts, the
| implementation optimizations will follow. Composition
| should ultimately be easier to get better performance out
| of, because it's more constrained (in particular, knowing
| that a given class or method is final means that you can
| optimise that class or method much more aggressively).
|
| > So people just aren't exposed to the situations where
| OOP is useful because browsers weren't designed for UI,
| and the memory of why it was originally developed
| atrophies.
|
| AIUI the association between OOP and GUI is more of a
| historical coincidence than a deliberate design. Though I
| agree that they do seem to fit together well.
| ernst_klim wrote:
| > Unfortunately, the current landscape of popular languages
| have equated "object programming == inheritance" in people's
| minds.
|
| Because OOP is somewhat equals to inheritance (or more
| strictly late binding + open recursion + self-polymorphism,
| inheritance is just one way to achieve that).
|
| "delegating" is completely orthogonal to OOP, you can have
| delegates in any modular language, like ML. Saying "you need
| to use more delegates and less inheritance" basically equals
| to "you need to write less OOP code".
| crabmusket wrote:
| > you can have delegates in any modular language, like ML
|
| I'm struggling to understand this, could you give an
| example?
| ernst_klim wrote:
| module type Repo = sig type t
| type user val get_user : t -> name:string
| -> user option val get_all_users : t ->
| user list end module LoggedRepo (R :
| Repo) : sig (* Resulting module is of type Repo
| plus create function *) include Repo
| val create : delegate:R.t -> logger:Logger.t -> t
| end = struct type t = { delegate :
| R.t; logger : Logger.t } type user = R.user
| let create ~delegate ~logger = { delegate; logger }
| let get_user repo name = Logger.debug
| repo.logger "Get user by name"; R.get_user
| repo.delegate name let get_all_users
| repo = Logger.debug repo.logger "Get all
| users"; R.get_all_users repo.delegate
| end
|
| In languages like C it would be harder since they don't
| have notion of interface and implementation, so you would
| have to implement that as an object-like structure of
| function pointers and opaque structs for state.
|
| The point is, for delegate you don't need lately bound
| methods and virtual dispatching, i.e. oop, only the
| notion of interface and implementation supported (or
| emulated), and then you can create implementations that
| wrap another implementations.
| _old_dude_ wrote:
| You still need virtual dispatch (or type pattern
| matching) if there is a variable which can contain either
| a BDRepo or a LoggedRepo(BDRepo).
| ernst_klim wrote:
| No, you don't. LocalRepo.t != Repo.t, and all the
| functions are purely static. LocalRepo.get_user won't
| work with other Repo.t, and other Repo.get_user won't
| work with LocalRepo.t. What you would need is to
| parametrize your further computation with a Repo module,
| as in module HttpHandler (R : Repo) :
| sig val handle_user : R.t -> HttpWriter.t ->
| username:string -> unit end = struct
| let handle_user repo writer ~username =
| match R.get_user repo username with | None
| -> Writer.write_status writer 404 | Some
| user -> Writer.write_body writer (User.to_string user)
| end
|
| and then to construct a handler module with a proper
| repo.
| mrkeen wrote:
| > Unfortunately, the current landscape of popular languages
| have equated "object programming == inheritance" in people's
| minds.
|
| That's because inheritance isn't really elsewhere, making it
| uniquely an OO concept (though I'm sure there are
| exceptions).
|
| When people say OO is all about X, Y, and inheritance, well,
| X and Y are in most of the other languages too.
| BurningFrog wrote:
| The big "X" is that the code is implemented by objects
| calling each other.
|
| That's what OO is to me. Inheritance is nice for some
| things, but entirely optional.
| jrumbut wrote:
| We forget that favoring composition over inheritance is one
| of the core ideas of OOP. It's supposed to be, at least.
|
| Textbooks have an example like "Dog inherits from Animal" as
| a way to demonstrate the concept of inheritance, but people
| took that as an invitation to become Linnaeus and create a
| hierarchical taxonomy of their domain.
|
| It might be a useful exercise, actually, but it's rarely the
| best way to design software and you can do OOP (and do it
| better) with sparing use of inheritance (by replacing it with
| extensive use of composition).
| bob1029 wrote:
| The #1 canary for me is circular dependencies. In more complex
| domains, a strict hierarchy of types begins to fall apart
| rapidly.
|
| In my experience, a relational model is the best solution for
| domain modeling precisely because it can directly represent
| circular dependencies without any sort of weird interface
| hacks, one off Init() methods, etc.
|
| A practical example of this would be something like the domain
| of banking. In a bank, you generally have 2 major types of
| entities: accounts and customers. Accounts can have many
| customers and customers can have many accounts. Which type
| should "win"? In SQL you don't have to make this decision at
| modeling time - it is deferred to the caller and its specific
| view of the world.
|
| You can certainly emulate this in OOP, but then you are stuck
| doing joins between collections manually.
| Xcelerate wrote:
| This is why I prefer multiple dispatch over classes:
| functions dispatch on the types of all arguments, not just
| the type of the implicit first argument. After first
| encountering this paradigm in Julia, it's painful to use
| other languages that don't have this feature.
| JanisErdmanis wrote:
| I don't think multiple dispatch solves this issue. Julia
| does have a strong type hierarchy and the proposed example
| with customers and accounts would be likewise hard to
| model. IMO only Rust like traits solves this issue
| elegantly.
| bob1029 wrote:
| > solves this issue elegantly
|
| What about cycles?
|
| For example, "List all distinct customers from all
| accounts that are owned by customers who are members of
| some other subset of accounts".
| JanisErdmanis wrote:
| I agree Rust does not make it easier either. This is an
| interesting design problem.
|
| I tried it with ChatGPT and it did produce a robust
| solution. This does not seem to be a limitation for
| strong type hierarchy after all:
|
| ```
|
| # Define a struct for Customer struct Customer
| id::Int name::String # Other
| customer-related fields
|
| end
|
| # Define a struct for Account
|
| struct Account id::Int
| balance::Float64 type::String # e.g.,
| "Savings", "Checking" # Other account-
| related fields
|
| end
|
| # Define a struct to manage the many-to-many relationship
|
| struct CustomerAccount
| customer::Customer account::Account
|
| end
|
| # Define a struct for the Bank which holds all
| information
|
| struct Bank customers::Dict{Int,
| Customer} accounts::Dict{Int, Account}
| customerAccounts::Vector{CustomerAccount}
|
| end
|
| ```
| bob1029 wrote:
| > it did produce a robust solution.
|
| Indeed. It produced a relational solution that emulates
| what you'd do with SQL tables.
|
| This is approximately the schema shape we use in our
| product today.
| nradov wrote:
| I don't see the problem. You can have a class for accounts, a
| class for customers, and a class for the links between
| accounts and customers. Those classes are peers; there's no
| need for one to be the winner. Most OOP languages also have
| ORM frameworks that can help here. I've built stuff like that
| in Java and in practice it's just not issue.
| uticus wrote:
| I understand what you mean but at the same time in the web-dev
| world I find it interesting that React is migrating from OOP to
| functional.
|
| ("We recommend defining components as functions instead of
| classes." at top of page at
| https://react.dev/reference/react/Component)
| codeptualize wrote:
| React has always used functional programming patterns. They
| used classes to define components, but even those applied
| functional principles.
| meheleventyone wrote:
| From the looks of things the main benefit is less
| boilerplate? What are the other benefits from beyond this?
| codeptualize wrote:
| Mainly hooks. Hooks make it easy to reuse component logic,
| something that was difficult with class components. You'd
| need HOC's which are hard to deal with, especially with
| prop collisions and refs.
|
| (This change has little to do with OOP vs functional. React
| calls them function components, not functional. React has
| always used functional patterns, even in the class
| components.)
| meheleventyone wrote:
| Thanks I went and looked up the different approaches
| (have never written a line of React in my life) and I
| definitely like the hooks more on first blush.
| mike_hearn wrote:
| React isn't actually functional. It's OOP in disguise.
| There's no such thing as functional UI, in the classical
| sense of FP. It's one of the weaknesses of FP.
|
| Although React tutorials start by saying your UI becomes a
| pure function of your app's state, React components do have
| encapsulated state. Any time you call useState inside a react
| function you're doing something that doesn't exist in real FP
| programs. Additionally React boils down to a way to create
| DOM elements, which are highly stateful C++ objects exposed
| to JavaScript, implemented using inheritance:
|
| https://source.chromium.org/chromium/chromium/src/+/main:thi.
| ..
|
| An HTMLButtonElement _is a_ HTMLFormControlElement _is a_
| HTMLElement (and a ListedElement and a FormAssociated).
|
| The rules for how you write code in React (or Jetpack
| Compose) violate even the most basic rules of the host
| language. useState defines or returns what is effectively an
| object field (it's data associated with that widget), but it
| looks syntactically like a local variable. It's not a local
| variable though because its value will survive the exit of
| the scope. Nor is useState a normal function call because
| there's a hidden invocation counter used to work out what
| state slot you want, which is why you aren't allowed to call
| it inside conditional branches and why loops need special
| handling.
|
| So in React's "functional style" we can see that you're
| actually defining objects with encapsulated state, that
| register event handlers, that export properties and so on.
| It's just obfuscated to look like it's not OOP.
| mwcampbell wrote:
| I'm curious, do you have a current favorite way of
| implementing UI, particularly for desktop applications,
| that isn't obfuscated and doesn't violate the rules of the
| host language? Or do you find that Jetpack Compose is a
| good choice overall despite those shortcomings?
| mike_hearn wrote:
| I'm still making up my mind about Compose, there are
| aspects I really like and aspects I really don't. It's
| not the fault of Compose itself, the aspects I don't like
| are basically the conceptual model as copied from React.
|
| Honestly for recent simple GUIs I've made for my own in
| house tools I'm still using JavaFX. Mostly because I
| always seem to end up needing a good table view, and
| because I happen to know it. If I were working with it a
| lot I'd end up maintaining a TornadoFX style set of
| wrappers but for simple stuff the plain API works OK.
|
| Because I'm weird I'd probably use it for low-traffic web
| apps these days too, with JPro (jpro.one). Probably not
| for mobile web, and not for stuff where users are on high
| latency connections in general, but if the user has a
| nice big screen and a low latency connection I'd
| personally find it a lot easier and more productive than
| dealing with HTML and CSS. The results are
| indistinguishable for the user if you are careful.
| mwcampbell wrote:
| Thanks for that info. I promise the original intent of my
| question wasn't to find an excuse to nag about
| accessibility, but I should point out that JPro is
| certainly not indistinguishable from typical HTML for a
| screen reader user, judging by the JPro website. In
| particular, the links are marked up strangely, with a
| separate link for each word in a multi-word phrase, and
| there are no headings or landmarks to aid navigation.
| Just thought you should know.
| convolvatron wrote:
| you can certainly write functional UI using a differential
| 'pure core state on the outside model' (someone here had a
| discussion that gave this thing a name)
| lmm wrote:
| > Any time you call useState inside a react function you're
| doing something that doesn't exist in real FP programs.
|
| Meh, it's just a monad, hidden in a fake syntax so people
| don't panic.
|
| > Additionally React boils down to a way to create DOM
| elements, which are highly stateful C++ objects exposed to
| JavaScript, implemented using inheritance
|
| And C++ and JavaScript are just ways to run machine code,
| which is fully unstructured. But all of that is an
| implementation detail. React creates DOM elements because
| that's what you have to do to render UI in the browser, not
| because its creators think the DOM is a good idea.
|
| > So in React's "functional style" we can see that you're
| actually defining objects with encapsulated state, that
| register event handlers, that export properties and so on.
| It's just obfuscated to look like it's not OOP.
|
| It's not OOP - it doesn't have inheritance, most state is
| (idiomatically) immutable and exposed, and as you noted,
| React state does not follow the same rules as OOP state.
| While it's not fully pure functional (I'd actually say the
| React state model is a novel and interesting thing, that I
| expect may end up being a language feature in the next
| generation of successors), it's closer to functional than
| traditional OOP, and most of the benefits are those of a
| functional rather than OO style.
| kaba0 wrote:
| React is not a UI library itself -- the actual UI stuff is
| delegated to the DOM. It is more like a library on top a UI
| library, that manages the state->representation flow (where
| reactive FP is indeed a good choice).
| ithkuil wrote:
| And in case it wasn't clear let's say it explicitly: the
| DOM is pretty much an object oriented model (the O stands
| for object)
| mostlylurks wrote:
| Yes, but the fact that react uses the DOM is because that
| is what the very restricted platform gives you out of the
| box, and opting for other options would have several
| disadvantages that are purely the result of those other
| options not being directly provided by the platform
| itself. A reactive UI library (like react) could very
| easily be implemented on top of other UI paradigms as
| well, including decidedly non-OOP ones. There's nothing
| special about object oriented UI systems that motivates
| reactive UI libraries to use them except for the fact
| that that's what the underlying platform provides because
| that was what was in fashion when the platform was taking
| shape.
| ernst_klim wrote:
| > I find "modern" OOP concepts apply pretty well to graphical
| user interfaces.
|
| Though most contemporary frameworks (react, elm, android
| compose) tend to prefer (functional) reactive programming.
| Scarblac wrote:
| That's also because they exist in a browser. The browser
| displays one page (from the viewpoint of a web app) and has
| one global event loop, the event loop calls into the
| application code. Application code doesn't run at other
| times. Reactive programming is a good fit for that.
| PaulDavisThe1st wrote:
| > one global event loop, the event loop calls into the
| application code. Application code doesn't run at other
| times.
|
| In the 80s we called that "event-driven programming", and
| until threads became widely available, it is how all native
| GUI programming worked, more or less.
| marcosdumay wrote:
| Event-driven is the "reactive" part only. And the old
| implementations of it where so less capable of fine-
| handling the events and complex dependencies that it's
| reasonable to change the name.
| codeptualize wrote:
| I disagree on the UI side. UI has been moving towards more
| functional declarative approaches as it's much easier to
| manage.
|
| CSS is also interesting, we mostly try to avoid the inheritance
| with scoping, utility classes and or css in js solutions as it
| becomes really hard to manage otherwise.
| marcosdumay wrote:
| > In a way, even CSS is kind of OOP
|
| Inheritance makes for a very powerless form of logical
| programing. CSS is way more than OOP.
|
| (Powerless is always preferred if it's sufficient, but CSS
| clearly shows a single hierarchy isn't sufficient.)
| nine_k wrote:
| Inheritance is likely the worst idea of OOP's implementations.
|
| The right way to achieve the same and better results is
| interfaces / typeclasses / traits. They work pretty well in
| languages from Rust and Haskell to Typescript, Kotlin, Java,
| and even Go. (Go is a bit special because you don't even have
| to declare that your class implements an interface, as long as
| it actually has a set of methods with right signatures.)
|
| Ideally, all public methods should only describe parameters and
| return values in terms of interfaces, and never as concrete
| classes.
|
| If you need to take a big class and inherit from it to override
| one or two methods, it's a failure to compose the things
| correctly. These one or two methods usually are better off as
| their own entity which you can pass to the "base" as
| parameters. This both prevents proliferation of "god objects",
| increases testability, and improves build times (shallower
| dependency graph, yay).
| 38 wrote:
| > Ideally, all public methods should only describe parameters
| and return values in terms of interfaces, and never as
| concrete classes.
|
| Citation needed. I've seen interface heavy Go code, and
| without fail it's always unreadable garbage. Interfaces are
| best used in moderation, at API boundaries only.
| ChrisMarshallNY wrote:
| For me, personally, I've learned to use the right tool for the
| job.
|
| In some cases, it's OOP, in some cases, it might be something
| like FP, or Protocol-Oriented Design.
|
| I write in Swift, which supports all of the above. It's been a
| while, since I've worked with C++, but I have heard that the
| modern version of the language similarly supports multiple
| paradigms.
|
| For the frontend, if I am writing for UIKit, I use OOP, and
| fairly standard MVP, as that is exactly what the framework was
| designed to afford. I haven't done much SwiftUI, but I suspect
| that I'll be using a lot less OOP, when I do.
|
| On the server end, I tend to use good ol' PHP. It's not an
| especially good OOP language, but I find that OOP isn't as
| relevant for my backend work. That work tends to look a lot like
| very old-fashioned Structured Programming, which PHP is good for.
| As others have pointed out, I've found it easy to go down
| rabbitholes, with backend OOP.
|
| There's a lot to be said for an approach as an experienced,
| multidisciplinary professional.
|
| It seems as if programming and software engineering pundits
| generally start from the premise that all [other] software
| developers stink, and rules and tools should be used in an effort
| to force them to write "good code," as opposed to, for example,
| hiring smart people, and training and mentoring them to be
| adaptable, resourceful, disciplined engineers.
|
| That's nothing new. I have been seeing this for my entire [long]
| career. The "new hotness" changes, but the attitudes are
| consistent.
|
| Here's an old joke, from the 1980s:
| https://news.ycombinator.com/item?id=29345907
| kbrannigan wrote:
| Do you use any framework for PHP?
| ChrisMarshallNY wrote:
| No, but if I did, it would probably be Laravel. A number of
| folks I highly respect, swear by it. In that case, I'd
| probably be using more OOP.
|
| With PHP, I tend to use OOP constructs as namespaces.
|
| My backend work is fairly humble. Most folks here, would
| laugh at it, but it works really, really well.
| zigzag312 wrote:
| OOP, FP and other paradigms are all useful. Just don't do
| extremes like _everything is an object_ or _everything is
| immutable_ or _everything is without side effects_.
|
| I enjoy multi-paradigm languages the most.
| danielvaughn wrote:
| Yep. Any given software project of non-trivial complexity is
| going to be composed of many types of problems, each of which
| requiring its own solution and approach. I don't think I've
| ever worked on a codebase that didn't need both OOP and FP in
| different places.
| pydry wrote:
| Yeah but have you considered joining the church of everything
| is a list?
| augustk wrote:
| > Data abstraction > Encapsulation > Polymorphism
| > Inheritance
|
| Note that it's the last two concepts which are specific to object
| oriented programming. The first two are already supported by non
| OOP languages like C and enable implementation of abstract data
| types. I think it's unfortunate that all concepts are often
| introduced in the same OOP course which may make the students
| believe that a class and an abstract data type is the same thing.
| Log_out_ wrote:
| > Encapsulation as an idea failed spectacular again and again.
| What would have been valuable would have been a way to enforce
| layers of abstractions, with no composition or inheritance of
| objects on the same layer.
| ryandv wrote:
| Polymorphism is not at all specific to OOP, and there are in
| fact different kinds of polymorphism; "generics" are pervasive
| in pure FP languages such as Haskell or imperative/functional
| languages like Rust, and are an instance of parametric
| polymorphism. Ad-hoc polymorphism, or "operator overloading,"
| can also be seen in both languages as the "Eq" typeclass/trait
| and the equality operator `==`. It's subtype polymorphism and
| deeply nested inheritance structures that are maybe particular
| to OOP.
|
| It's also worth making a distinction between an abstract data
| type - a "thing" and some set of operations defined over it -
| and a data structure - a particular in-memory construction and
| set of procedures defined over it, often used to realize an
| abstract data type. An example being a dictionary ADT -
| supporting get, set, delete, etc. being realizable through
| different data structures; for example, a linked "association
| list" of (key, value) pairs vs. a hash table - with each having
| different performance characteristics, despite possessing the
| same properties as the ADT they implement.
| adrian_b wrote:
| Polymorphism that is resolved at run-time, i.e. "virtual"
| functions is what is specific to OOP.
|
| The term "polymorphism" was first used in the documents of
| the language CPL (in mid sixties), the source of BCPL, where
| in British English it corresponded to what in the American
| documents of the same age, starting with McCarthy, were
| called overloaded operators and overloaded functions.
|
| The use of "polymorphism" mostly for the run-time
| polymorphism of OOP languages has appeared only a couple of
| decades later, with Smalltalk.
|
| "Generic", which might be a better term than both, has been
| popularized during the programming language contest that has
| lead to Ada (1979). Nowadays it is mostly used for
| polymorphism/overloading that is resolved at compile time,
| because this is how the term has been used in Ada.
| ryandv wrote:
| Even still, there exist non-OOP implementations of runtime
| dynamic dispatch, such as Clojure's multimethods.
| ykonstant wrote:
| I thought Julia's multiple dispatch was run-time
| polymorphism?
| mostlylurks wrote:
| Even polymorphism that is resolved at run-time is not
| specific to OOP. In fact, it's used quite extensively
| outside of OOP, including in both functional and procedural
| programming. Even ignoring the types of dynamic
| polymorphism present in other paradigms that might be
| called "OOP-inspired", there are commonplace patterns such
| as the use of closures that let you achieve run-time
| polymorphism and data encapsulation.
| augustk wrote:
| What you call an abstract data structure is what I have
| learned is called a concrete data type. To my understanding
| an abstract data structure is a module with global (single
| instance) private data which is manipulated using the
| module's interface. In OOP terminology it's a singleton
| class.
| joe_the_user wrote:
| How exactly does C support encapsulation?
|
| I mean, you can do OO type stuff with anonymous handles but the
| compiler will let a function take one handle type where another
| belongs and things will fail opaquely but immediately if you're
| luck and fail slowly if you're unlucky. Which is to say there
| is no "support".
| augustk wrote:
| > How exactly does C support encapsulation?
|
| You simply declare a type as a pointer to a struct in the
| header file along with the functions that operate on
| variables of this type, and define the struct and the
| functions in the implementation file.
|
| M.h typedef struct M_TDesc *M_T;
| void M_Foo(M_T x); ...
|
| M.c #include "M.h" struct M_TDesc
| { ... }; void M_Foo(M_T x) {
| ... } ...
| ryandv wrote:
| The slide deck is about performance pitfalls of OOP languages,
| which can surreptitiously introduce cache misses, random access
| to memory, and excessive levels of indirection into your code:
|
| > Prefetching
|
| > Data accesses are now predictable
|
| I'm reminded of a Stroustrup talk in which he says that vectors
| will always outperform linked list structures, by taking
| advantage of spatial locality and predictable access patterns to
| enable better cache prefetching. He even has a slide on C++ vs
| "True OO" style object graphs, and it looks remarkably similar to
| what's described in the OP:
| https://www.youtube.com/watch?v=YQs6IC-vgmo&t=3m53s
| commandlinefan wrote:
| That went in a different direction than I expected it to - but
| it's also not specific to object oriented programming. Richard
| Stevens summarized this 40 years ago: "modularity is the enemy of
| performance". We've mostly accepted that this is an acceptable
| tradeoff, although the author (and I) aren't fully on board.
| rkagerer wrote:
| When the whole OOP movement gained traction I struggled to accept
| the degree of value it provided above simple structs and
| functions (at least for an organized programmer).
|
| After decades developing on a lot of diverse platforms in a
| variety of languages and models, I've decided there ain't much
| you can't achieve without structs and functions (or their
| equivalent).
|
| Sure, any tool is most efficient in the hands of a master who
| knows how to wield it. But in practice all those shiny extras too
| often become productivity distractions.
| AnimalMuppet wrote:
| To me, the big difference between a struct and an object is
| when data has constraints.
|
| Let's say I have a rotation matrix. That means that the squares
| of all the entries in any one row or column add up to 1. If I
| have such a structure, and I find that it has rows that no
| longer add up to 1, well, what function messed it up? I have no
| idea. And if I have a million lines of code, that gives me a
| lot of places I have to look.
|
| But with an object, if I made that data private, only a member
| function can have messed up. I may still have a thousand lines
| I have to look at, but that's a lot less than a million. For
| larger code (tens of thousands of lines and larger), I consider
| access control to be essential, just to limit the size of the
| code that could possibly be at fault.
|
| Back in the day, I spent a _lot_ of time playing "who messed
| up the structure?" in structure-and-function code...
| ReleaseCandidat wrote:
| > But with an object, if I made that data private
|
| Being able to make a field of a record private does have
| nothing to do with OOP, even in C you can hide a struct's
| fields.
___________________________________________________________________
(page generated 2023-12-28 23:02 UTC)