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
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

How does AI transform chaos engineering from an experiment into a critical capability? Learn how to effectively operationalize the chaos.

Data quality isn't just a technical issue: It impacts an organization's compliance, operational efficiency, and customer satisfaction.

Are you a front-end or full-stack developer frustrated by front-end distractions? Learn to move forward with tooling and clear boundaries.

Developer Experience: Demand to support engineering teams has risen, and there is a shift from traditional DevOps to workflow improvements.

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

  • Beyond Java Streams: Exploring Alternative Functional Programming Approaches in Java
  • Enterprise-Grade Distributed JMeter Load Testing on Kubernetes: A Scalable, CI/CD-Driven DevOps Approach
  • Want to Become a Senior Software Engineer? Do These Things
  • How You Can Use Few-Shot Learning In LLM Prompting To Improve Its Performance
  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.2K 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

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
  • [email protected]

Let's be friends: