Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

> So, I guess the question I ask as someone who doesn't write a ton of python code is: Is that true?

I don't think it is. His objections to the json library API can be addressed without introducing a single class.

He seems to want access to the internal implementation of converting bytes into a single Python object. That is probably just an internal function that can just be exposed. If he wants to override it from the top level, the library could permit that by just adding the function as a default parameter.

He wants it to deal with streaming. Python generators can help with this. The library could be refactored into implementing a json.load_stream function instead, which takes a string generator and returns some kind of object stream generator. The original json.loads function could be replaced with a wrapper around it for backwards compatibility and for API users who don't want the additional complexity.

None of this involves writing classes. A generator could be seen as a type of class instance, and one can implement a generator with classes, but generators have a well known and simple API which eliminates the need to define a new API, as the traditional writing of classes would do.

It seems to me that the writer has little experience of not using classes, so wants to solve every problem he has with a class. The API problems he want to address are genuine, but he's blind to any solution that doesn't use classes.



> It seems to me that the writer has little experience of not using classes, so wants to solve every problem he has with a class.

That's a bold statement.

Where is the trend to forcefully avoid classes here coming from? Classes are a perfectly valid tool in Python to solve problems. Someone sent me a mail this morning proposing a dictionary with callback functions in addition to a function as if that would solve anything.


> Where is the trend to forcefully avoid classes here coming from?

See the "Stop Writing Classes" video.

Classes hold state. More state adds more complexity, and typical OO design add lots of layers of indirection which also add complexity. Most of the time, you don't need the flexibility that the extra indirection gives you. So you get complexity for little benefit.

If you don't need to hold state, then a collection of functions will usually suffice. In Python, you can use modules for this.

By all means use classes if they are the best way to solve a problem. But for those who know nothing else, how do they even know this?

My problem with this article is that it says "I have a problem; I can solve it with classes; therefore everyone should use classes more". It fails to even consider anything else.


> Classes hold state. More state adds more complexity, and typical OO design add lots of layers of indirection which also add complexity. Most of the time, you don't need the flexibility that the extra indirection gives you. So you get complexity for little benefit.

Functions hold state as well, they just encapsulate it. You can still break up your algorithm into a class with multiple template methods and then encapsulate it in a function if you're afraid of leaking state elsewhere.

What I see instead is that people can't get rid of state and put it as global variables into Python modules. Case in point: pickle. It uses sys.modules and pickle.dispatch_table to store some of its state.

> If you don't need to hold state, then a collection of functions will usually suffice. In Python, you can use modules for this.

Classes have another advantage: virtual method calls. If one function in a module calls into another function in the module I can only do two things: a) copy/paste the library and change one of the functions or b) a monkeypatch which modifies a shared resource someone else might want to use with the old semantics.

A class I can subclass and change the behavior for each method separately.


> You can still break up your algorithm into a class with multiple template methods

Template methods are an abomination. They are only widely used because many OO languages don't have first-class functions. A template method means that the method is being parameterized by one or more functions. Parameters should be expressed as parameters! You wouldn't design a Circle class so that you need to subclass it to specify the radius of the circle. The radius is a parameter. Likewise for the callback functions used by a template method. Template methods are intrinsically difficult to understand and spaghetti-like, unless well-documented, because it is not immediately clear what the parameters to it are. And when overriding a callback method, it's not immediately clear what template method the overridden method is paramaterizing. Or that it is even parameterizing anything. (Caveat: Sometimes template methods are warranted, but they are greatly overused.)

> Classes have another advantage: virtual method calls.

You don't need virtual method calls in order to parameterize functionality. See previous paragraph. Furthermore, it is not safe to override a method without knowing if it has been designed for overriding, and what the class expects of the overriding method. See Effective Java, "Item 17: Design and document for inheritance or else prohibit it". Also see "Item 16: Favor composition over inheritance", and a similar discussion in the Gang of Four book.

> If one function in a module calls into another function in the module I can only do two things: a) copy/paste the library and change one of the functions or b) a monkeypatch which modifies a shared resource someone else might want to use with the old semantics.

If a method is not designed to be overridden, you shouldn't override it anyway. (See previous paragraph.) When using a functional approach (in a language with first-class functions), you can usually design for flexibility just as easily or more easily than when using an inheritance-based OO approach for flexibility.


> Furthermore, it is not safe to override a method without knowing if it has been designed for overriding, and what the class expects of the overriding method.

This is true, but likewise, its not safe to parameterize functionality by passing callbacks unless you know what the function you are passing the callback to expects of callback functions (in terms of arguments, return values, and side effects.)

Both of these, really, are the same principle: its not safe to call code without knowing what the code expects of you when calling it.


> This is true, but likewise, its not safe to parameterize functionality by passing callbacks unless you know what the function you are passing the callback to expects of callback functions

Of course; this goes without saying. But when programming in an OO style, programmers often override methods thinking that they are only changing the behavior of that one method, when in fact they could be inadvertently changing the behavior of other methods in the class.

When passing in functions as arguments, on the other hand, this makes it much more explicit what is supported and what isn't.


> Of course; this goes without saying. But when programming in an OO style, programmers often override methods thinking that they are only changing the behavior of that one method

This is not a problem with using classes or overriding methods, its a problem either with either failure to document behavior on the part of the library author or failure to read documentatio on the part of the library consumer.

Neither of those is any less a danger in the case here the design of the library involves parameterizing functionality by passing functions around as parameters.


> This is not a problem with using classes or overriding methods, its a problem either with either failure to document behavior on the part of the library author or failure to read documentation on the part of the library consumer.

Missing or poor documentation is a sad reality of programming in the real world. And is the normal state when working on a active project with other developers. This would be mitigated if programmers were very careful about declaring methods to be final if it's not perfectly safe to override them, but most are not that careful, or even fully aware of the issues. And in many programming languages there is no way to declare a method to be final.

> Neither of those is any less a danger in the case here the design of the library involves parameterizing functionality by passing functions around as parameters.

When programming in a functional style, functionality that is not intended to be modified by the client will not provide a parameter for doing so, so this significant source of confusion is eliminated.


> This would be mitigated if programmers were very careful about declaring methods to be final if it's not perfectly safe to override them

Its always perfectly safe to override a method if you maintain its required features. Its rarely, if ever, perfectly safe to do so if you don't. The issue is correctly documenting the required features.


> The issue is correctly documenting the required features.

I'm not sure where the disconnect here is. First of all, "correct documentation" might exist in an ideal world, but it rarely exists in the real world. Given this imperfect world, it seems prudent to use technologies and idioms that mitigate the consequences of this imperfection to the best that we are able. Of course no solution is going to be able to eliminate such consequences completely, but that's no reason not to use solutions that offer some benefit.

Secondly, the OP claimed that OO design is generally superior to functional design because it's easier to support overriding of behavior and it's easier to override behavior. Neither of these claims is true. In both cases, attention to detail is required.

Furthermore, in the OO case programmers using a class often fall into the attractive nuisance pit of thinking that just because a method is there, they can and should override it, and programmers implementing a class often neglect to even consider what might happen if a subclass overrides some methods. It's not just a matter of documentation; it's a matter of not even considering the consequences of providing this flexibility by default.

Additionally, when you do parameterize behavior using OO idioms such as template methods, the fact that parameterization is occurring has been obscured, while doing so using the typical functional programming idioms represents paramaterization as, well paramaterization. Who could argue that representing something as what it is is not a good thing?


> Given this imperfect world, it seems prudent to use technologies and idioms that mitigate the consequences of this imperfection to the best that we are able.

Assuming that methods which actually mitigate those consequences exist, and all other things being equal, this is true. In the use cases for which class-based object-oriented design is well-suited, all other things are not equal between class-based object-oriented design to support providing base functionality with overriding in subclasses and using functions that take functions as optional parameters to provide base functionality with per-call overrides, even before considering whether, when used for that purpose, the functions-as-parameters approach actually mitigates anything.

Particularly, they are not equal in that the class-based approach avoids repetition and makes it clear the unit the behavior is attached to, while the functional approach does not. The functional approach is obviously cleaner and clearer for per-function-call parameterization, while the OO based approach is cleaner and clearer (unsurprisingly) for per-object or per-class parameterization.

> Secondly, the OP claimed that OO design is generally superior to functional design because it's easier to support overriding of behavior and it's easier to override behavior.

I haven't been defending OPs claim, I've been criticizing your response which argued that functions-as-parameters was not merely as good but actually categorically superior to OO design for this purpose.

> Neither of these claims is true. In both cases, attention to detail is required.

"Attention to detail is required in both cases" does not, if taken as true (which I have no problem with), refute the claim that these things are generally easier to support in OO.

> Additionally, when you do parameterize behavior using OO idioms such as template methods, the fact that parameterization is occurring has been obscured

No, its not. Inheritance is categorical rather than per-call parameterization, and so using it presents what is being done as exactly that. Using functional idioms for categorical parameterization either involves recreating class-oriented structures or conceals (and makes less DRY) the categorical nature of the parameterization behind per-call overrides.


> The functional approach is obviously cleaner and clearer for per-function-call parameterization, while the OO based approach is cleaner and clearer (unsurprisingly) for per-object or per-class parameterization.

Having programmed heavily in both functional and OO styles, I personally find the opposite to be true. I find the functional approach to be cleaner and clearer in general.

I particularly find template methods to be egregious because when you override a callback method, it's not immediately clear that what is being overridden is even a callback. Additionally, in programming languages that don't require an "override" declaration to override a method, it's not immediately clear that a method is being overridden, rather than just a new method being defined. And with multiple inheritance, this is even worse, because you might have to look in a zillion different other places to even determine this.

> Using functional idioms for categorical parameterization either involves recreating class-oriented structures or conceals (and makes less DRY) the categorical nature of the parameterization behind per-call overrides.

Recreating class-oriented structures? There's no work to do this, and there's nothing non-DRY about it. E.g., see the book JavaScript the good parts. It shows you how to define objects using either JavaScript's OO-based mechanism or using a Scheme-like functional approach. The functional approach is elegant and popular.


> What I see instead is that people can't get rid of state and put it as global variables into Python modules.

What I see is people writing classes that hold state in instances for far longer than is needed. Other code written around these classes then need to take this into account. This tends to the same place where global variables are. A tangled mess.

> If one function in a module calls into another function in the module I can only do two things...

3) Adjust the function to include the behaviour that you need. In Python, default parameters often mean that you can do this without breaking backwards compatibility. If a function is deficient in some way, why not fix the function rather than working around it?


I'd be so bold as to make a categorical statement: If your classes are invariably stateful, or if they can reasonably be described with the phrase "lots of layers", then you're doing it wrong. You're not doing object-oriented programming so much as the mongrel "class-oriented programming" style that rose to prominence among C++ programmers in the 1990s.

Good well-modularized object-oriented code should not be an alternative to a collection of functions. It should be a technique for making your collections of functions more effective. It allows you to write a smaller, more concise collection of functions that can operate in a consistent manner on a whole plethora of datatypes. It accomplishes this because you can write your functions against a common set of mixins that your datatypes inherit rather than having to worry about specializing them for every datatype.


The question is, what classes do that module systems don't? The answer is generally inheritance, and through it, class polymorphism.

With first class function, you hardly need class polymorphism. Just pass the function as argument already, don't bother with writing a whole new class just to override one method.

The other use for inheritance is plain code reuse. This is bad most of the time because it promotes thick interfaces, and function calls are great at code reuse anyway.


> With first class function, you hardly need class polymorphism. Just pass the function as argument already, don't bother with writing a whole new class just to override one method.

Here, here! After returning to functional programming after a 30 year hiatus, I am beginning to realize just how detrimental the OO orthodoxy has been, and just how impoverished the world has been without first class functions. If only Scheme had caught on in the mainstream when it was invented, the programming world would be a much happier and safer place.


> Here, here!

Where, where? ITYM "Hear! Hear!"

http://en.wikipedia.org/wiki/Hear,_hear

"Hear, hear is an expression used as a short, repeated form of hear him, hear him. It represents a listener's agreement with the point being made by a speaker."


Yes, I know, but it's not nice to mess with the lysdexics.


:) I would have let it go, but a lot of young people read HN and I didn't want them to think the expression really was "Here! Here!" Once these things get started ...


The question is, what classes do that module systems don't?

Modules (at least in Python) are singletons.


Doesn't count. When you define a single data structure in a module, (along with functions to manipulate it) you can instantiate it just like you would a class.


Ugh. That's like saying that high-level programming languages "don't count" because you can do that stuff in assembler.


I'm not arguing Turing completeness here. I'm arguing trivial one to one mapping. Syntax sugar. Compare:

  struct foo {
    int   bar;
    float baz;
  };
  int   get_bar(foo);
  float get_baz(foo);
Which would emulate this:

  class foo {
  public:
    int   get_bar();
    float get_baz();
  private:
    int   bar;
    float baz;
  };
If you're interested, I wrote more about that here: http://loup-vaillant.fr/articles/classes-as-syntactic-sugar




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: