Are objects Evil now?


(Andy Wootton) #1

@halfer said:

In javaScript, objects are now considered Evil.

As someone who stopped coding before objects took hold and started again after they were discredited, I’m interested :slight_smile:

I’ve worked alongside OO people and it seemed to make sense for simulation of real-world things but I was always uncomfortable when objects were being invented to pin methods or data to.


Hello! Tom here
(Jon) #2

I’m a PHP dev, and in PHP-land we like objects. However, they (or the state they hold) are sometimes regarded as a source of bugs. There’s a famous quote from a critic of OO:

All I wanted was the banana, but I ended up with the gorilla and the whole jungle

I had a try at some Redux in my last role. It uses “reducers”, which are functions that are 100% stateless - they do not store state and you should not carry out unreproducable operations, such as reading a date or generating a random number. This ethic makes them very testable. It also permits “back in time” debugging, so you can reverse the operation of an app to examine (and re-examine) intermediate values in a debugging system.

However - and it is a big however - this restriction gets onerous after a while. The point of objects was that you could operate on a thing without always having to specify it. Saying that OO is harmful results in needing adding another parameter (or several) to all your functions. This works against the Clean Code principle that small numbers of parameters is better, which in turn seemed to be a good idea because having less parameters means there is less to test (especially if you have optional parameters).

I think classes are very testable anyway. You inject mocked values where it needs other objects - and writing these tests early means that classes are kept testable.

As I understand it, JS is now getting first-class class operators, which I guess would upgrade it from being a prototypical inheritance language. It would therefore be odd if the anti-OO shift in the community were to persist in the long term, since the core team seem to be taking it into another direction. That said, my resistance to eschewing object orientation may just be habit. There is no shift happening in the PHP community at all, and with the energy around PHP 7.x and the corporate support from Facebook et al, I’m happy to take that as a recommendation we do not need to change in our own work. However, I’d like to get into JS, and there really is a big paradigm shift going on - I just have not understood it yet.


(Andy Wootton) #3

I was taught to code in the structured programming, data analysis, “‘database’ has no plural” era. I intuitively knew we were being told things that were wrong but I didn’t have the personal confidence to challenge them or know what was ‘right’. We were taught to make a generic abstract model of the data in an organisation (which is impossible) then structure our code to match the data. I think those ideas were captured more formally in Jackson Structured Programming. In my 3rd job, I worked with some Staffs. Uni guys who’d studied JSP. They’d hated it.

OOP became visible as I was moving to systems operations and I went to about 3 lectures before I had a clue what people were talking about. It always seemed to presented as about 6 words: methods, encapsulation, polymorphism etc. but no-one told me ‘why’.

I get now that it was about simulating real-world objects with a bundle representing object state & behaviour and something like message-passing going on in between them but there always seemed to be other stuff that didn’t belong with the objects - system state rather than ‘real-object’ state.

Now I’ve started to learn Clojure, I was a bit shocked at how many new concepts had been added to the Lisp I (hardly) knew. A couple of days ago, I wrote a page on what I’ve learned so far. I knew a bit of self-taught functional but I’d used variables because there was no-one to tell me not to. Now, I see functions as pipelines that values flow between, without side effects, instead of being put in a ‘place’. In a Lisp, code=data and functions are values. In Clojure, data is immutable, so facts are time-ordered values . Evaluation is lazy. It’s all a bit mind-bending.

I haven’t yet seen any reason you couldn’t also duplicate behaviour similar to objects by grouping functions with a data structure, if it was useful to do so, but I haven’t seen anyone do it yet or identified something more idiomatic. That’s why I was interested in why objects are considered Evil (like GOTOs.) I don’t want to learn something that’s passed its sell-by-date.


(Jon) #4

I wouldn’t worry about it in the general case. It certainly has not changed in Java or PHP. I can’t imagine that it has changed in C#, Ruby or Python. I’d be interested of course in replies from folks regarding any language, but the shift is only happening in JS as far as I know. It seems to be affecting the backend too (Node.JS).


(Andy Wootton) #5

I “have no current plans” to learn an OOP language, until I feel more in control of Clojure. Do you think it’s because JavaScript is trying to shape-change into a functional language?

I think it’s always very hard to add simplicity later.


(Michael Brett) #6

I don’t agree with this, objects are a fundamental part of the language that everyone (not everyone everyone, but you know) uses every day. I think that there has traditionally been an uncomfortable fit between OOP and JS, due to the prototypal inheritance nature of the language. The es6 class syntax was a move towards providing some syntactic sugar for this issue, and it seems to have been welcomed (at least in the React world, might still be controversial elsewhere). But as far as objects go, I’m pretty sure people are ok with them.


(Marc Cooper) #7

OOP became visible as I was moving to systems operations and I went to about 3 lectures before I had a clue what people were talking about. It always seemed to presented as about 6 words: methods, encapsulation, polymorphism etc. but no-one told me ‘why’.

Coming from COBOL (and other procedural languages), OO made a lot of sense. Encapsulation, in particular. The idea of having the behaviour of things linked to their state was very attractive.

I get now that it was about simulating real-world objects with a bundle representing object state & behaviour and something like message-passing going on in between them but there always seemed to be other stuff that didn’t belong with the objects - system state rather than ‘real-object’ state.

Objects can be behavioural. They don’t have to have state. Also, see the strategy pattern.

Now I’ve started to learn Clojure, I was a bit shocked at how many new concepts had been added to the Lisp I (hardly) knew. A couple of days ago, I wrote a page on what I’ve learned so far. I knew a bit of self-taught functional but I’d used variables because there was no-one to tell me not to. Now, I see functions as pipelines that values flow between, without side effects, instead of being put in a ‘place’. In a Lisp, code=data and functions are values. In Clojure, data is immutable, so facts are time-ordered values . Evaluation is lazy. It’s all a bit mind-bending.

Chains of functions are pipelines. That’s exactly why it’s part of the elixir syntax.

The fact that data is immutable doesn’t imply that things are time-ordered. It’s important not to forget about concurrency — although many devs don’t come near it within their apps, but only at the boundaries.

Lazy evaluation is one of those things that requires you to think slightly differently. But once the penny drops, it’s straightforward. Clojure isn’t lazy by default (unlike Haskell), so you can retrofit.

I haven’t yet seen any reason you couldn’t also duplicate behaviour similar to objects by grouping functions with a data structure, if it was useful to do so, but I haven’t seen anyone do it yet or identified something more idiomatic.

That’s how schemas (models) in ecto (elixir) are constructed. It’s fairly common for a module to have a struct. There’s no encapsulation, of course — or getters and setters, obviously — but the idea is the same.

That’s why I was interested in why objects are considered Evil (like GOTOs.) I don’t want to learn something that’s passed its sell-by-date.

Objects aren’t evil. In the right hands — and a decent OO language — they are a fabulous tool. I suspect the issue is really about state.


(Andy Wootton) #8

I’m reading David Weinberger’s ‘Everything Is Miscellaneous’. This morning I got to Chapter 4, ‘Lumps and Splits’. It makes the case that tree-structures are a result of atoms only being able to exist in a single place at the same time. If objects represent collections of atoms, are we imposing unnecessary restrictions on a world made of bits instead of atoms? “Should we organize knowledge the way we put away our washing?” I can see myself using a lost sock as a metaphor ‘real soon now’.


(Andy Wootton) #9

To me too. That’s why I went to such effort to try to work out what they were. I was trying to find the solution to problems I didn’t have any more. My intention was always to go back to development; it just took longer than I expected.

I’m more interested in having data structures without behaviour.

Wouldn’t it have to be part of any functional language syntax?

Mutations are always time-ordered, not all ‘things’. Clojure’s immutable data structures have a time dimension.

I think I get laziness though I haven’t proved that yet.

That’s fine. I don’t fully understand what they are :slight_smile:

That’s where my interest comes from. I’m trying to unbundle the pieces that are considered components of OOP and FP, to see what can be removed or mixed and matched, to gain a better understanding of computation. The Lisp I knew had mutable values, so that clearly wasn’t an essential on the journey but I think it is needed for safe concurrency.


(Marc Cooper) #10

I’m more interested in having data structures without behaviour.

Which is what structs gives you.

Steve Klabnik wrote an informative article on ruby’s structs years ago that I still refer to occasionally.

Chains of functions are pipelines. That’s exactly why it’s part of the elixir syntax.
Wouldn’t it have to be part of any functional language syntax?

Nope. Typically you’ll see:

odd? = &(rem(&1, 2) != 0)
Enum.sum(Enum.filter(Enum.map(1..100_000, &(&1 * 3)), odd?))
#7500000000

With elixir’s pipes, that can be rewritten:

1..100_000
|> Enum.map(&(&1 * 3))
|> Enum.filter(odd?)
|> Enum.sum
#7500000000

Mutations are always time-ordered, not all ‘things’.

They’re not. Unless you think sequentially.

I think I get laziness though I haven’t proved that yet.

As simple as (from above):

1..100_000
|> Stream.map(&(&1 * 3))
|> Stream.filter(odd?)
|> Enum.sum

That’s where my interest comes from. I’m trying to unbundle the pieces that are considered components of OOP and FP, to see what can be removed or mixed and matched, to gain a better understanding of computation. The Lisp I knew had mutable values, so that clearly wasn’t an essential on the journey but I think it is needed for safe concurrency.

I think I’ve mentioned this before, but ruby is a lovely language to explore both OO and (elements of) FP. A lot of techniques can be interchanged.

I do think you’re missing an opportunity, though, with you adherence to clojure. José is very much experimenting with elixir in FPland, and given his strong ruby background, bringing across many of OO’s successful elements. If you want to learn, then I can’t think of a better language to explore computation at this time.


(Andy Wootton) #11

I know but I’ve changed language once already. I’m determined to stick with this decision to win the rematch with Lisp AND I’ve just bought a book. I don’t do that very often. I got ‘Living Clojure’ by Carin Meier and I’m very happy with it’s approach, so far. I can’t tell if I’d be enjoying it so much if I hadn’t read other things first but it’s filling in some gaps in understanding I was aware of. It’s only 148 pages then it goes on-line for a hands-on training course. Apparently, I can buy the eBook for a few quid too, if I sign up for O RLY.


(Marc Cooper) #12

It’d be interesting to talk about specifics with code examples. Otherwise, I don’t find the conversation very useful.


(Alex Russell) #13

That code sample alone doesn’t really explain anything. However, from reading a single line of the docs you linked to earlier about pipelines, I think I understand that when you say lazy you mean like generators in other languages (like JS, PHP, pretty much anything with the ‘pausable function’ yield type stuff). Is that right?

So instead of, for example, getting all rows out of a database and then mapping over them before display, you actually get a ‘lazy resultset’ that the map function takes one result at a time from and performs its operation and returns to whatever called it. Basically a ‘stream’ rather than a ‘bulk’ system, right? (But of course, not simply limited to database reads!)

Presumably this means that, as much as possible, all your code has to be lazy-oriented. If at any point you aren’t streaming/pipelining you lose the laziness, because you force it to evaluate the whole list/resultset. Of course this could depend on the language’s own support - in PHP, if you ‘cast’ these results to an array and store in a variable, you have forced evaluation. In Elixir, maybe they still stay as a reference to the stream until you do something that really truly forces evaluation like reading to the end of the stream for output or reducing or something.

What would be interesting to know is whether the actual ‘computation’ is kept from being done until this forced evaluation. For example, if you only ever take this 1..100_000 and map it, does that map ever actually get called unless you start ‘reading’ the stream, e.g. in the filter or sum? (I imagine if you ‘break’ FP and make the map function do stuff like output to the console it could be interesting, if the map is not evaluated as you might expect, but then you are going off-piste anyway so you get what you deserve.)


(Andy Wootton) #14

It’s more than just a pipeline. It’s a pipeline that stretches to infinity. You can write a function that gives you all the integers, lazily, one at a time, when you ask for another one, as a list or a set. It’s all done with magic. I can’t get my head around what an infinite set is. It feels like it shouldn’t have a right-bracket.

p.s. that was a joke. I thought saying “right paren” would made it too obvious but now it looks like I don’t know the difference between a function and the value it returns :smiley:


(Alex Russell) #15

Oh I get the infinite thing (hence mentioning ‘generator-like’, a common example of the utility of these things is fib numbers but of course ‘the set of all integers’ works nicely too). Of course, if you do anything that does need evaluate the whole set (e.g. reduce or forEach), you will find yourself in a fun situation :slight_smile:


(Andy Wootton) #16

I don’t think that can ever happen because you don’t have places (variables) where values can be put temporarily, so any collection is ‘the whole thing’ until you ask for a single element. I guess it’s a bit like a virtual memory address space being mapped on to far less physical memory, but it’s infinity being mapped onto the finite.


(Andy Wootton) #17

@alexrussell I’m not sure why my mind felt the need to keep this idea a secret until I was in the shower this morning but I feel a bit silly not to have seen it last night:

The situation of the infinite sequence of values is computationally equivalent to a simple loop that increments the value of an integer variable, without specifying an effective end condition. In a function to return all the integers, they pop out of the end of the pipe in sequence until one is so big, an internal ‘register’ overflows. In an imperative procedure, exactly the same sequence is generated, but as states (values), held in a ‘place’ that holds the index variable, until that explodes.

A sequence of immutable, read-only values, in a gradually expanding data-structure means that values in Clojure can be stateless and you can go back to an earlier state, effectively time-travelling.


(Alex Russell) #18

Indeed - this is pretty much exactly how you use generators in other languages. E.g. in JavaScript:

function * integers(start = 1, end = Infinity) {
    for (let i = start; i <= end; i++) {
        yield i
    }
}

// calling a generator function actually returns an iterator object on which you call `next` for the lazily-evaluated next result, but let's ignore this weird internal detail
const gen = integers()

// and calling `next` actually returns an object with `value` and `done`, but again, let's ignore
console.log(gen.next().value) // -> 1
console.log(gen.next().value) // -> 2
console.log(gen.next().value) // -> 3

// a better example, where the language automatically transmutes this weird internal detail to a normal 'enumerable'
for (const int of integers(5, 10)) {
    console.log(int)
}
// -> 5, 6, 7, 8, 9, 10 (on separate lines, not actually comma separated)

But my point is that if the ‘condition’ has no end, you end up in a infinite loop*. Which is pretty obvious. Just pointing it out really.


* Or, as you say, some kind of eventual overflow, depending on your state variable.


(Andy Wootton) #19

I sometimes write things just to check if I really understand. This was a useful explicit example of a mechanism I’d been thinking about in the abstract. I’m working towards describing what computation is without relying on a single paradigm. Everything I’ve found so far also loops.


(Andy Wootton) #20

This explains the ‘what’, if not the ‘how’, “Most of the sequence library functions are lazy, i.e. functions that return seqs do so incrementally, as they are consumed, and thus consume any seq arguments incrementally as well.”

In ‘Lean’ terms, it seems to be ‘a pull system’, driven by the consumption of values, like a pop-up stack or a gerbil’s water bottle. With hind-sight, I could probably have opted for whisky there.

There’s some insight into Rich Hickey’s thinking when he increased laziness here:
https://clojure.org/reference/lazy
I understood that ‘give me the next in the sequence’ (seq) was better than ‘give me the rest of the sequence’.
I enjoyed that he rejected streams in the pipeline.