[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)