[HN Gopher] When objects are not enough (2021)
___________________________________________________________________
When objects are not enough (2021)
Author : o2l
Score : 52 points
Date : 2024-07-22 20:48 UTC (3 days ago)
(HTM) web link (www.tonysm.com)
(TXT) w3m dump (www.tonysm.com)
| smitty1e wrote:
| Needs "2021" in the headline
| artemonster wrote:
| I never liked ,,OOPs world" obsession with a receiver. Why the
| hell receiver of a message gets to dispatch the implementation at
| very late moment? Why not context? And when there are many
| receivers? I.e. a composite object of an int and a float receive
| message ,,add" - who decides which implementation to use and why
| the heck any of them may even know how to add self to another?
| There were many attempts to solve this and all were horrible
| (i.e. extension methods). I have been experimenting with these
| concepts too (i.e. a receiverless message is a throw, which will
| be picked up by an enclosing effect handler via pattern match),
| but its all unreadable and unmaintainble mess. Anyone doing the
| same thing? I would love to exchange thoughts :) extras to read:
| ian piumarta's papers on his OOP system and a paper on Korz
| programming language
| Joker_vD wrote:
| > Why the hell receiver of a message gets to dispatch the
| implementation at very late moment? Why not context?
|
| Well, then it is the context who is the actual receiver, isn't
| it? Since in this scenario the object may never even receive
| the message since the context has already processed it on its
| own, so calling it a "receiver" would be incorrect.
|
| > a composite object of an int and a float receive message
| ,,add" - who decides which implementation to use
|
| The composite object itself, who else? It can do anything,
| including doing nothing, or not using any of their
| implementations, or dividing its int by its float, etc.
| meheleventyone wrote:
| Maybe I'm misunderstanding but the whole point of any messaging
| system is that the sender "doesn't know how to process the
| message" and it's up to the receivers to process it and
| coordinate amongst themselves. Otherwise you could just do the
| processing where the message was sent. For non-OOP examples you
| have most service architectures.
|
| So from that perspective an int-float object would be built by
| composition and the containing object would receive and process
| the message before dispatching to it's component objects as it
| saw fit to accomplish the task of being an int-float object.
| c-linkage wrote:
| In OOP as I understood it, messages are first-class entities
| at the same level as "objects". This is strikingly different
| from modern OOP languages where a message is conflated with
| "function calls". This is pointed out in the article: "[I]n
| Smalltalk, messages 'were not real messages but disguised
| synchronous function calls', and this mistake was also
| repeated in other languages[...]".
|
| If a message is a first class entity, then an object can
| technically have only one "function call" -- receive message.
|
| So that an object can simulate an arbitrary set of functions
| with arbitrary arguments, the implementation of the "receive
| message" function cannot impose conditions on the format or
| content of the message. Instead, the message must be encoded
| in a self-describing format so that the object can
| interrogate the message and its contents, and only _then_
| decide what to do with that message, up to an including
| ignoring the message entirely.
|
| To make this more concrete, imagine having an JavaScript
| object that has only one method: receive messages encoded as
| JSON strings. With JSON strings we can say that the message
| is self-describing and is easily parsed by the object. Once
| the JSON string is parsed, the object can then decide what to
| do based on the content of the message. This is both a late-
| binding and a dispatching activity.
|
| It should be clear that the version of OOP does not include
| anything about types. That's because OOP was designed with
| LISP-like languages in mind, where _symbols_ were processed
| and _strongly-typed objects_. It also means that build-
| /complile-time checking wasn't possible.
|
| I'd say the modern web with JavaScript and HTTP calls is more
| like the original OOP design than any modern "OOP"-like
| programming language.
| dexwiz wrote:
| Does send() and method_missing() in Ruby fit that bill?
| From what I remember from Ruby, and it's been a while, all
| method calls are just a message via send() with a symbol
| and arguments. Normal method calls are just syntactic sugar
| over this system. With method_missing() you can handle any
| messages that use a symbol that doesn't match a method
| name. You could make the object handle messages in a
| completely dynamic way.
| artemonster wrote:
| then why the receiver would know too? There are more parties
| in this orgy: message receiver, message sender (method
| caller), environment (static: like imports), context
| (dynamic: like stack). Why the heck of all of those the
| receiver decides?
| cypherpunk666 wrote:
| possibly related food for thought: google up DCI?
|
| https://duckduckgo.com/?q=trygve+dci
| hcarvalhoalves wrote:
| If you want a "receiverless message", you want a queue surely?
| codr7 wrote:
| Why not generic methods like in Common Lisp?
| ChrisMarshallNY wrote:
| _> An object has state and operations combined._
|
| My own definition has always been that an object has state and
| _identity_.
|
| I have never considered functions/methods to be a requirement for
| something to be an object.
| asgeir wrote:
| It's not a requirement, but an immutable object has fewer uses
| than a mutable one.
|
| Functions/methods/actions/operations are just different names
| for the operations which mutate the state of the object. So, I
| would argue that they are a necessary attribute of mutable
| objects.
| localhost8000 wrote:
| We could say that mutability (or having
| functions/methods/etc) is part of the object's identity.
| ChrisMarshallNY wrote:
| I consider _identity_ to be an object that is exclusively
| itself, and knows what it is. You can treat it in an opaque
| manner, and its own identity will dictate how external
| stimulus works on it.
| ChrisMarshallNY wrote:
| Maybe, but mutability does not require inbuilt operations.
|
| I have often used data-only objects, and passed them to
| fixed-context functions.
|
| It's a cheap way to get OO behavior, in non-OO languages.
| asgeir wrote:
| I would still classify that as object oriented behavior.
|
| You have a class/type of objects, and you have a set of
| functions or operations which are associated with (loosely
| or tightly) and operate on that type of object.
|
| For example, I would say that file descriptors are a handle
| to a type of object. They encapsulate the thing that they
| represent (file, DRM buffer, eventfd, whatever), and some
| of the functions that operate on them are polymorphic. For
| example, read, close, ioctl, etc., don't care if the file
| descriptor is a file, a queue, or whatever.
|
| You use syscalls to interact with file descriptors, but
| they are just as much object handles.
| ChrisMarshallNY wrote:
| It definitely is. I was just talking about the definition
| of "an object."
|
| It separates the functionality from the object. Turns it
| into an external stimulus to change the state of an
| object.
|
| I used this pattern, back in the 1990s, to make a C API
| behave like a C++ API. This was back when every compiler
| had a different stack convention, and the only one we
| could rely on, was C.
|
| To be fair, I did have function pointers, in some of the
| objects, that acted as "poor man's vtables."
|
| I know that the API was still in use, 25 years later.
| asgeir wrote:
| It's possible that we are talking at cross purposes.
|
| I think you might be arguing that the object itself does
| not include the set of functions/methods/operations that
| act on it, whereas I see them as another attribute of the
| class/type which is an attribute of the object.
| knome wrote:
| Yeah, object orientation isn't just a language feature,
| it's a pattern for structuring interaction with data.
|
| And as for object orientation being less useful in
| immutable languages, I see it used plenty.
|
| Erlang and haskell both define dictionary types that return
| new copies of the dictionary on what would normally be
| mutating function calls. The dictionary's gory details are
| hidden behind the object's mask, leaving the user free to
| ignore if it is a hash into an array of arrays, as many
| dicts used to be and as allows sharing most of the entries
| between copies of the dict until resizing, or maybe it's
| actually a balanced tree or just an a-list.
|
| The outer code doesn't need to know because you create and
| manipulate the dictionary abstractly through helper
| functions, be they attached via the language or simply
| exposed while leaving the implementation opaque.
|
| Object orientation will also be used to hide the
| implementations of files, sockets, and other abstract
| resources.
|
| Many interfaces throughout the linux kernel use a plain C
| form of object orientation, a language which is definitely
| not object oriented on its own, filling out an 'interface'
| structure with functions to be used for objects originating
| from a particular area, allowing the kernel to interact
| abstractly with filesystems via the function pointers in
| the struct, for example.
| jghn wrote:
| OOP schemes like CLOS, Dylan, S4, and others keep Objects and
| Actions separate. This is similar to Haskell type classes.
| eyelidlessness wrote:
| Object identity _effectively implies_ mutability. Without
| that implication, two objects of the same structured value
| being non-equal doesn't mean anything (and would probably be
| better classified as a mistake).
|
| Another way to look at it is that property setters (or
| whatever mechanism is used to directly mutate an object's
| sub-data) is not meaningfully different from a method doing
| the same. You could even call it syntax sugar for the same.
| odipar wrote:
| My take is that identity doesn't imply mutability, if you
| _version_ objects. It could well be that your are looking
| at an old version of an object, using its unique identity
| combined with its version (number).
|
| Objects refer to other objects using their (immutable)
| identity. In turn, resolving identities to objects requires
| (version) scope which can be in the past or present.
| eyelidlessness wrote:
| > My take is that identity doesn't imply mutability, if
| you version objects. It could well be that your are
| looking at an old version of an object, using its unique
| identity combined with its version (number).
|
| Are you storing the version as part of the object? If so,
| they're no longer equal values regardless of identity. If
| not, what purpose is there in versioning the same value?
| Even if there is a purpose, are same-value-different-
| version objects not otherwise interchangeable
| unless/until some value change does occur?
| niam wrote:
| I think you're right that, in the object-oriented world, a
| method isn't a necessary condition for most people to consider
| something an "object".
|
| What the author seems to be saying is that if someone asked you
| to sell OOP, one way you'd sell it is by mentioning that an
| object can couple logic and state. That's a distinguishing
| factor between objects and other data structures.
| jghn wrote:
| > one way you'd sell it is by mentioning that an object can
| couple logic and state
|
| Depends on the OOP paradigm. This is not always the case.
| PaulHoule wrote:
| +1 for use of the word "Reification"!
|
| There's another universe of object-adjacent systems that may or
| may not be connected with conventional OO programming languages.
| Two examples I'd point to are Microsoft's COM (designed so it is
| straightforward to write and call COM objects from C) and the
| "objects" in IBM's OS/400. In both of those cases I think the
| reification is the important thing, although you can see
| reification in Java's object headers where objects get a number
| of attributes necessary for garbage collection, concurrency
| control, etc.
| smackeyacky wrote:
| COM dates back to a time when OO languages were uncommon but
| the techniques were being used in C.
|
| I worked on a system back then that had "objects" that was
| based heavily around function pointers.
|
| When we first got hold of the cfront C++ pre processor it did
| much the same thing but automated all the kludges we had for
| compile time checking.
|
| So I wouldn't really class something like COM as object
| adjacent, it was more "proto" OO
| bob1029 wrote:
| The objects are just one tool. You usually need a few to do the
| job well.
|
| The biggest thing I've seen blow up actual OOP projects has been
| a lack of respect for circular dependencies in the underlying
| domain. If you have one of those problems where it is ambiguous
| which type "owns" another type, then the moment you start writing
| methods in these types you are treading into the dark forest.
| Often times it is unclear that your problem exhibits circular
| dependencies until code is already being written and shipped.
|
| My approach to these situations is to _start_ with a relational
| data model. A SQL schema (and its representative DTOs) can model
| circular dependencies competently. You can then have additional
| object models (views) that can be populated by the same
| relational data store (just a different query). One other
| advantage with the relational modeling approach is that it is
| very easy to explain things to the business _before you write a
| single line of code_ [0]. The purpose of a SQL table can be
| demonstrated with a sample excel sheet with mock business data.
|
| This path was largely inspired by Out of the Tar Pit [1] and
| practical experience in fairly wicked domains (semiconductor
| mfg., banking, etc). I am not sure _Functional_ Relational
| Programming is the answer for everything, but the "Relational"
| part certainly seems to be universally applicable.
|
| [0]:
| https://en.wikiquote.org/wiki/Fred_Brooks#:~:text=Show%20me%....
|
| [1]: https://curtclifton.net/papers/MoseleyMarks06a.pdf
| dullcrisp wrote:
| Why would a type ever own another type? What would that even
| mean? Like an inner class? Or are you taking about ORMs?
| mattgreenrocks wrote:
| I want to re-read this and think about it more.
|
| But one thing stuck out: I never liked most of the distillation
| of actions as clean architecture would advocate for, be they
| lambdas in class form (DepositAction) or interactors. I feel
| strongly that they are the right thing in describing business
| logic, however.
|
| What did click for me is the conceit of a service, which the JVM
| world embraces. Services are plain old objects that have a method
| for each action you'd like to model. This is nicer than the
| aforementioned approaches because there is often common code to
| different actions. Like actions, services are the place where
| validation happens, persistence happens, and all of the
| interesting business logic. IO and orthogonal concerns are
| injected into them (via constructor), which lets you write tests
| about the core logic pretty easily.
|
| What you get is the ability to reason about what happens without
| the incidental complexity of the web. Web handlers then boil down
| to decoding input, passing it to the service, examining the
| result of calling an action, and then outputting the appropriate
| data.
|
| That's all they should've ever been doing. :)
| menotyou wrote:
| Yet another article about problems you'd never have if you
| wouldn't use object oriented paradigm.
| goatlover wrote:
| But then you'd have other problems that come with using another
| paradigm, since there's no silver bullet, and no paradigm that
| handles all problems better than other paradigms. Probably
| popular languages tend to be multi-paradigm.
___________________________________________________________________
(page generated 2024-07-25 23:07 UTC)