DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Because the DevOps movement has redefined engineering responsibilities, SREs now have to become stewards of observability strategy.

Apache Cassandra combines the benefits of major NoSQL databases to support data management needs not covered by traditional RDBMS vendors.

The software you build is only as secure as the code that powers it. Learn how malicious code creeps into your software supply chain.

Generative AI has transformed nearly every industry. How can you leverage GenAI to improve your productivity and efficiency?

Related

  • Mastering Advanced Aggregations in Spark SQL
  • Thermometer Continuation in Scala
  • Deploying a Scala Play Application to Heroku: A Step-by-Step Guide
  • Upgrading Spark Pipelines Code: A Comprehensive Guide

Trending

  • Navigating and Modernizing Legacy Codebases: A Developer's Guide to AI-Assisted Code Understanding
  • Advancing Robot Vision and Control
  • The Perfection Trap: Rethinking Parkinson's Law for Modern Engineering Teams
  • The Role of AI in Identity and Access Management for Organizations
  1. DZone
  2. Coding
  3. Languages
  4. A Simple Try-With-Resources Construct in Scala

A Simple Try-With-Resources Construct in Scala

Check out how to implement try-with-resources in Scala, then take a lesson in generic programming using the Shapeless library.

By 
Stanislav Chetvertkov user avatar
Stanislav Chetvertkov
·
Oct. 02, 17 · Tutorial
Likes (5)
Comment
Save
Tweet
Share
21.1K Views

Join the DZone community and get the full member experience.

Join For Free

You may have noticed that Scala is lacking the try-with-resources construct that Java has. This construct allows us to automatically invoke the close method of an AutoCloseable resource at the end of a code block that uses it. The interface is very broadly used in lots of classes of the standard Java library and many third-party libs as well.

This structure is really handy because it allows us to avoid human errors caused by inattentiveness. Calling the close method after the resource has been used can be easily missed or forgotten if done manually every time, but the consequences of missing it can sometimes be severe.

Luckily, it is easy to implement an analogous — and even more powerful — construct in Scala.

Here is how it looks:

def loan[A <: AutoCloseable, B](resource: A)(block: A => B): B = {
    Try(block(resource)) match {
        case Success(result) =>
        resource.close()
        result
        case Failure(e) =>
        resource.close()
        throw e
    }
}


This is a simple curried function with two arguments. The first argument is the resource of type A that has to implement Autocloseable (A <: AutoCloseable). The second argument, a function from A to B, is the actual code that we want to execute that uses the resource.

Here is how the usage looks:

// Lets define our on AutoCloseable implementation for testing
case class ResourceA(it: String) extends AutoCloseable {
    override def close(): Unit = println("closing a")
}

val result:
    String = loan(ResourceA("123")) {
        resource =>
            print(s "${resource.it}")
        resource.it
    }

    result // "123"


When being run, it outputs 123 and then closing a, affirming that it has closed the resource as we expected.

Notably, loan is also an expression that means we can assign a value it returns to a variable.

If we want to get hold of several resources, we can nest such calls in the following fashion:

// another AutoCloseable implementation for testing
case class ResourceA(it: String) extends AutoCloseable {
    override def close(): Unit = println("closing a")
}

val result = loan(ResourceA("123")) {
    r1 =>
        loan(ResourceB("456")) {
            r2 =>
                // both ResourceA and ResourceB in this scope  
                r1.it + r2.it // returns "123456"
        }
}


It looks fine, though nesting three or more such resources can be tedious and won’t look nice (although admittedly, the possibility of requiring it is relatively rare).

So here comes the next logical step…

Arbitrary Number of Arguments Using Shapeless

Let's go one step further. Suppose you want to avoid nesting and be able to call it this way:

val r1 = ResourceA("123")
val r2 = ResourceB("456")
val r3 = ResourceC("456")

loan(r1, r2, r3) { case (a, b, c) =>
    // piece of code that uses all 3 arguments
}


We can, of course, overload our function to accept two arguments, and another one for three and so on, but there is no fun in that!

Let’s have an exercise in generic programming with Shapeless instead (but don’t confuse it with Java generics).

Shapeless is a popular library for generic programming that used in many Scala libraries. Sometimes, it can almost do miracles from a library user’s perspective.

A very common pattern it uses is to state the dependencies we need and dependencies between them via implicit arguments. When compiling, the compiler searches for a suitable combination of these parameters, or infers them. It compiles fine when the search is successful and fails otherwise.

We need to add Shapeless to your build.sbt file (assuming that we use SBT as a build tool).

resolvers++ = Seq(
    Resolver.sonatypeRepo("releases"),
    Resolver.sonatypeRepo("snapshots")
)

libraryDependencies += "com.chuusai" % "shapeless" % "2.3.2"


And here is how the code looks:

import shapeless._, ops.hlist._, ops.nat._
import scala.util. {
    Failure,
    Success,
    Try
}

object Loan {
    def apply[A <: AutoCloseable, B](resource: A)(block: A => B): B = {
        Try(block(resource)) match {
            case Success(result) =>
            resource.close()
            result
            case Failure(e) =>
            resource.close()
            throw e
        }
    }

    def apply[Tup, L <: HList, Len <: Nat, B](resources: Tup)(block: Tup => B)(
        implicit gen: Generic.Aux[Tup, L],
        con: ToList[L, AutoCloseable],
        length: Length.Aux[L, Len],
        gt: GT[Len, nat._1]
    ): B = {
        Try(block(resources)) match {
            case Success(result) =>
            gen.to(resources).toList.foreach {
                _.close()
            }
            result
            case Failure(e) =>
            gen.to(resources).toList.foreach {
                _.close()
            }
            throw e
        }
    }
}


For this case, with only one element, the code remains the same. So, let's skip to the interesting part.

For first implicit argument, gen: Generic.Aux[Tup, L], we enforce a requirement for it to have an HList (a heterogeneous list that stores the types of elements it has in compile time) representation to be available somewhere in the scope. Think of L as the output type of the HList for such conditions.

But let's elaborate on this a bit more. Aux is a well-established pattern in Shapeless. For Generic, the definition looks like this: type Aux[T, Repr0] = Generic[T] { type Repr = Repr0 }. It is designed to avoid the following problem. To express a dependency between implicit arguments, we may want to write something like this:

implicit gen: Generic[Tup], con: ToList[gen.Repr, AutoCloseable] ...


And use the output type of the generic with a gen.Repr reference. But unfortunately, it won’t compile because we can’t refer to a type member of one parameter from another parameter in the same block.

The next three conditions we enforce on the HList L are that all its elements should be implementing AutoCloseable , and it should contain more than 1 element (because the one element method def apply<a href="resource: A">A <: AutoCloseable, B</a>(block: A => B) handles a single element case):

con: ToList[L, AutoCloseable], 
length: Length.Aux[L, Len],
gt: GT[Len, nat._1]
object Example extends App {

    case class ResourceA(it: String) extends AutoCloseable {
        override def close(): Unit = println("closing a")
    }

    case class ResourceB(it: String) extends AutoCloseable {
        override def close(): Unit = println("closing b")
    }

    Loan(ResourceA("123"), ResourceB("456")) {
        case (a, b) =>
        println(a.it)
        println(b.it)
    }


Loan(ResourceA("123"), ResourceB("456")) looks like a regular function call with two arguments, but we are actually passing a tuple here — basically a syntactic sugar for Loan((ResourceA("123"), ResourceB("456"))). Another convenient thing about tuples in Scala is that they are case classes and can be easily represented as Shapeless HLists via the Generic to method without much effort.

Recap

So we’ve shown how easy it is to implement try-with-resources in Scala — and an even more powerful solution with some Shapeless magic.

I also suggest you read this great guide about Shapeless and generic programming.

Scala (programming language) Construct (game engine)

Opinions expressed by DZone contributors are their own.

Related

  • Mastering Advanced Aggregations in Spark SQL
  • Thermometer Continuation in Scala
  • Deploying a Scala Play Application to Heroku: A Step-by-Step Guide
  • Upgrading Spark Pipelines Code: A Comprehensive Guide

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!