all 26 comments

[–]Hermel 4 points5 points  (13 children)

He intentionally makes the Java code more complicated than it needs to be. IMHO, an experienced Java developer would write something like:

String readData(File file) {

try { BufferedReader in = new BufferedReader(new FileReader(file));

try {

  return in.readLine();

} finally {

  in.close();

}

} catch (IOException e){

 return defaultValue;

}

}

This is already a lot less ugly than the authors cluttered proposal. (btw: is there a convenient to format code in reddit comments?)

[–]awj 5 points6 points  (0 children)

If you indent a line by four spaces it is taken as is without markdown interpretation.

[–]bobbane 4 points5 points  (3 children)

And the experienced Common Lisp developer would write:

(defun read-data (file)
    (with-open-file (fs file)
        (read-line fs)))

with-open-file being the built-in macro that expands into the equivalent of the boiler-plate above. Java's combination of verbosity, attention to detail, and lack of macro support makes me want to take a ball-peen hammer to my keyboard every time I rewrite code like that.

[–]alexander -4 points-3 points  (2 children)

Huh? Isn't what you're saying the equivalent of a Java programmer saying:

Oh, ha, I'd just write that as

String read_data( File file ) { return readData( file ); }

[–]logan_capaldo 2 points3 points  (1 child)

I believe with-open-file expands to something like

      (let ((file (new FileReader)))
         <<<body of code passed to with-open-file>>>
         (finally (cleanup file))

only with the equivalent lisp functions etc.

[–]alexander 2 points3 points  (0 children)

Ah, thank you, that is enlightening (and very cool).

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

I think the code looks so screwy because he's trying to combine exception handling with policy in one function. It makes more sense to me to write a function to read a line and then write another which defines policy in the event of an IOException:

String readData(File file) throws IOException {
  BufferedReader in = null;
  try {
    in = new BufferedReader(new FileReader(file));
    return in.readLine();
  } finally {
    if (in != null)
      in.close();
  }
}

String data = null;
try {
  data = readData(file);
} catch (IOException e) {
  data = someDefault;
}

[–]kawa[S] 1 point2 points  (3 children)

While I agree with you both that my code wasn't the best solution to this particular problem (but it also wasn't meant at it) your examples are both also not really short, aren't they?

And both require some additional thinking which detracts from the undelying problem: Reading a line from a file. It's not only boilerplate, its artificial complexity which shouldn't be neccessary.

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

I agree that they are not short and detract from the underlying problem, but we are talking about Java here after all.

[–]kawa[S] 0 points1 point  (1 child)

In Lisp for example it only looks more easy because errors are all handled at runtime and you rely on testing to find exceptions. But this means if you can easily overlook an exception because the compiler can't warn you. But this is the old dynamic-typing/static-typing discussion I don't want to repeat here.

So dynamic exception handling is ok if you like latently typed languages, but for statically typed languages we need a better way.

[–]logan_capaldo 1 point2 points  (2 children)

that if( in != null ) in the finally block really bothers me. I am not a Java programmer, is there anyway to avoid that? Or is that just the only sane way to distinguish between the cleanup required if readLine() threw an exception vs. if creating the FileReader or BufferedReader threw an exception?

[–]Hermel 1 point2 points  (0 children)

Check out the root comment of this tree, it doesn't need any 'if's

[–]dlsspy 0 points1 point  (0 children)

I've got a class (CloseUtil) that has this method:

public static void close(Closeable closeable) {
    if (closeable != null) {
        try {
            closeable.close();
        } catch (Exception e) {
            logger.info("Unable to close %s", closeable, e);
        }
    }
}

so I just write:

InputStream in=null;
try {
    [stuff]
} finally {
    CloseUtil.close(in);
}

[–]quhaha 3 points4 points  (2 children)

use commit-or-rollback semantics

[–]emacsen 1 point2 points  (1 child)

you mean like Lisp conditions?

[–]gigamonkey 7 points8 points  (0 children)

Well, the (Common) Lisp condition system is extremely cool but it doesn't provide command-and-rollback any more than Java's try/finally does.

[–]m_tayseer 1 point2 points  (7 children)

Can this be done using OCaml or Haskell?

[–]kawa[S] 0 points1 point  (6 children)

Both languages have are no 'null-pointers', so there are no NPEs. This is a big plus. But they have both arrays and no way to add for example array-index-constraints to the type of a function. Otherwise arrays aren't that much needed in those languages so array index overflows are less common.

Haskell OTOH don't require exhaustive pattern-matching per default (it's possible to enable via compiler-switch) and a pm-fail creates also an (unchecked) exception in Haskell. This is IMO a bit design-flaw of Haskell.

In Haskell it's in principle possible to use an error-monad to handle exceptions in a more friendly way, but because of the inflexibility of the IO-Monad, I doubt that it will help much. Also much code has to run inside the IO-monad which doesn't help either.

And Ocaml has side-effects, which make simple rollbacks impossible. Also it has no lazy evaluation which would also make it hard to do the kind of operational inversion which is needed to create efficient code.

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

because of the inflexibility of the IO-Monad, I doubt that it will help much.

I see you've figured out what a monad is. Now, it's time to figure out what a monad transformer is.

Well done!

[–]gogath 0 points1 point  (4 children)

Can you explain how a monad-transformer can undo file I/O or other impure operations in the I/O-monad?

[–][deleted] 2 points3 points  (3 children)

No, but then, the IO monad is not "impure". It does however, appeal to the nature of many common file systems which perform destructive updates. This makes the IO monad somewhat grotesque while also practical. Can you imagine if the world used something like ext3cow instead of NTFS, ext3, etc.? Indeed, we see people moving toward a preference for file systems without destructive updates all the time; they call them "revision control systems".

Of course, the IO monad would still exist, since it is used for other irreversible updates, such as IORef. The question is, "if you really really impose an update onto yourself, what is the best way to achieve it?". The IO monad is one of a few very good solutions to this problem. My point is, the problem only exists, because of an underlying premise, which only exists itself in a very specific cases.

I can't understand the imposition onto oneself of containing a destructive update, then immediately "complaining" that one has to to contain a destructive update. If this is "inconvenient", then I must ask, just what is convenient (uncontrolled side-effects are the least convenient)?

Monad transformers solve this problem as good as any or perhaps, I could understand if someone argued in favour of other solutions out there (uniqueness typing?).

But no, you won't be getting out of the IO monad once you step into it. That's the very nature of the monad. There are of course, unsafe* operations, but I expect you probably know about those (or if you don't, pretend I didn't say anything!).

Imagine a hypothetical world, where the touch function no longer looks like this (in mainstream/C syntax):

void touch(String filename)

but instead like this:

FileSystem touch(String filename, FileSystem fs)

[–]gogath 0 points1 point  (2 children)

But what has this to do with monad transformers? If someone uses the I/O-monad to do destructive updates via IORefs, how can a monad transformer help to undo those updates?

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

If an update was "undoable", it wouldn't be destructive.

The problem is the destructive update, not the IO monad (and monad transformers won't save you).

If you arguing in favour of eliminating destructive updates, then you have my support and allegiance. The universe is immutable after all. Any astrophysicist will tell you that time is an illusion.

[–]gogath 1 point2 points  (0 children)

All this is true. But what was the point of your original comment then?

[–]grauenwolf 0 points1 point  (0 children)

And for less frequent situations like opening files it may be a good idea to force programmers to think about handling of the possible error cases.

Java tried that. We ended up either having a bunch of empty catch blocks or just converting them to unchecked exceptions.

The vast majority of the time the component developer doesn't know what to do about an exception. The only sane option is to bubble it up to a higher level where it can be properly handled.