proj-oot-ootOopNotes2

" Go is “OO-ish” with its use of interfaces — interfaces are basically duck typing for your structs (as well as other types, because, well, just because). I had some trouble at first understanding how to get going with interfaces and pointers. You can write methods that act on WhateverYouWant? — and an interface is just an assertion that WhateverYouWant? has methods for X, Y, and Z. It wasn’t really clear to me whether methods should be acting on values or pointers. Go sort of leaves you to your own devices here.

At first I wrote my methods on the values, which seemed like the normal, American thing to do. The problem of course is that when passed to methods, values are copies of the original data, so you can’t do any OO-style mutations on the data. So instead methods should operate on the pointers, right?

This is where things get a little bit tricky if you’re accustomed to subclassing. If your operate on pointers, then your interface applies to the pointer, not to the struct (value). So if in Java you had a Car with RaceCar? and GetawayCar? as subclasses, in Go you’ll have an interface Car — which is implemented not by RaceCar? and GetawayCar?, but instead by their pointers RaceCar?* and GetawayCar?*.

This creates some friction when you’re trying to manage your car collection. For example, if you want an array with values of type Car, you need an array of pointers, which means you have to need separate storage for the actual RaceCar? and GetawayCar? values, either on the stack with a temporary variable or on the heap with calls to new. The design of interfaces is consistent, and I generally like it, but it had me scratching my head for a while as I got up to speed with all the pointers to my expensive and dangerous automobiles. "

---

" The Common Lisp Object System (CLOS) has multiple-inheritance, multi-methods, method combinations, introspection and extensibility via the MOP, generic functions that work on builtin classes, support for dynamic instance class change (change-class, update-instance-for-changed-class) and class redefinition (defclass, update-instance-for-redefined-class), a semi-decent story for combining parametric polymorphism and ad hoc polymorphism (my own lisp-interface-library), etc. Racket seems to still be playing catch-up with respect to ad hoc polymorphism, and is lacking a set of good data structure libraries that take advantage of both functional and object-oriented programming (a good target is Scala's scalaz or its rival cats). "

---

keithnz 3 days ago [-]

I think a lot of bad things happened when the advice around how to determine classes started by looking at the nouns in the problem domain.

OO is fundamentally about messaging not abstract data types. So if you start your design from trying to determine ADTs instead of trying to work out the kinds of message conversations that need to go on you end up with mess of types.

In the wizards and warriors [1] we see a large amount of modelling of a domain with rules. But we, even at the end, still have little idea of what messaging is going to be going on, there is a hint about attacking werewolves. But we've already made a lot of assumptions about what our first class entites are. Often what can happen is we find, while we thought we modelled it ok and even sorted out a rule based system, it still seems difficult for our objects to have conversations.

maybe what would be better is some thing that can attack other things and wizards, weapons, and warriors are all simply modelled by some composable set of stat modifiers.

reply

aryehof 2 days ago [-]

The original advice was to use nouns to "identify candidate classes", rather than "determine classes".

Sadly when confronted with a problem, the majority of programmers can get no further than writing down lots of nouns, perhaps dozens of them, with no idea how to combine the few actually required into an object-oriented design.

OO isn't fundamentally about messaging. That is incorrectly assuming that object-orientation began with Smalltalk, Alan Kay and Dan Ingalls. Smalltalk's "messaging with objects, all the way down" approach largely ended with its popular use and the widespread adoption of Java. Java's object model is entirely based on Simula (James Gosling Sept 2017).

reply

---

"information: it is simple. The only thing you can possibly do with information is ruin it. :) ... Don't ruin it By hiding it behind a micro-language ie a class with information-specific methods thwarts generic data composition ties logic to representation du jour Represent data as data " ... Objects were made to encapsulate IO devices, so there's a screen, but I can't touch the screen, so I have the object...That's all they're good for. They were never supposed to be applied to information...it ruins your ability to build generic data manipulation things." -- Rich Hickey, Simple Made Easy talk

So, in oot, we want to make it so that we can have classes and yet still manipulate the values of the attributes in those classes just as easily as if it were a map in clojure

---

BruceIV? 49 days ago [-]

[on the Cforall team] FWIW, the Go docs are somewhat ambivalent on the OO nature of Go[1]. Along similar lines, our main objections to object-orientation are that, in "traditional" implementations like C++ or Java, it tightly couples a number of features that we believe would be better served by a more orthogonal design.

More specifically, C++-style OO couples (1) in-memory layout of types, (2) behaviour of objects, (3) encapsulation boundaries, and (4) the hierarchical "is-a" relationship. In Cforall, (1) and (3) are handled by the usual C mechanisms of struct types and translation-unit boundaries [we go to great effort to maintain C-style separate compilation, though we will likely build a better namespace/module system at some point], (2) by traits, and (4) by a yet-to-be-implemented system of RTTI for specific "inherited" types [likely when the undergrad RA who started our exceptions implementation comes back as a Master's student this fall].

As you mention, many use-cases (including widgets in GUIs, but also abstract syntax trees in compilers) require some sort of heterogeneous collection type to work properly, and for this OO is the right model. We just don't want to force the entirety of that model on every use case, so we can just take traits when we want to write a polymorphic function over related types or an ADT like "Set" which requires its keys be sortable, or just take RTTI for exception handling, when the set of exception types we're handling is known statically, but we need to determine if the thrown exception inherits from one of the caught types.

[1] https://golang.org/doc/faq#Is_Go_an_obje

---

gbrown 2 days ago [-]

...You can still do (several types) of object oriented programming in R,...

mr_toad 2 days ago [-]

A sane language shouldn’t need three different object systems.

reply

gbrown 1 day ago [-]

Meh, S3 is nice and lightweight for a very particular kind of analysis interoperability. S4 isn't super useful IMHO. RC is very well thought out, and I've heard good things about R6. It might not be sane language design, but it works well for designing very different kinds of analytical procedures.

reply

---

losteric 7 minutes ago [-]

Is it unreasonable to think of the method as a semantic "port" to which messages (arguments) are passed?

---

revvx 2 hours ago [-]

> Isn't a method call a message, and the return value a message back?

It is!

...

Alan Kay uses the term "late binding". In Kay's opinion, "extreme late binding" is one of the most important aspects of his OOP [1], even more important than polymorphism. Extreme late binding basically means letting the object decide what it's gonna do with a message.

This is what languages like Objective-C and Ruby do: deciding what to do after a method is dispatched always happen during runtime. You can send a message that does not exist and have the class answer to it (method_missing in Ruby); you can send a message to an invalid object and it will respond with nil (Objective-C, IIRC); you can delegate everything but some messages to a third object; you can even send a message to a class running in other computer (CORBA, DCOM).

---

" Why OO Sucks by Joe Armstrong ((the Erlang guy))

Objection 1. Data structure and functions should not be bound together. Since functions and data structures are completely different types of animal it is fundamentally incorrect to lock them up in the same cage.... In most languages...To understand functions you have to understand the order in which things get done...Data structures...are intrinsically declarative

...

Objection 2. Everything has to be an object. Consider “time”. In an OO language a “time” has to be an object...But in a non OO language a “time” is a instance of a data type. For example, in Erlang there are lots of different varieties of time, which can be clearly and unambiguously specified using type declarations... -deftype day() = 1..31. -deftype month() = 1..12.

...

Objection 3. In an OOPL data type definitions are spread out all over the place. In an OOPL data type definitions belong to objects. So I can't find all the data type definition in one place. In Erlang or C I can define all my data types in a single include file or data dictionary.

...

Lisp programmers have know for a long time it is better to have a smallish number of ubiquitous data types and a large number of small functions that work on them, than to have a large number of data types and a small number of functions that work on them.

Objection 4. Objects have private state. State is the root of all evil. In particular functions with side effects should be avoided.

The “hide the state from the programmer” option chosen by OOPLs is the worst possible choice. Instead of revealing the state and trying to find ways to minimise the nuisance of state, they hide it away. " -- [www.cs.otago.ac.nz/staffpriv/ok/Joe-Hates-OO.htm]

---

..."I wrote a an article, a blog thing, years ago - Why object oriented programming is silly. I mainly wanted to provoke people with it. They had a quite interesting response to that and I managed to annoy a lot of people, which was part of the intention actually. I started wondering about what object oriented programming was and I thought Erlang wasn't object oriented, it was a functional programming language.

Then, my thesis supervisor said "But you're wrong, Erlang is extremely object oriented". He said object oriented languages aren't object oriented. I might think, though I'm not quite sure if I believe this or not, but Erlang might be the only object oriented language because the 3 tenets of object oriented programming are that it's based on message passing, that you have isolation between objects and have polymorphism. " -- https://news.ycombinator.com/item?id=19716258

---

tux1968 3 hours ago [-]

Isn't a method call a message, and the return value a message back? Or is it that "true OO" must be asynchronous?

reply

randomdata 28 minutes ago [-]

I think the spirit of OO, an object has agency over how the message is interpreted in order for it to be considered a message. If the caller has already determined for the object that it is going to call a method then the object has lost that agency. In a 'true OO' language an object may choose to invoke a method that corresponds to the details within the message, but that is not for the caller to decide.

Consider the following Ruby code:

    class MyClass
      def foo
        'bar'
      end
    end
    class MyClass
      def method_missing(name, *args, &block)
        if name == :foo
          return 'bar'
        end
        super
      end
    end

---

losteric 7 minutes ago [-]

Is it unreasonable to think of the method as a semantic "port" to which messages (arguments) are passed?

And languages that allow programmers to bypass OO with jmp instructions seem multiparadigm rather than not-OO...

reply

---

What is the difference between Alan Kay's definition of OOP and Carl Hewitt's Actor Model?

Alan Kay Alan Kay, Had something to do with "Object-Oriented Programming" Answered Feb 13, 2018 · Author has 289 answers and 2.3m answer views

Not a lot of difference.... -- https://www.quora.com/What-is-the-difference-between-Alan-Kays-definition-of-OOP-and-Carl-Hewitts-Actor-Model

---

"

> Objection 1. Data structure and functions should not be bound together ... > Objection 2. Everything has to be an object. ...

Also, most OO langs make a big ceremony out of each new type: create the class file, create the test file, blah blah blah. I want types to be cheap so I can make them easily and capture more meaning with less work. "

"

-- https://news.ycombinator.com/item?id=19715778

---

" Why inheritance never made any sense Graham / March 16, 2018 / OOP

There are three different types of inheritance going on.

    Ontological inheritance is about specialisation: this thing is a specific variety of that thing (a football is a sphere and it has this radius)
    Abstract data type inheritance is about substitution: this thing behaves in all the ways that thing does and has this behaviour (this is the Liskov substitution principle)
    Implementation inheritance is about code sharing: this thing takes some of the properties of that thing and overrides or augments them in this way. The inheritance in my post On Inheritance is this type and only this type of inheritance.

These are three different, and frequently irreconcilable, relationships. Requiring any, or even all, of them, presents no difficulty. However, requiring one mechanism support any two or more of them is asking for trouble.

A common counterexample to OO inheritance is the relationship between a square and a rectangle. Geometrically, a square is a specialisation of a rectangle: every square is a rectangle, not every rectangle is a square. For all s in Squares, s is a Rectangle and width of s is equal to height of s. As a type, this relationship is reversed: you can use a rectangle everywhere you can use a square (by having a rectangle with the same width and height), but you cannot use a square everywhere you can use a rectangle (for example, you can’t give it a different width and height).

Notice that this is incompatibility between the inheritance directions of the geometric properties and the abstract data type properties of squares and rectangles; two dimensions which are completely unrelated to each other and indeed to any form of software implementation. We have so far said nothing about implementation inheritance, so haven’t even considered writing software.

Smalltalk and many later languages use single inheritance for implementation inheritance, because multiple inheritance is incompatible with the goal of implementation inheritance due to the diamond problem (traits provide a reliable way for the incompatibility to manifest, and leave resolution as an exercise to the reader). On the other hand, single inheritance is incompatible with ontological inheritance, as a square is both a rectangle and an equilateral polygon. " -- [2]

---

"are es6 classes just syntactic sugar for the prototypal pattern in javascript?

Yes, perhaps, but some of the syntactic sugar has teeth...Less sugary is that class declarations and methods are always executed in strict mode, and a feature that gets little attention: the .prototype property of class constructor functions is read only: you can't set it to some other object you've created for some special purpose...

---

" Dynamic dispatch mechanism of OOP

Existential types in conjunction with type classes can be used to emulate the dynamic dispatch mechanism of object oriented programming languages. To illustrate this concept I show how a classic example from object oriented programming can be encoded in Haskell.

 class Shape_ a where
   perimeter :: a -> Double
   area      :: a -> Double
 
 data Shape = forall a. Shape_ a => Shape a
 
 type Radius = Double
 type Side   = Double
  
 data Circle    = Circle    Radius
 data Rectangle = Rectangle Side Side
 data Square    = Square    Side
 
 
 instance Shape_ Circle where
   perimeter (Circle r) = 2 * pi * r
   area      (Circle r) = pi * r * r
 
 instance Shape_ Rectangle where
   perimeter (Rectangle x y) = 2*(x + y)
   area      (Rectangle x y) = x * y
 
 instance Shape_ Square where
   perimeter (Square s) = 4*s
   area      (Square s) = s*s
 
 instance Shape_ Shape where
   perimeter (Shape shape) = perimeter shape
   area      (Shape shape) = area      shape
 
 
 --
 -- Smart constructor
 --
 
 circle :: Radius -> Shape
 circle r = Shape (Circle r)
 
 rectangle :: Side -> Side -> Shape
 rectangle x y = Shape (Rectangle x y)
 
 square :: Side -> Shape
 square s = Shape (Square s)
 
 shapes :: [Shape]
 shapes = [circle 2.4, rectangle 3.1 4.4, square 2.1]

(You may see other Smart constructors for other purposes). " -- [3]

---

"An object is a variable of an abstract data type consisting of private data (its state) and procedures that operate on this data." -- https://cseweb.ucsd.edu/~wgg/CSE131B/oberon2.htm

---

HN discussion on OOP:

https://news.ycombinator.com/item?id=23192264

i just skimmed the first few comments. The only thing i got out of it so far is Implementation Inheritance Considered Harmful. ( https://news.ycombinator.com/item?id=23197753 , and a lot of +1 replies to that; many dislikes inheritance hierarchies; one or two recommend interface but not implementation inheritence)

some say that information hiding is good. One guy likes polymorphism.

one guy mentions the wording "data-with-associated-functions, encapsulation"

“OOP consisted of three major ideas: classes that defined protocol and implementation, objects as instances of classes, and messages as the means of communication.”

an example of why ppl dislike hierarchies:

" A (non-satirical) illustration is in the infamous “TDD Sudoku” series of blog posts (follow the links from http://ravimohan.blogspot.com/2007/04/learning-from-sudoku-s... or https://news.ycombinator.com/item?id=3033446) where (in contrast to Norvig's program which just solves the problem) the TDD/OOP proponent ends up with a “class Game”, “class Grid”, “class Cell”, “class CellGroup”? (with derived classes “Row”, “Column”, and “Square”), but ends up nowhere. (From Seibel's post: “…got fixated on the problem of how to represent a Sudoku board. […] basically wandered around for the rest of his five blog postings fiddling with the representation, making it more “object oriented” and then fixing up the tests to work with the new representation and so on until eventually, it seems, he just got bored and gave up, having made only one minor stab at the problem”.)

I think we can agree that this sort of object oriented programming can hurt a lot (thinking about objects is not a substitute for solving your problems, though it is tempting), while objects themselves are useful. "

"I think it all comes down to one thing: Shared mutable state"

(in context:

 I don't find this article well thought out. One, the writer presents OOP and FP as opposing paradigma's. While object oriented programming is orthogonal to functional programming. You can have a purely functional programming language with objects.

Two:

    100% pure functional programing doesn’t work. Even 98% 
    pure functional programming doesn’t work

Doesn't work for what?

I think pure functional programming has its place, it is not just for everything you want to do. But object oriented programming is also not meant for every domain. It is like saying scalpels are stupid, because you can't build a house with them.

reply

specialp 1 day ago [–]

I think it all comes down to one thing: Shared mutable state. We can discuss what is functional and what is OOP, but in general functional programming does not mutate data in place. OOP does in general with encapsulated instantiated data in classes being operated on in that class.

However sometimes it is hard or impossible to avoid shared state even in functional approaches. So I suppose that is the 98% part. Things like databases, connection pools, files.

)

"In this screencast we look at one method for crossing this divide. We review a Twitter client whose core is functional: managing tweets, syncing timelines to incoming Twitter API data, remembering cursor positions within the tweet list, and rendering tweets to text for display. This functional core is surrounded by a shell of imperative code: it manipulates stdin, stdout, the database, and the network, all based on values produced by the functional core."

---

pony

Rust constant expressions

---

https://en.cppreference.com/w/cpp/language/pimpl

""Pointer to implementation" or "pImpl" is a C++ programming technique[1] that removes implementation details of a class from its object representation by placing them in a separate class, accessed through an opaque pointer..."

"

Rust also lacks an analog for the pimpl idiom, which means that changing a crate requires recompiling (and not just relinking) all of its reverse dependencies. " [4]

---

[5] makes an impassioned argument for multiple dispatch (as something needed for really good code reuse / as a solution to the expression problem). I took notes on that essay in plChSciLangs, don't reread the essay, just read those notes if you want more details. The essay claims that Julia has a more efficient implementation of multiple dispatch than others, refers to the paper https://dl.acm.org/doi/10.1145/3276490 for details on that.

I haven't read most of https://dl.acm.org/doi/10.1145/3276490 but here's my notes on the first 2.5 pages:

" Type stability is key to performant Julia code, allowing the compiler to optimize using types. An expression is type stable if, in a given type context, it always returns a value of the same type. " -- [6]

" Julia’s design was carefully tailored so that a very small team of language implementers could create an efficient compiler. The key to this relative ease is to leverage the combination of language features and programming idioms to reduce overhead, but what language properties enable easy compilation to fast code?

Language design: Julia includes a number of features that are common to many productivity languages, namely dynamic types, optional type annotations, reflection, dynamic code loading, and garbage collection. A slightly less common feature is symmetric multiple dispatch [Bobrow et al. 1986]. In Julia a function can have multiple implementations, called methods, distinguished by the type annotations added to parameters of the function. At run-time, a function call is dispatched to the most specific method applicable to the types of the arguments. Type annotations can be attached to datatype declarations as well, in which case they are checked whenever typed fields are assigned to. The language design does impose limits on some of those features, for instance the eval function does not run in local scope, but instead is evaluated at the top-level. Another significant choice for optimizations is the difference between concrete and abstract types: the former can have fields and can be instantiated while the latter can be extended by subtypes. Language implementation: Performance does not arise from great feats of compiler engineering: Julia’s implementation is simpler than that of many dynamic languages. The Julia compiler has three main optimizations that are performed on a high-level intermediate representation; native code generation is then delegated to the LLVM compiler infrastructure. The optimizations performed in Julia are (1) __method inlining__ which devirtualizes multi-dispatched calls and inline the call target; (2) __object unboxing__ to avoid heap allocation; and (3) __method specialization__ where code is special-cased to its actual argument types. The compiler does not support the kind of speculative compilation and deoptimizations common in dynamic language implementations, but supports dynamic code loading from the interpreter and with eval() .

The synergy between language design and implementation is in evidence in the interaction between the three optimizations. Each call to a function that has, as arguments, a combination of concrete types not observed before triggers specialization. A data-flow analysis algorithm uses the type of the arguments (and if these are user-defined types, the declared type of their fields) to approximate the types of all variables in the specialized function. This enables both unboxing and inlining. The specialized method is added to the function’s dispatch table so that future calls with the same combination of argument types can reuse the generated code. " -- [7]

---

describes how objective-c does something like 'everything is a typeclass':

What Language I Use for… Creating Reusable Libraries: Objective-C

" The key to this is ensuring that the library's interface is stable and does not depend on any implementation details that are likely to change over time. C++, for example, is particularly bad at this. Consider the following trivial C++ class interface:

class point { int x, y; virtual int getDistanceFromOrigin(); };

If you add or remove any of the fields, you've changed the class' binary interface so that any subclass or anything that allocates an instance of the class on the stack will need to be recompiled. Worse, if you add any (virtual) methods to the class, you've changed the vtable layout, which is part of the class' binary interface and so will force anything that might contain subclasses of this to be recompiled.

...

this class in Objective-C would have its implementation and interface written like this:

@interface Point : NSObject

@implementation Point { int x; int y; }

If you decide to change the class to use double-precision floating-point polar coordinates internally, the interface remains the same. If you want to add some methods (or even reorder the existing ones to make the header more readable), this has no effect on the binary interface.

Late Binding

A related idea to the separation of interface and implementation is that of late binding. The point example applies even if someone subclasses the Point class. The two instance variables are not part of the interface, so they can't be accessed by subclasses unless they go via the introspection mechanisms. Objective-C objects can't be allocated on the stack, and the offsets of instance variables are defined by the runtime library at load time, so you can make a superclass larger or smaller without breaking any of its subclasses.

In Objective-C, when you call a method, it is looked up based on its name (and in the GNUstep implementation, the types of its arguments, which avoids some stack-corruption issues in Apple's version of the language), independently of the class hierarchy.

"

the article also says that Objective-C has interop bridges to a number of other languages, and it has static compilation.

---

" JavaScript? inherits a bit of brain damage from C++ via Java: Constructor functions are required to return the object that they were passed, which was created implicitly by the new keyword (or, in the case of C++, the new operator). This makes it impossible to transparently implement singletons, for example, or objects allocated from a cache. It also makes class clusters impossible.

In Smalltalk and Objective-C, there is no such thing as a constructor in the language; there is just a convention that the class method called new returns a new instance of the object. In Objective-C, it calls +alloc and then -init, so allocation and initialization are separated. In Smalltalk, the VM does the allocation, so this would not make sense.

In Dart, you can declare a constructor with the factory keyword. It will then be responsible for creating, as well as initializing, the object that it returns. It can, for example, return a different subclass depending on the values of the parameters. "

---

gorgoiler 4 days ago [–]

In general, object orientation is a reasonably elegant way of binding together a compound data type and functions that operate on that data type. Let us accept this at least, and be happy to use it if we want to! It is useful.

What are emphatically not pretty or useful are Python’s leading underscores to loosely enforce encapsulation. Ugh. I’d sooner use camelCase.

Nor do I find charming the belligerent lack of any magical syntactic sugar for `self`. Does Python force you to pass it as an argument to make some kind of clever point? Are there psychotic devs out there who call it something other than `self`? Yuck!

And why are some classes (int, str, float) allowed to be lower case but when I try to join that club I draw the ire from the linters? The arrogance!

...but I still adore Python. People call these things imperfections but it’s just who we are.

PS I liked the Python5 joke a lot.

reply

---

jphoward 4 days ago [–]

I found when I started programming I never used OOP. Then I used it too much. And then recently I use it incredibly sparingly. I think this is most people's experience.

However, there are certain situations where I cannot imagine working without OOP.

For example, GUI development. Surely nobody would want to do without having a Textbox, Button, inherit from a general Widget, and have all the methods like .enable(), .click(), and properties like enabled, events like on_click, etc.?

Similarly, a computer game, having an EnemyDemon? inherit from Enemy, so that it has .kill(), .damage(), and properties for health, speed etc.?

I'd really like to know how the most anti-OOPers think situations like this should be handled? (I'm not arguing, genuinely interested)

reply

setr 4 days ago [–]

At least regarding games, ECS is the popular flavor-of-the-month alternative to OOP as a design strategy. The main problem being that games often have a lot of special cases, which break the inheritance hierarchy quite quickly.

E.g. Defining a weapon > {sword, wand} hierarchy, with respective properties for melee and casting, and then defining a unique weapon spellsword which is capable of both melee and casting. You could inherit from weapon, and copy & paste sword/wand code, or inherit from sword/wand, and copy & paste the other, but the hierarchy is broken.

ECS would rather have you define [melee] and [casting] components, and then define a sword to have [melee], wand to have [casting] and spellsword to have [melee, casting]. So instead of representing the relationships as a tree of inheritance, you represent it as a graph of components (properties). And then you generically process any object with the melee tag, and any object with the casting tag, as needed.

And of course then you could trivially go and reach out across the hierarchies and toss [melee] onto your house object and wield your house like a sword -- I don't know why you'd want to do that, but the architecture is flexible enough to do so (perhaps to your detriment).

Dwarf Fortress probably has the best example of this: https://github.com/BenLubar/raws/blob/archive/objects/creatu...

That's probably more an example of "metadata-driven" but it's ultimately the same thing -- an entity in the game is defined by its components, and the job of the game engine is to simply drive those components through the simulation. That particular example has its metadata (e.g. aesthetics: [CREATURE_TILE:249][COLOR:2:0:0]), its capabilities (e.g. [AMPHIBIOUS][UNDERSWIM]) and its data (e.g. [PETVALUE:10][BODY_SIZE:0:0:200]).

And it even has inheritance :-)

    [CREATURE:TOAD_MAN]
       [COPY_TAGS_FROM:TOAD]
       [APPLY_CREATURE_VARIATION:ANIMAL_PERSON]

reply

anaerobicover 4 days ago [–]

Note for readers who want to search for more: ECS is "Entity Component System"

And Eric Lippert has a fantastic series of blog posts where he also discusses this problem: https://ericlippert.com/2015/04/27/wizards-and-warriors-part...

reply

---

" Martin writes:

    These two examples show the difference between objects and data structures. Objects hide their data behind abstractions and expose functions that operate on that data. Data structure expose their data and have no meaningful functions. Go back and read that again. Notice the complimentary nature of the two definitions. They are virtual opposites. This difference may seem trivial, but it has far-reaching implications.

And... that's it?

Yes, you're understanding this correctly. Martin's definition of "data structure" disagrees with the definition everybody else uses. This is a very strange choice of definition, though Martin does at least define his term clearly. Drawing a clear distinction between objects as dumb data and objects as sophisticated abstractions with methods is legitimate, and useful. But it's quite glaring that there is no content in the book at all about clean coding using what most of us consider to be real data structures: arrays, linked lists, hash maps, binary trees, graphs, stacks, queues and so on. " -- [8]

---

" Organizing complex subsystems with Component

The daemons comprising the distributed system we’re building are comprised of dozens of subsystems that build on top of one another and depend on each other. The subsystems need to be started in a particular order, and in tests they must be torn down in a particular order. Additionally, within tests we need the ability to inject mocks for some subsystems or disable some subsystems altogether.

We use the Component library to organize our subsystems in a way that manages lifecycle and gives us the flexibility to inject alternate dependencies or disable subsystems. Internally, we built a "defrcomponent" helper to unify field and dependency declarations. For example, from our codebase: 1 2 3 4 5 6 7 8 9 10

(defrcomponent AdminUiWebserver? {:init [port] :deps [metastore service-handler cluster-retriever] :generated [^org.eclipse.jetty.server.Server jetty-instance]}

  component/Lifecycle
  ...
  )

This automatically retrieves fields "metastore", "service-handler", and "cluster-retriever" from the system map it’s started in and makes them available in the closure of the component’s implementation. It expects one field "port" in the constructor of the component, and it generates another field "jetty-instance" on startup into its internal closure.

We also extended the component lifecycle paradigm with "start-async" and "stop-async" protocol methods. Some components do part of their initialization/teardown on other threads, and it was important for the rest of our system (especially deterministic simulation, described below) for those to be doable in a non-blocking way.

Our test infrastructure builds upon Component for doing dependency injection. For instance, from our test code: 1 2 3 4 5 6 7 8

(sc/with-simulated-cluster [{:ticker (rcomponent/noop-component)} {:keys [cluster-manager executor-service-factory metastore] :as full-system}] ... )

That first map is a dependency injection map, and this code disables the “ticker” component. The “ticker” causes simulation tests to advance time occasionally, and since this test wants to control time explicitly it disables it. That dependency injection map can be used to override or disable any component in the system, providing the flexibility necessary for writing tests. " [9]

---

~ djsumdog 29 hours ago

link flag

One thing I’ve noticed in most non-OOP frameworks is that you have a lot of functions that just take in a data structure as their first argument. GTK does this and so does Elixir (and has a

> operator for calling multiple functions on some data). You essentially have a similar data mechanic, but the state is clearly separate. You can then create more functions that do not have side-effects.

The big advantage of this is unit testing. There’s less to mock. You don’t have to get your object in the right state during a setup() call before running your tests. You can pass one state and check the return state. You can also greatly reduce the number of mocks you need.

I still do a lot of OOP and non-OOP and I can see clear advantages and disadvantages to both. It’s all about tradeoffs. Testing is one thing non-OOP seems have some advantages in though.

    ~
    singpolyma 28 hours ago | link | flag | 

GTK is definitely built on an OOP framework (GObject)

    ~
    peter edited 27 hours ago | link | flag | 

Yeah, GUIs are a slam dunk for OOP. The metaphor works outrageously well, and the data model actually does fit an inheritance structure for once.

I’ll always remember my CS 102 class, where we were taught that Square naturally subclasses Rectangle. It still bothers me that I had to set width and height for Square.

.

    7
    Isaacy edited 27 hours ago | link | flag | 

Are they a slam dunk? React (which is effectively a GUI framework) has basically abandoned oop in favor of functional prop-drilling, and it’s wildly successful.

    6
    peter 27 hours ago | link | flag | 

The existence of another legitimate paradigm doesn’t invalidate the legitimacy of its predecessors.

---

cogent/short critique of (some parts of) OOP and specifies what e does instead https://dpc.pw/the-faster-you-unlearn-oop-the-better-for-you-and-your-software

this comment points out that in some ways the pattern that the prev article criticizes isn't the core of Oop:

https://lobste.rs/s/fuqmad/faster_you_unlearn_oop_better_for_you_your#c_31ajxe

---

kortex 1 day ago [–]

Suppose I wanted to make a program inscrutable, hard to modify, hard to test, heavily coupled, and hard to reason about:

Sounds familiar, maybe like a scathing criticism of OOP?

---

i forgot what the kinds of late binding are. kay talks about 'extreme late binding' ideas:

https://news.ycombinator.com/item?id=19716258

oh mb i'm thinking of this essay, see section 'extreme late binding':

https://ovid.github.io/articles/alan-kay-and-oo-programming.html

" Another key point is his arguing for extreme late-binding. What does that mean? Well, consider this code:

my $order = OrderFactory?->fetch(%order_args); my $invoice = $order->invoice;

If you have multiple "order" classes, you may not know which class you are dealing with, so you can't know, at compile time which invoice method you're calling. OOP languages generally don't select (bind) the method (invoice) for the invocant ($order) until run time. Otherwise, polymorphism can't work.

But what's extreme late binding? Does the invoice method exist? In a language like Java, that code won't even compile if the method doesn't exist. It might even be dead code that is never called, but you can't compile it if that method isn't there. That's because Java at least checks to ensure that the method exists and can be called.

Another key point is his arguing for extreme late-binding. What does that mean? Well, consider this code:

my $order = OrderFactory?->fetch(%order_args); my $invoice = $order->invoice;

If you have multiple "order" classes, you may not know which class you are dealing with, so you can't know, at compile time which invoice method you're calling. OOP languages generally don't select (bind) the method (invoice) for the invocant ($order) until run time. Otherwise, polymorphism can't work.

But what's extreme late binding? Does the invoice method exist? In a language like Java, that code won't even compile if the method doesn't exist. It might even be dead code that is never called, but you can't compile it if that method isn't there. That's because Java at least checks to ensure that the method exists and can be called.

For many dynamic languages, such as Perl (I ♥ Perl), there's no compilation problem at all because we don't bind the method to the invocant until that code is executed, but you might get a panicked 2AM call that your batch process has failed ... because you might have encapsulation, but not isolation. Oops. . This is "extreme" late binding with virtually no checks (other than syntax) performed until runtime. ... Extreme late-binding is important because Kay argues that it permits you to not commit too early to the "one true way" of solving an issue (and thus makes it easier to change those decisions), but can also allow you to build systems that you can change while they are still running! "

this post goes into that too:

https://softwareengineering.stackexchange.com/questions/301919/object-oriented-late-binding

this post goes into that too:

https://softwareengineering.stackexchange.com/questions/301919/object-oriented-late-binding

" “Binding” refers to the act of resolving a method name to a piece of invocable code. Usually, the function call can be resolved at compile time or at link time. An example of a language using static binding is C:

int foo(int x);

int main(int, char) { printf("%d\n", foo(40)); return 0; }

int foo(int x) { return x + 2; }

Here, the call foo(40) can be resolved by the compiler. This early allows certain optimizations such as inlining. The most important advantages are:

    we can do type checking
    we can do optimizations

On the other hand, some languages defer function resolution to the last possible moment. An example is Python, where we can redefine symbols on the fly:

def foo(): """"call the bar() function. We have no idea what bar is.""" return bar()

def bar(): return 42

print(foo()) # bar() is 42, so this prints "42"

  1. use reflection to overwrite the "bar" variable locals()["bar"] = lambda: "Hello World"

print(foo()) # bar() was redefined to "Hello World", so it prints that

bar = 42 print(foo()) # throws TypeError?: 'int' object is not callable

This is an example of late binding. While it makes rigorous type checking unreasonably (type checking can only be done at runtime), it is far more flexible and allows us to express concepts that cannot be expressed within the confines of static typing or early binding. For example, we can add new functions at runtime.

Method dispatch as commonly implemented in “static” OOP languages is somewhere in between these two extremes: A class declares the type of all supported operations up front, so these are statically known and can be typechecked. We can then build a simple lookup table (VTable) that points to the actual implementation. Each object contains a pointer to a vtable. The type system guarantees that any object we get will have a suitable vtable, but we have no idea at compile time what the value of this lookup table is. Therefore, objects can be used to pass functions around as data (half the reason why OOP and function programming are equivalent). Vtables can be easily implemented in any language that supports function pointers, such as C.

...

This kind of method lookup is also known as “dynamic dispatch”, and somewhere in between of early binding and late binding. I consider dynamic method dispatch to be the central defining property of OOP programming, with anything else (e.g. encapsulation, subtyping, …) to be secondary. ... While this is late-ish binding, this is not the “extreme late binding” favoured by Kay. Instead of the conceptual model “method dispatch via function pointers”, he uses “method dispatch via message passing”. This is an important distinction because message passing is far more general. In this model, each object has an inbox where other objects can put messages. The receiving object can then try to interpret that message. The most well-known OOP system is the WWW. Here, messages are HTTP requests, and servers are objects. ... The power of message passing is that it scales very well: no data is shared (only transferred), everything can happen asynchronously, and objects can interpret messages however they like. This makes a message passing OOP system easily extendable. I can send messages that not everyone may understand, and either get back my expected result or an error. The object need not declare up front which messages it will respond to. "

---

https://boxbase.org/entries/2020/aug/3/case-against-oop/ https://news.ycombinator.com/item?id=30293622

--- [10] [11] ---

interestingly, although closures and OOP are similar in that they both have a way to bundle together some state variables with code (as per the previous section), sometimes in VMs closures are implemented via a "scope stack" allowing access to the variables in a similar manner as accessing local variables; i'm not sure if OOP is often implemented that way (adding the receiver's instance variables to a scope stack), although it could well be.

---

singpolyma 26 hours ago

link flag

“sending messages” is late binding, because the object will do whatever it does when I send the message, and so whatever you get passed at runtime determines what you call instead of some compile time or link time th

---

https://www.hillelwayne.com/post/python-abc/

---

https://github.com/zkat/sheeple

---

HN user hayley-patton, who frequently posts insightful comments with links to research papers about advanced garbage collection techniques, in their blog https://applied-langua.ge/posts/zero-feet.html implies that they like the Newspeak https://newspeaklanguage.org/ object model and their object-capability model

---

"

kortex 1 day ago

root parent next [–]

On the contrary, I think that's a self-fulfilling perception. If assets are already organized as objects, then it'll continue to work well with oop. But if you take the approach from the get-go to use data and FP approaches, things can be quite nice. Examples:

So I would counter that the more valuable skill is "how do I solve problems in terms of applying and composing transforms to data"

To clarify, since everyone has their own definition of OOP, and of the four pillars, Abstraction, Polymorphism, aren't at all unique to OOP, and Encapsulation is just Abstraction: the defining features of OOP are inheritance and poking-and-prodding-state into opaque objects. Inheritance is subsumed by interfaces / structural subtyping, and poking at state is contrasted with reactor patterns, event sourcing, persistent data structures, etc.

Oop really shines at the middlin-low level, in languages without a lifetime (state for things like IO resourcese, at the GUI widget level, and the (micro)service level, which is more like the original smalltalk sort of objects, in which case inheritance isn't a think.

reply "

---

~ fouric 23 hours ago (unread)

link flag

What are “COLAs”? Internet searches for various flavors of “lisp cola” and “plt cola” yielded nothing.

    ~
    david_chisnall 15 hours ago (unread) | link | flag | 

Combined Object-Lambda Architectures. They were fashionable for a whole about 20 years ago as VMs built around the equivalence of object models and lambdas as a way of building different object models (class vs prototype, single vs multiple inheritance, and so on) on the same core substrate. Most of what they could do is also expressive in CLOS.

---

might be worth having classes that have their own encapsulated data (so not just traits) but which cannot access data outside of their own encapsulated data, or cause side effects (outside of exceptions like logging). So this allows one to prove that certain invariants will always be maintained in the data, without having to think of the object as a source of side-effects upon other areas of the code.

---