Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I express *finally* equivalent for a Scala's Try?

Tags:

scala

How do I translate the following Java code to Scala using the new Try API?

public byte[] deflate(byte[] data) {

    ByteArrayOutputStream outputStream = null;
    GZIPOutputStream gzipOutputStream = null;

    try {
        outputStream = new ByteArrayOutputStream();
        gzipOutputStream = new GZIPOutputStream(outputStream);
        gzipOutputStream.write(data);
        return outputStream.toByteArray();
    catch (Exception e) {
        ...
    } finally {
        if (gzipOutputStream != null) gzipOutputStream.close();
    }
}

The Scala version should be something like this...

def deflate(data Array[Byte]): Try[Array[Byte]] = Try {
  ByteArrayOutputStream outputStream = new ByteArrayOutputStream()
  new GZIPOutputStream(outputStream).write(data)
  outputStream.toByteArray
}

... but how do I implement Java's finally equivalent?

like image 856
j3d Avatar asked Dec 17 '13 10:12

j3d


People also ask

What does try {} do in Scala?

The Try type represents a computation that may either result in an exception, or return a successfully computed value. It's similar to, but semantically different from the scala. util.


3 Answers

Since a Try {} block will never throw an exception, there is no need for a finally statement. Also, for this particular scenario you should probably use scala-arm, like other posters have suggested.

But you can easily add a finally method to a Try that performs a side-effect in case of either success or failure.

Something like this:

implicit class TryHasFinally[T](val value:Try[T]) extends AnyVal { 
  import scala.util.control.NonFatal

  def Finally(action: => Unit) : Try[T] = 
    try { 
      action; 
      value 
    } catch { 
      case NonFatal(cause) => Failure[T](cause)
    } 
}

Note that in the spirit of all methods of Try, this will not throw an exception if your action throws a non-fatal exception, but simply capture it as a Failure.

You would use it like this:

import java.io._
import java.util.zip._

def deflate(data: Array[Byte]): Try[Array[Byte]] = {
  var outputStream : ByteArrayOutputStream = null
  Try {
    outputStream = new ByteArrayOutputStream()
    new GZIPOutputStream(outputStream).write(data)
    outputStream.toByteArray
  } Finally {
    outputStream.close()
  }
}

Note that you don't have to check for null in the Finally, since if for some unfathomable reason the outputStream is null you will just get a Failure(NullPointerException). Also, in the event that close throws an IOException you will just get a Failure(IOException).

like image 162
Rüdiger Klaehn Avatar answered Oct 26 '22 22:10

Rüdiger Klaehn


Scala's Try API is not supposed to be direct equivalent to try-catch-finally construct in Java. Indeed, why it should be? Scala has built-in try-catch-finally construct too. You can use it directly as in Java. Try is needed when you need to combine multiple actions which can fail.

In fact, you raise more complex problem - resource management. You code, in fact, should have looked like this:

try (ByteArrayOutputStream os = new ByteArrayOutputStream(data);
     GZIPOutputStream gzos = new GZIPOutputStream(os)) {
    gzipOutputStream.write(data);
    return outputStream.toByteArray();
} catch (Exception e) {
    ...
}

This "try-with-resources" language feature in Java automatically closes all resources you specify in parentheses after try.

Unfortunately, Scala does not have its direct equivalent in the library or in the language yet. But because Scala is much more expressive, you can write this construct manually or use third-party libraries, among which I recommend scala-arm. See these links for more information:

  • Scala finally block closing/flushing resource
  • http://illegalexception.schlichtherle.de/2012/07/19/try-with-resources-for-scala/
  • functional try & catch w/ Scala
like image 39
Vladimir Matveev Avatar answered Oct 26 '22 23:10

Vladimir Matveev


Choppy's Lazy TryClose monad is made for this kind of scenario where you want try-with-resources. Plus, it's lazy so you can compose stuff.

Here is an example of how you would use it:

val output = for {
  outputStream      <- TryClose(new ByteArrayOutputStream())
  gzipOutputStream  <- TryClose(new GZIPOutputStream(outputStream))
  _                 <- TryClose.wrap(gzipOutputStream.write(data))
} yield wrap(outputStream.toByteArray())

// Does not actually run anything until you do this:
output.resolve.unwrap match {
    case Success(bytes) => // do something with bytes
    case Failure(e) => // handle exception
}

More info here: https://github.com/choppythelumberjack/tryclose

(just be sure to import tryclose._ and tryclose.JavaImplicits._)

like image 30
Choppy The Lumberjack Avatar answered Oct 26 '22 23:10

Choppy The Lumberjack