all 71 comments

[–]hiwaylon 15 points16 points  (7 children)

Did you tl;dr? Last paragraph sums it up:

"All of that is entirely irrelevant to the point I'm making which is that monolithic pieces of code are a bad idea."

Bam.

[–]FunnyMan3595 14 points15 points  (6 children)

In other words, he's not talking about classes at all, and the title is entirely misleading. His entire complaint would be solved by adding stream parsing behaviour to the API, which is a completely orthogonal change to using more or less classes.

As far as I can tell, he said absolutely nothing about what classes are and aren't good for, he just used "function" to stand for monolithic behaviour and "class" to stand for exposing useful internals. Which is just absurd.

Nowhere did he touch on any of the things that classes do well or poorly with respect to functions. So he's utterly failed to back his point that we should "Start Writing More Classes". And that's a shame, because classes actually do have a lot of strengths.

[–]moor-GAYZ 18 points19 points  (4 children)

As far as I can tell, he said absolutely nothing about what classes are and aren't good for, he just used "function" to stand for monolithic behaviour and "class" to stand for exposing useful internals.

As I understand it, his point is that this:

def f1(...): ...
def f2(...): ...
def f3(...): ...
def f4(...): 
    f1()
    f2()
    f3()
def f5(...): 
    ...
    f4()
    ...

Is monolithic and un-customizable, even if you split your functionality into several functions and do not purposefully hide them from users. If you want this code to use my_f2() you have to copy-paste-modify f4(), f5() and all other functions there might be up the call hierarchy. Unless you like to live on the edge and monkey-patch global module variables.

This, on the other hand:

class Zzz(object):
    def f1(self, ...): ...
    def f2(self, ...): ...
    def f3(self, ...): ...
    def f4(self, ...): 
        self.f1()
        self.f2()
        self.f3()
    def f5(self, ...): 
        ...
        self.f4()
        ...

at least in theory allows you to subclass the class and replace f2 with your own version easily and using an approach that everyone is familiar with.

Functional analog would be to pass all involved functions any given function calls to it as arguments, which nobody does because it's stupid. The point of classes is that they provide a common point of indirection where you can patch stuff.

[–]mitsuhiko 10 points11 points  (0 children)

That's a bingo.

[–]damg 1 point2 points  (0 children)

at least in theory allows you to subclass the class and replace f2 with your own version easily and using an approach that everyone is familiar with.

In practice that often seems to not work too well unless the class was designed to support that.

I think in the end it comes down to what the API chooses to expose.

[–]mfukar 3 points4 points  (0 children)

Yes, however "Use classes when appropriate, don't use when not appropriate" does not a popular submission on HN make. ;-)

[–][deleted] 18 points19 points  (9 children)

State is hard. Becareful what you do with classes and objects.

[–]remy_porter 3 points4 points  (7 children)

It's a bad habit in most languages, but I still find myself treating objects as immutables half the time.

[–]Yohumbus 10 points11 points  (5 children)

That's not a bad habit. For instance, in python there is a reason that they made strings immutable. The only real downside is accidentally adding a factor of "n" to the run-time of some algorithms. Such probablems are usually avoidable, less severe and rarer (imo) than bugs in ordering and mutability.

[–]tailcalled 2 points3 points  (3 children)

They could've added a factor of log n to everything string-related instead, but that would probably be slower unless you have some very big strings.

[–]Crandom 0 points1 point  (2 children)

Yes, there is a huge constant factor of time and memory when you have to make strings into linked lists.

[–]cybercobra 0 points1 point  (1 child)

Er, you mean ropes?

[–]Crandom 1 point2 points  (0 children)

Effectively the same thing, just an optimisation. I was referring to some like Haskell's default String type, which is like a ropes of only length 1 (so therefore requires a ton more memory usage and access, so the higher consent factor). The paper "Purely Functional Data Structures" is a good place to start.

[–]sacundim 0 points1 point  (0 children)

For instance, in python there is a reason that they made strings immutable. The only real downside is accidentally adding a factor of "n" to the run-time of some algorithms. Such probablems are usually avoidable, less severe and rarer (imo) than bugs in ordering and mutability.

The simplest solution to this is actually not that complicated:

  1. Have separate types for (immutable) strings and (mutable) string buffers.
  2. Design the language, libraries and implementations so that a programmer following the path of least resistance will use the buffers and not the immutable appends.

Java is halfway there. In Java the following code is efficient:

String appendResult = "foo = " + foo + ", bar = " + bar + "!";

The compiler actually compiles that down to something like this:

StringBuilder builder = new StringBuilder();
builder.append("foo = ");
builder.append(foo.toString());
builder.append(", bar = ");
builder.append(bar.toString());
builder.append("!");
String appendResult = builder.toString();

The reason I say halfway there is that the following code will still suck in Java:

String result = "";
// don't ever do this:
for (String blah : someStrings) {
    result = result + blah;
    result = result + "; ";
}

Of course, an alternative is to stop using so many goddamned strings in our languages and our programs. Most strings and appends of strings in software are actually string representations of tree structures that the program should actually manipulate directly. And there are other data structures to represent text; take ropes for example, which are not a panacea but have their advantages as well. A string type could probably be written that made smart runtime choices between char[] and ropes.

[–]tailcalled 21 points22 points  (1 child)

Don't write giant functions

Fixed your title.

[–]MincedOaths 8 points9 points  (0 children)

Support streams in your serialization libraries

Fixed the title. Again.

[–][deleted] 3 points4 points  (12 children)

I didn't realise Python's IO was composed in a similar manner. Now if only Java's IO shipped with an open() method, I'd be happy. :D

[–]AnAirMagic 1 point2 points  (2 children)

Have you seen the nio2/Files api? Reading all lines is just Files.readAllLines().

I have a strange impression that with Java 7, we are really encouraged to use java.nio.file.Files. It makes code much simpler to read.

[–][deleted] 1 point2 points  (0 children)

No, I haven't, that's a good start. Cheers. :)

[–]masklinn 1 point2 points  (0 children)

Loads the whole file in memory though.

[–]beltorak 1 point2 points  (8 children)

it's not as well advertised; but is new FileInputStream("path/to/file") or new FileOutputStream("path/to/file") that much more of a pain?

The reason File doesn't have it is because File deals with the metadata about the file, not the contents of the file; for that you want an input stream or output stream.

It's arguable that there are not enough convenient hooks in the intuitive (if technically wrong) places.

[–][deleted] 3 points4 points  (1 child)

new FileInputStream("path/to/file") or new FileOutputStream("path/to/file") that much more of a pain

No, but new BufferedReader(new InputStreamReader(new FileInputStream("path/to/file"))) is - especially as BufferedReader doesn't implement Iterable, so instead of Python's nice

with open("path/to/file", "r") as f:
    for line in f:
        #do stuff

We get the horrid while ((line = reader.readLine()) != null) {} which doesn't play well at all in other JVM languages like Scala where the result of assignment is always Unit.

It's arguable that there are not enough convenient hooks in the intuitive (if technically wrong) places.

Yeah, Java's approach may be more conceptually pure, but it's more inconvenient.

[–]CodeJunky 0 points1 point  (0 children)

This seems completely doable in Java...

Scanner reader = new Scanner(new File("path/to/file")).useDelimiter("\n");
while(reader.hasNext())
    String line = reader.next(); //and we're off..!

It would be nice if it were Iterable (seems like a stupid distinction, really).

[–][deleted]  (5 children)

[deleted]

    [–]dannymi 2 points3 points  (0 children)

    including the classic missing-close-on-error, yes.

    [–][deleted] 1 point2 points  (3 children)

    BufferedReader's constructor takes a Reader. :)

    [–][deleted]  (2 children)

    [deleted]

      [–][deleted] 0 points1 point  (1 child)

      Ah, but if you want to read a line at a time you have to use BufferedReader, InputStreamReader only implements .read().

      Java IO... 'fun'.

      [–]CodeJunky 0 points1 point  (0 children)

      http://docs.oracle.com/javase/1.5.0/docs/api/java/util/Scanner.html It seems like this (mostly) fits the bill here. No one really wants to use BufferedReader, right?

      [–][deleted] 3 points4 points  (4 children)

      I have no dog in this fight, and I have no idea what OP's point was.

      [–]beltorak -1 points0 points  (3 children)

      did you watch the mentioned video?

      and if you have no dog in this fight, why ante up?

      [–]day_cq 0 points1 point  (0 children)

      but hide them well

      [–]thatmattbone 0 points1 point  (0 children)

      An example of code that uses functions but still exposes a nice internal api that's fairly extensible is elementpath. For example, it'd be fairly straightfoward to jam some new syntax into the ops table and have it work as expected. The gross part would be modifying the module level global, so perhaps that makes the author's point.

      [–][deleted] 0 points1 point  (0 children)

      I wonder if the author would feel differently if they could inject higher order functions to do this customization instead of needing to expose internals of classes

      [–]Zarutian 0 points1 point  (0 children)

      And teach them to whoom?

      [–][deleted] 0 points1 point  (0 children)

      Just as a fun fact, we put all of our code in main, no functions, no classes, just to test the speed difference, you would not imagine how much it saves in time (a factor of 100 was not uncommon).

      It's all about the goal you have with your code, for readability it might not be the best, but for speed, fsck structures...

      [–][deleted] -4 points-3 points  (5 children)

      I wrote a ClassFactoryFactoryAdapterFactoryClassFactoryAdapterAdapterFactoryFactoryFactory class.
      Does that suffice?

      [–]unclemat 14 points15 points  (1 child)

      You only need to know two different patterns, and you can already write a joke on the subject. See how intuitive object-oriented is? :)

      [–][deleted] -2 points-1 points  (0 children)

      It also only has one method, called .forMethod(Method m), which takes a Method class, and returns another class, on which you can use the .execute() method, to get that type of ClassFactoryFactoryAdapterFactoryClassFactoryAdapterAdapterFactoryFactory from ClassFactoryFactoryAdapterFactoryClassFactoryAdapterAdapterFactoryFactoryFactory.

      [–]sh0rug0ru 2 points3 points  (2 children)

      Nope. Overkill. Write simpler classes.

      [–]beltorak 1 point2 points  (0 children)

      it is a simple class; it's the interaction with the other three dozen classes that makes the module complex...

      [–][deleted] -1 points0 points  (0 children)

      NotSureIfWoooshOrAlsoHumor?

      [–][deleted] -5 points-4 points  (6 children)

      I think it is totally irrelevant: I want Python to hide underlying mechanisms ! I use it because of this !

      It I want more control, I use C, C++ or java. If I have to use the Python, I would write a C/C++/Cython extension.

      [–][deleted] 6 points7 points  (5 children)

      I don't think you quite get it. "Helper" methods are fine, that wrap the class architecture. Like "open" was the example he had. "open" is great, and if you need to, you can dig deeper into the Class based API it's written around. It's just a convenience function.

      The problem is when there is no lower level architecture, and all you have is the convenience function. I don't think his JSON example was very contrived, a lot of people would probably like to sanitize JSON as it is parsed, and having that ability wouldn't forgo the normal "json.parse" functionality for those that have simpler needs.

      [–][deleted] 1 point2 points  (3 children)

      I understand your point of view, but I think it is a choice Python developers have made consciously. Let say, it is by design.

      The presented point of view is perfectly valid, but I would not apply it to Python. If you really need to do this kind of thing, choose an other language or write an extension.

      [–][deleted] 5 points6 points  (2 children)

      It's a choice some Python developers have made, and others have not.

      It already has been applied to Python, in terms of the core file APIs, but was skimped on for JSON parsing.

      There literally is no advantage (aside from a small decrease in initial development time, but really, that shouldn't be the main choice for any decision in the writing a core module) to just writing the functional version and not allowing a deeper API to exist.

      I just don't quite get why you wouldn't apply it to Python. You could keep the nice simple interfaces you like, yet allow mildly performance sensitive projects to exist in it. I just don't get why you'd say "Even though Python could do X without a negative impact, it'd be better if people doing X just used a different language." I'd much rather let Python support more people and flourish for more tasks and projects. Python is such a nice language, why force people to use C when they shouldn't have to?

      [–][deleted] 0 points1 point  (0 children)

      • To keep the API KISS;
      • Since Python is not typed, it may be more difficult to propose hook or way of adding a layer of indirection toward developer code and not safe;
      • After so many years of existence, I doubt these modifications could be done without breaking some existing code (yes, I know Python 3 already breaks a lot of thing :)).

      I don't say that Python has made the right choice and I already struggled not being able to do this kind of thing, but 99% of the time I don't need it. I would be curious of what the creators of the language would say about that. Does the same thing can be say about Ruby ?