6 ms·
You don’t agree with an idea so it’s silly? Passing around state via variables is no more tedious than the extra syntax for class definitions, constructors, mem
by amw-zero 6y ago
You don’t agree with an idea so it’s silly? Passing around state via variables is no more tedious than the extra syntax for class definitions, constructors, member variable access, extra semantics related to objects, extra keywords related to visibility, etc.
You can encapsulate functionality without objects. There is nothing that you can do with an object that you can’t do with a closure, and the closure version will be 1/10th the size and have 1/10th the semantic programming language elements. That is why you see ‘object-hating’ all over the industry. It hasn’t lived up to its promise.
Objects aren’t simpler, inherently. It’s just what you know, today, and you’re unwilling to acknowledge your biases.
- woah 6y agoSeems like kind of a meaningless gesture to pass in your own “this” just because you don’t like OOP. If you need a class, why not write it the idiomatic way?
- nightski 6y agoI didn't down-vote you, but in my opinion it's not meaningless because it makes it inherently more testable. Try testing an opaque class without any accessibility into its state. It is much more difficult. If it takes in the state, performs an operation, and returns a new version of that state (ideally an immutable copy) then it becomes much easier to test and validate. The reason information hiding is/was advocated was for reduced coupling. But in reality I find that this coupling becomes implicit which is arguably worse. But this is just my opinion.
- sgift 6y agoThe class will have to provide you with an option to get your answer back after a computation, so that's what you test. I don't see a reason to test how a class computes a result as long as the result is correct.
- nightski 6y agoWell either you have internal state or you don't. If you don't have internal state (and it just returns the answer), then there is no difference between it and a function. If it does have internal state, then the answer it returns depends on the value of that internal state which means it's hard to verify that the answer returned is correct since you have to know the internal state of the object.
- simiones 6y agoThe whole idea of internal state is that it is not accessible (so not relevant) to the outside world. You test a class as a user by calling its methods in the desired order (per requirements and documentation) and you check that the results you get are correct. If the results are correct, the class fits your use case. If they are not, then you're either calling it wrong or it has a bug. Either way, no need to know the internal state.
- nightski 6y ago"Calling its methods in the desired order" This is my point. The order of calling the methods matters, because it manipulates opaque internal state. Since this ordering matters, the caller is implicitly dependent on this internal state as it must know the order to call the methods in to get the desired result. So it's very relevant to the outside world in my opinion and nothing is truly hidden. It becomes part of the public API whether you hide it or not.
- simiones 6y agoIt still depends. Think of a List class. Let's say it has methods to add, remove and retrieve elements, and it has a method to retrieve the size. Let's say the internal implementation is an array with a fill pointer. Obviously, before you can usefully call list.get(7), you must have called list.add(T) at least 7 times (assuming no deletes). Do you need to know the the state of the backing array and the value of the fill pointer? Do you need to know whether there even is a fill pointer, or a linked list underneath (obviously, the performance would be different so you would need to know at some point)?
- sixcorners 6y agoIsn't how you pass things into a function separate from whether you are hiding information? If your state parameter was an opaque pointer then you wouldn't necessarily be able to do anything with it. Also if your class internals were public you would be able to change anything you wanted.
- nightski 6y agoExactly! You don't need classes to obtain information hiding. In general treating the state as an opaque pointer is a good thing. But the state is part of the public API whether you want it to be or not. Even if you try to "hide" it in the OOP sense, the result of the methods depends on the internal state so it is leaked through the behavior of those methods. However, when it is hidden in the OOP sense it makes it much harder to reason about the behavior of a function without reading the source code. Because there is this internal "hidden" state that you do not know exists. But a referentially transparent function is much easier to understand. For a given input, you get an exact output.
- kyralis 6y agoYou're talking yourself in circles. Your final sentence contradicts the remainder of your point. Testing is limited when you do not have the ability to fully control state, and understanding can be limited when information is hidden. The impacts of this are determined by the information being hidden, its relationship to the function's behavior, and the documentation of that behavior relative to the calling context. Limitations on the ability to fully control state are orthogonal to the method of information hiding (opaque function parameters, internal object state, etc). This is not an OOP/functional discussion, this is a discussion about the tradeoffs inherent in information hiding. And let's be perfectly clear here: These are tradeoffs, not black and white clear wins in either direction.
- nightski 6y agoI think information hiding is the wrong term. It should be hidden for modification, open for inspection. Functional programming naturally promotes this. OO does not. That's my opinion. It's been a useful discussion though so I appreciate everyone's input.
- cma 6y agoTests.h #define private public #define protected public
- munchbunny 6y agoI see your point, but I 80% disagree with your conclusion. When you test opaque objects, you're generally not trying to test individual state transitions, you're trying to test that the class's interface adheres to the external promises it makes. That said, many OOP languages have solutions specifically for when you actually do need to test those internal state transitions. C# for example has the "internal" keyword and allows you to declare friend assemblies, so you mostly get your cake and eat it too, at the cost of not hiding the code from yourself as the module implementer.
- NoodleIncident 6y agoI interpreted the top-level comment as a list of extra external promises you could add to your class's interface (incremental search, save/restore, etc). If those are easier to test with functions and a state parameter, it might be better to skip the class.
- kortilla 6y agoIf you’re asserting non-public internals of a class, your testing is wrong in the first place. Assert outcomes, not the process.
- nightski 6y agoThat's the whole point. You can't test outcomes if they are dependent on internal hidden state as that affects the outcome. Whether you make it private or not hidden state leaks into the output since the output is dependent on it.
- kortilla 6y agoIt sounds like you’re confusing side effect free code with OOP. You can use OOP while having side effect free stuff that never requires you to assert internals.
- jayd16 6y agoYou do not want to test private implementation. You should be testing the public contract only. Reaching in and making private things public crystallizes the implementation and reduces your ability to refactor.
- rashkov 6y agoMaybe I'm mis-reading it, but it seems that your comment is just a re-statement of what your parent comment is replying to. It’s like, “Why A” “Because B” “But why A?”
- ImprobableTruth 6y agoIn what way do you 'need' a class? It uses none of the features that a class provides over normal structs.
- simiones 6y agoThe idea is, if you need the functionality that classes give you (passing the implicit state between multiple calls), why not use the special syntax for that use case?
- crimsonalucard1 6y agoBecause it's like using a while loop to print out each character in a string when you can just do print to print the entire string. I don't want to worry about external methods modifying state, I don't want to deal with constructors, I don't want to deal with instantiating state or modifying state before I call my function. My function just takes a state and returns a new state. Simple.
- dkersten 6y agoBecause sometimes you don't need a class, or don't need the full-blown machinery that an object system entails.
- optimiz3 6y ago> Passing around state via variables is no more tedious But it is - the programming language is now repeatedly quizzing you on something you've already told it.
- panopticon 6y agoThat depends on the language. Haskell provides some nice syntax here with monads.
- crimsonalucard1 6y agoWell I mean would you rather have this: method(state){ return doSomething(state) } Or this: class A: otherThingThatMutatesState1() otherThingThatMutatesState2() otherThingThatMutatesState3() method(){ this.state += 1 // or some other mutation }
- simiones 6y agoObjects with a single public method are more tedious than equivalent closures. Objects with multiple public methods are much more friendly than closures with multiple public entry points (they can be done, but they look and feel hacky). Also, pieces of data that hide their internal structure and just expose an API to modify that data (Objects) are a very useful construct, and used in all languages with first-class mutation. Some languages have special syntax for this case (e.g. C++, Python, Go, OCaml), some don't (e.g. C). For example, no language that I know of exposes a mutable List type with a public count of elements. Neither does any language I know of expose a mutable List type as a closure.
- epidemian 6y agoThough i agree with with your general statement, i think the 1/10th the size figure is a bit disingenuous. At least the cases where i've gone from the unnecessary OO style to a simpler function/procedure based solution (in JS, which supports both styles just nice), the code size difference hasn't been anything that great. At most it's been half the size. Which is a lot i think! But not an order of magnitude less. I'm noting this because i think that number kinda detracts from your main argument, with which i agree completely. I think even if the code would remain roughly the same size, the removal of OO would still be worth it, since, as you mention, is much less jargon and conceptual baggage. And if i can achieve the same result with a much simpler conceptual model and language, then all the better :)
- amw-zero 6y agoI will definitely admit that those numbers are made up, more of a rhetorical technique. Numbers are important, I do agree.
- fendy3002 6y agoFor JS, it's best to avoid class as possible, due to this and bind / apply shenanigans. Closure and function return function can be used as substitute, and except for inheritance (which should be avoided too), I haven't found any use case for using class class. Example: function MyList { let data = []; let add = (item) => data.push(item); return { add }; }
- nonsense1234 6y ago> Closure and function return function can be used as substitute So for every list you have an additional allocation for the add function. It obviously continuously to get much worse as more "methods" are inevitably added. That's extremely inefficient for something that's used often and it's not something JavaScript engines can optimize away. The benefit is also extremely negligible since the closed over values are still implicit in the call to add.
- munchbunny 6y agothe closure version will be 1/10th the size and have 1/10th the semantic programming language elements This seems pretty central to your point, but a factor of 10 is also a very strong claim. I'd like to see sources. Passing around state via variables is no more tedious than the extra syntax for class definitions, constructors, member variable access, extra semantics related to objects, extra keywords related to visibility, etc. So instead of doing objects, you're doing closures that act like objects?
- skratlo 6y agoYou're saying what? Doing objects that act just like closures? :)
- munchbunny 6y agoI didn't spell it out, but the gist of it is that their behavioral equivalence means that they also share the same problems, and therefore closures aren't an obviously superior choice (regardless of the syntactic sugar aspect of the debate) the way the parent comment portrays it.
- chrisrichard 6y agoOO - data with functions Closures - functions with data
- dkersten 6y ago> So instead of doing objects, you're doing closures that act like objects? No, I'm using using as-plain-as-possible data structures for my state, that I pass to functions that operate on it and return new state. Simple, composable, easy to test, easy to pass the state data elsewhere, serialize it or whatever else I might want to do. I'm not arguing against using classes or objects, but often its unnecessary and hiding mutable state in an implicit "this" variable is, in my opinion, something that often gets in the way of simplicity, as it often tends to imply mutable objects when in my opinion mutable data should be a careful decision rather than the default. Not necessarily of course, but even when you're operating on a constant "this", I feel that hiding the data that its acting on internally is still often not the right choice, especially if the object itself isn't constant/immutable: then its a coeffect instead of an effect, and if you're using truly immutable objects, then its really just a minor syntax difference to write obj.foo(bar) instead of obj(foo, bar). Object systems still carry around a whole bunch of other functionality that you may not want or need, but by using objects, a reader can't know if you are or not without reading the code or documentation. If its data and functions, it isolates the places things can happen somewhat. Classes and objects have their place, but I feel they shouldn't always be the default choice, just because. That's my take at least, and a big reason I switched from Python and Java to Clojure, but to each their own :)
- crimsonalucard1 6y agoLess complexity to deal with. No need to worry about external scope or external methods mutating properties on objects. No need to think about constructors or anything else. No need to worry about inherited properties. No need to instantiate the object before executing the method. All you need to do is define a function and call it. Literally, again I urge you to take a look at the following example, it's waay more simple than OOP: https://news.ycombinator.com/item?id=23338700 https://news.ycombinator.com/item?id=23338700
- dingoegret 6y agoExactly. So you aren't proposing any improvements if by your own admission, it's no more tedious. Except that it's actually more tedious implementing your own bootleg object. Then managing the scope, namespace and pointers. When they could all be contained inside a language construct guaranteed to follow the rules.
- robocat 6y ago> There is nothing that you can do with an object that you can’t do with a closure How do you prevent memory “leaks” due to the banana referencing a forest problem? With JavaScript libraries returning a function pointer, the returned function pointer would often have a closure that contained a lot of irrelevant state. There is no easy way to reason about the leak, there is virtually no way to work out the cause at runtime (debuggers can help, but a problem with increasing memory usage certainly is not easy to diagnose why). Even when really conscientious about the problem, it is seriously hard to avoid the problem or inadvertently create a closure e.g. many programmers are unaware that arguments may not get GCed if you add an event handler within a function (closing over arguments and maybe other variables). Objects have their problems, but the memory leak problem with JavaScript closures is insidious and very very hard to fix.
- TheOtherHobbes 6y agoThere's nothing you can do in Python that you can't also do in assembler. It just takes a bit longer to code, but runs a lot faster. But no one is going to write a big industrial project in assembler today, because it's the wrong abstraction for the job. Likewise with objects. They make it easy to switch conceptual levels in a domain in a way that projects don't. They take a bit longer to code, but with careful coding you can reuse them ad lib. The OP is making the point that OOP is the wrong abstraction if there's no reuse. And that's perfectly true. But there are situations where you want to say "Make a thousand of these items which respond dynamically and somewhat independently to their environment but which still support some kind of top-down management..."[1] You're going to have a bad time trying to do that with exclusively with closures. Of course you can probably make it work - but that doesn't mean you should. [1] E.g. particle and/or crowd simulation in games/CGI.