Vavr: Try with resource cleanup

Created on 8 Apr 2016  路  12Comments  路  Source: vavr-io/vavr

Hi,

I'm looking for something similar to this:

Closeable closeQuietly = ...;

Try.run(() -> read(reader)).always(closeQuietly);

Basically, need something that will be always executed regardless of the output (Success, Failure).

feature revertecloseduplicate 芦vavr-core禄

Most helpful comment

I hadn't the time yet to modify the generator but I'm on it.

In the meanwhile one thing came to my mind:

Instead of

// Solution 1
Try.with(this::getSession, session -> ...)

we could provide a _curried_ form:

// Solution 2
Try.with(this::getSession).of(session -> ...)

// or name it 'apply'
Try.with(this::getSession).apply(session -> ...)

An implementation could look like this:

interface Try<T> {

    // Solution 1

    static <R, A1 extends AutoCloseable> Try<R> with(CheckedSupplier<A1> supplier1, CheckedFunction1<A1, R> f) {
        try (A1 autoCloseable1 = supplier1.get()) {
            return Try.success(f.apply(autoCloseable1));
        } catch (Throwable x) {
            return Try.failure(x);
        }
    }

    // Solution 2

    static <A1 extends AutoCloseable> TryWith1<A1> with(CheckedSupplier<A1> supplier1) {
        // DEV-NOTE: We need an anonymous class instead of a lambda because 'the target method is generic'.
        return new TryWith1<A1>() {
            @Override
            public <R> Try<R> of(CheckedFunction1<A1, R> f) {
                try (A1 autoCloseable1 = supplier1.get()) {
                    return Try.success(f.apply(autoCloseable1));
                } catch (Throwable x) {
                    return Try.failure(x);
                }
            }
        };
    }

    @FunctionalInterface
    interface TryWith1<A1 extends AutoCloseable> {
        <R> Try<R> of(CheckedFunction1<A1, R> f);
    }

}

The pro of solution 2 is that a TryWith is reusable, i.e. given a set of resource suppliers we could perform different computations:

TryWith1<Session> tryWith = Try.with(this::getSession);
Try<Boolean> result1 = tryWith.of(session -> true);
Try<String> result2 = tryWith.of(session -> "ok");

Another idea that comes to my mind while writing these lines is to provide notation where provides resources may depend on each other - similar to a for comprehension:

Try<R> result =
    Try.with(this::getConnection, connection ->
        Try.with(() -> connection.prepareStatement(...)).of(preparedStatement ->
            ...
        )
    );

This notation is similar to For-comprehensions (in Javaslang):

Iterator<String> iterator =
    For(persons.filter(Person::hasAddress), p ->
        For(p.addresses).yield(a ->
             p.name + "," + a.street
        )
    );

All 12 comments

I have this also in mind. We need to abstract over it. Basically it is IO related - with resource cleanup. Java has AutoClosable and try with resources. The main pain point is maintaining composability. E.g. assuming we have monads that capture some state depending on a resource, compining those requires to ensure cleaning up all resources under all circumstances (failure, exhaustive read, ...)

I don't think we should couple this directly with Try. It would fit also great to the Stream collection.

Need time to think about it...

Hi,
Is there a plan to support something similar, but more like a plain try/finally block?

e.g. instead of using:

Session session = getSession();
Try.run(() -> runSomething())
                .onFailure(t -> {
                    session.close();
                })
                .andThen(() -> {
                    session.close();
                });

we would use

Try.run(() -> runSomething())
    .finally(() -> session.close());

Hi @mladenbolic,

nice idea, we could add _finally(r) as shortcut for onFailure(t -> r.run()).andThen(r) where r is a CheckedRunnable.

finally is a keyword, so we need an alternative. E.g. _finally or Finally (both ugly) or finallyRun.

Don't you like always?

Try.run(() -> runSomething())
    .always(() -> session.close());

or after, done or finallyRun

Yes it's a keyword, just posted it as an example that it does not need to implement AutoClosable.

As for the cool name: finallyRun, thenFinally, always.

@jknack Oh sorry, I've overseen on my small phone display that you suggested always - I forgot because it has been 12 days ago.

@jknack, @mladenbolic: Our current approach is not complete. We need to capture the resources in a 'safe' context when creating them. We can do better by abstracting over it:

// AutoCloseable resources are supplied and automatically closed on error or when Try was executed
WithResources(...).Try(...); // again upper-cases for methods, like in Match and For (...) :-/

An example:

Try<String> result = WithResources(this::getSession).Try(session -> "ok");

because the result is a Try, we also could implement always or finallyRun or Finally:

Try<String> result = WithResources(this::getSession).Try(session -> "ok").Finally(() -> { ... });

Please note that we do not need Finally to close the resources - that is done automatically. However, of cause we could close resources in Finally if we do not use a WithResourcescontext:

Try<String> result = Try.of(() -> "ok").Finally(() -> { /* close resources */ });

That is a functional try-with-resources!


We are currently able to support up to 8 resources because we have Function1, ..., Function8.

Here is an example for just one resource:

// the API interface exists. Usage: import javaslang.API.*;
interface API {

    static <A1 extends AutoCloseable> WithResources1<A1> WithResources(Try.CheckedSupplier<A1> supplier1) {
        // DEV-NOTE: We need an anonymous class instead of a lambda because 'the target method is generic'.
        return new WithResources1<A1>() {
            @Override
            public <R> Try<R> Try(CheckedFunction1<A1, R> f) {
                try(A1 autoCloseable1 = supplier1.get()) {
                    return Try.success(f.apply(autoCloseable1));
                } catch (Throwable x) {
                    return Try.failure(x);
                }
            }
        };
    }

    @FunctionalInterface
    interface WithResources1<A1 extends AutoCloseable> {
        <R> Try<R> Try(CheckedFunction1<A1, R> f);
    }
}

I think we additionally need to provide side-effecting variants WithResources(...).run(CheckedConsumer1..8).The functional interfaces Consumer1..8 and CheckedConsumer1..8 currently do not exist.

Like the idea! but don't love the WithResources(...)... what if we do the same but using something like this:

Try.with(this::getSession, session -> {
  // work with session and return something
  return ...;
})

It works as your example... but feel we type less and we don't create or have to remember a new construction: WithResources

Also, it probably read better by moving the closeable supplier at the end:

Try.with(session -> {
  // work with session and return something
  return ...;
}, this::getSession)

@danieldietrich Great idea, but I agree with @jknack here, WithResources(...) seams a bit awkward.

As for the suggested implementations, I prefer the first one suggested by @jknack. It's more Java-like and it would be natural for the user to first specify the AutoClosable supplier and then to do something with it inside the checked function.

For a with(...) that accepts one supplier we would have something similar already proposed for WithResources(...):

static <T, A1 extends AutoCloseable> Try<T> with(CheckedSupplier<A1> supplier1, CheckedFunction1<A1, T> function1){
        try(A1 autoCloseable1 = supplier1.get()) {
            return Try.success(function1.apply(autoCloseable1));
        } catch (Throwable x) {
            return Try.failure(x);
        }
    }

@jknack, @mladenbolic that makes pretty much sense. I like your idea to keep things in one place. I also like the first proposal of @jknack.

Try has currently 950 lines of code. I need to move the class completely into Generator.scala to generate the with methods (because we do not have protected regions). That should be no problem. I will provide a version tomorrow...

I hadn't the time yet to modify the generator but I'm on it.

In the meanwhile one thing came to my mind:

Instead of

// Solution 1
Try.with(this::getSession, session -> ...)

we could provide a _curried_ form:

// Solution 2
Try.with(this::getSession).of(session -> ...)

// or name it 'apply'
Try.with(this::getSession).apply(session -> ...)

An implementation could look like this:

interface Try<T> {

    // Solution 1

    static <R, A1 extends AutoCloseable> Try<R> with(CheckedSupplier<A1> supplier1, CheckedFunction1<A1, R> f) {
        try (A1 autoCloseable1 = supplier1.get()) {
            return Try.success(f.apply(autoCloseable1));
        } catch (Throwable x) {
            return Try.failure(x);
        }
    }

    // Solution 2

    static <A1 extends AutoCloseable> TryWith1<A1> with(CheckedSupplier<A1> supplier1) {
        // DEV-NOTE: We need an anonymous class instead of a lambda because 'the target method is generic'.
        return new TryWith1<A1>() {
            @Override
            public <R> Try<R> of(CheckedFunction1<A1, R> f) {
                try (A1 autoCloseable1 = supplier1.get()) {
                    return Try.success(f.apply(autoCloseable1));
                } catch (Throwable x) {
                    return Try.failure(x);
                }
            }
        };
    }

    @FunctionalInterface
    interface TryWith1<A1 extends AutoCloseable> {
        <R> Try<R> of(CheckedFunction1<A1, R> f);
    }

}

The pro of solution 2 is that a TryWith is reusable, i.e. given a set of resource suppliers we could perform different computations:

TryWith1<Session> tryWith = Try.with(this::getSession);
Try<Boolean> result1 = tryWith.of(session -> true);
Try<String> result2 = tryWith.of(session -> "ok");

Another idea that comes to my mind while writing these lines is to provide notation where provides resources may depend on each other - similar to a for comprehension:

Try<R> result =
    Try.with(this::getConnection, connection ->
        Try.with(() -> connection.prepareStatement(...)).of(preparedStatement ->
            ...
        )
    );

This notation is similar to For-comprehensions (in Javaslang):

Iterator<String> iterator =
    For(persons.filter(Person::hasAddress), p ->
        For(p.addresses).yield(a ->
             p.name + "," + a.street
        )
    );

Status update:

We are now able to act on AutoCloseables and finally run code:

Try.withResources(/*resource-suppliers*/)
   .of(/*resource-processor*/)
   .andFinally(/*unit-of-code*/);

(see vavr-0.9.0: Try)

Next we will add the side-effecting run API by introducing CheckedConsumer1..8:

Try.withResources(/*resource-suppliers*/)
   .run(/*resource-consumer*/)
   .andFinally(/*unit-of-code*/);

Vavr 1.0 reduces the API surface area. Instead of adding an internal DSL to mimic the existing try/catch/finally, we use it directly (within a Try.of or Try.run)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

santiagopoli picture santiagopoli  路  6Comments

ashrwin picture ashrwin  路  6Comments

maystrovyy picture maystrovyy  路  3Comments

paplorinc picture paplorinc  路  6Comments

nfekete picture nfekete  路  5Comments