Idea[Simplified try-with-resources statement]

tzengshinfu tzengshinfu at gmail.com
Mon Oct 2 07:46:15 UTC 2023


Hi, Holo,

Concurrency, as you mentioned, is indeed a challenging topic.
However, personally, I find new scoped/manual Buffer() much cuter
compared to indentation.
Unfortunately, it didn't exist in the Java world from the beginning.

Sometimes, I use IIFE (Immediately Invoked Function Expressions) to "hide" TWR,
but it's somewhat like not tidying up a room and just closing the door...

```java
public class example1 {
    public static void main(String... args) throws
FileNotFoundException, IOException {
        String studentName = ((Function<String, String>) path -> {
            String result = "not found";

            try (BufferedReader reader = new BufferedReader(new
FileReader(path))) {
                while ((result = reader.readLine()) != null) {
                    break;
                }
            } catch (final FileNotFoundException ex) {
            } catch (final IOException ex) {
            }

            return result;
        }).apply("studentName.txt");

        System.out.println(studentName);
    }
}
```
By the way, Swift's IIFE is quite elegant.
How can an IIFE in a strongly typed language look so good?

/* GET BETTER EVERY DAY */



Holo The Sage Wolf <holo3146 at gmail.com> 於 2023年9月28日 週四 上午5:01寫道:
>
> Hello Tzengshinfu,
> Unfortunately completely automatic cleanup is not a solved problem, so "as shown bellow" can't work (the 2 main problems are fields and concurrency).
>
> The best we can hope for is to have a set of modifiers to `new` and let the compiler throw a compile time exception if we don't specify the modifier (e.g. `new scoped Buffer()`, `new manual Buffer()`, ...), but this also has problems (it is annoying).
>
> Also the problem if "fluent cleanup" is not the only problem we have with the current TwR.
>
> Because of the complexity of the subject I don't think this discussion will go anywhere in here.
> I believe that a separate discussion should be opened in amber-spec-experts, I also don't know the current "free hands" to work on anything in this subject, but this is a different question.
>
>
> On Wed, Sep 27, 2023, 11:24 tzengshinfu <tzengshinfu at gmail.com> wrote:
>>
>> Hi, Folks:
>>
>> Since I previously used C# (to the extent that I consumed syntactic
>> sugar until I almost got "syntax diabetes"), I unintentionally brought
>> my habits into the Java world. However, I now recognize the
>> differences in their design philosophies.
>>
>> In fact, I agree with Tagir's opinion. TWR (Try-With-Resources) is
>> indeed a more explicit and reliable way of automatic resource cleanup.
>> However, when I saw JEP 445: Unnamed Classes and Instance Main
>> Methods, which bears similarities to C# Top-level statements, and
>> noticed that Java chose Virtual Threads instead of async/await, I
>> realized that "less is more, simplification is the future direction;
>> we should make the compiler do more, and we should focus on business
>> logic. So, wouldn't it be better if we didn't have to manage the
>> timing of resource release?" That's the basis for my suggestion.
>>
>> Holo's opinion is excellent. The awkward thing is that C# chose to
>> turn the using statement into a using declaration version, while Java
>> chose to add resource management functionality to try-catch-finally.
>> So, any modification would break the semantics of try, but I'm also
>> concerned that since try-with-resources already exists, there may be
>> little room for further changes in the automatic cleanup mechanism.
>>
>> I also agree with Brian's opinion. `defer` might behave unexpectedly
>> due to different scope levels, and developers might lump logic outside
>> of resource cleanup into it, leading to dreadful results. Instead of
>> desiring the delayed execution effect of `defer`, I think we want "a
>> more automated automatic cleanup mechanism" as shown below:
>>
>> ```java
>> public static void main(String... args) throws FileNotFoundException,
>> IOException {
>>     BufferedReader reader = new BufferedReader(new
>> FileReader("studentName.txt"));
>>     String studentName = "not found";
>>
>>     while ((studentName = reader.readLine()) != null) {
>>         break;
>>     }
>>
>>     System.out.println(studentName);
>>     /* The BufferedReader 'reader' will be automatically cleaned up
>> before the method exits, even without explicit TWR indication. */
>> }
>> ```
>>
>> Thank you for taking the time to provide thoughtful responses from
>> various perspectives.
>>
>>
>> /* GET BETTER EVERY DAY */
>>
>>
>>
>> tzengshinfu <tzengshinfu at gmail.com> 於 2023年9月25日 週一 下午2:10寫道:
>> >
>> > Hi, Folks,
>> >
>> > Since my last suggestion,
>> > I've conducted some research and stumbled upon the
>> > `@Cleanup`[https://projectlombok.org/features/Cleanup] feature in the
>> > popular library `Lombok`.
>> >
>> > Personally, I believe that the existence of this feature indicates a
>> > certain demand, but it's unfortunate that it requires importing the
>> > library, which adds to the maintenance complexity.
>> > I'd like to hear your thoughts on this situation. Is importing Lombok
>> > the best approach?
>> >
>> > P.S. This is my second email to the mail list. If there's anything I
>> > may have overlooked or done incorrectly, please feel free to let me
>> > know. Thank you.
>> >
>> > Thanks for your interest and support,
>> > Hsinfu Tseng
>> >
>> > /* GET BETTER EVERY DAY */
>> >
>> >
>> > tzengshinfu <tzengshinfu at gmail.com> 於 2023年9月4日 週一 上午11:14寫道:
>> > >
>> > > Hi, Folks:
>> > >
>> > > Recently, I've been using `JBang` to write some script tools, and I
>> > > feel that Java has transformed with modern syntax features like
>> > > `Records`, `Switch Expressions`, and `Pattern Matching`. These newly
>> > > added language features provide developers with more options in
>> > > specific scenarios, almost like giving Java a set of wings.
>> > >
>> > > When using the `try-with-resources statement`, I thought it might be
>> > > possible to introduce an additional simplified syntax, which I believe
>> > > would be useful when developing simple, small-scale programs.
>> > > Meanwhile, scenarios requiring resource closure timing specification
>> > > or exception handling can still make use of the original syntax.
>> > >
>> > > At first glance, this simplified syntax may seem a bit unusual, but
>> > > considering that `switch` itself encompasses both statements and
>> > > expressions, and that `switch
>> > > expression`[https://openjdk.org/jeps/361] also adds a semicolon at the
>> > > end of `}`, perhaps it won't be as difficult to accept.
>> > >
>> > > Because it reduces indentation, making the logic clearer and more
>> > > visible, users don't need to consider resource closure timing; they
>> > > can focus solely on business logic. It's possible that in the future,
>> > > the likelihood of using this simplified syntax will increase.
>> > >
>> > > In my personal opinion, the original `try-with-resources statement`
>> > > feels like driving a manual transmission car, where I have full
>> > > control over everything. However, if we can let the runtime manage it
>> > > autonomously, it's like driving an automatic car on the long journey
>> > > of coding, and even more effortless.
>> > >
>> > > If there could be an alternative approach that is both hassle-free and
>> > > doesn't require altering Java specifications, I hope that experienced
>> > > individuals can share their insights.
>> > >
>> > >
>> > > Thanks for your interest and support,
>> > > Hsinfu Tseng
>> > >
>> > > (Using the JEP template because I've found it helps clarify my thoughts.)
>> > >
>> > >
>> > > ## Summary
>> > >
>> > > Allow omitting the definition of scope when declaring
>> > > `try-with-resources statement`.
>> > >
>> > >
>> > > ## Goals
>> > >
>> > > * Not introducing new scopes to reduce the cognitive burden on
>> > > developers, especially beginners.
>> > > * Reducing the interference of hierarchical indentation to enhance
>> > > code readability.
>> > > * The declaration position of variables is not restricted by
>> > > `try-with-resources statement`.
>> > > * Ensure that resources are released before the current scope ends.
>> > >
>> > >
>> > > ## Motivation
>> > >
>> > > The `try-with-resources statement` simplified resource management in
>> > > Java after version 7. However, its side effect is the necessity to
>> > > declare a scope and, at the same time, it restricts lifetime of the
>> > > variable declared within the scope:
>> > >
>> > > ```java
>> > > public class example1 {
>> > >     public static void main(String... args) throws
>> > > FileNotFoundException, IOException {
>> > >         try (BufferedReader reader = new BufferedReader(new
>> > > FileReader("studentName.txt"))) {
>> > >             String studentName = "not found";
>> > >             /* Variable 'studentName' is only alive in the scope of
>> > > try-with-resources statement. */
>> > >
>> > >             while ((studentName = reader.readLine()) != null) {
>> > >                 break;
>> > >             }
>> > >         }
>> > >
>> > >         System.out.println(studentName);
>> > >         /* Variable 'studentName' cannot be resolved here. */
>> > >     }
>> > > }
>> > >
>> > > ```
>> > >
>> > > While it's possible to move variable declaration before
>> > > `try-with-resources statement` and modify variable content within the
>> > > scope of `try-with-resources statement`, the actions of declaration,
>> > > assignment, and usage of variables are segmented by the scope of
>> > > `try-with-resources statement`. In practice, we would prefer variable
>> > > declarations to be as close as possible to their usage locations and
>> > > avoid unnecessary
>> > > separation[https://rules.sonarsource.com/java/RSPEC-1941/]:
>> > >
>> > > ```java
>> > > public class example2 {
>> > >     public static void main(String... args) throws
>> > > FileNotFoundException, IOException {
>> > >         String studentName = "not found";
>> > >
>> > >         // #region
>> > >         // The scope of try-with-resources statement separates
>> > >         // the declaration, assignment, and usage of variable 'studentName'.
>> > >         try (BufferedReader reader = new BufferedReader(new
>> > > FileReader("studentName.txt"))) {
>> > >             while ((studentName = reader.readLine()) != null) {
>> > >                 break;
>> > >             }
>> > >         }
>> > >         // #endregion
>> > >
>> > >         System.out.println(studentName);
>> > >     }
>> > > }
>> > >
>> > > ```
>> > >
>> > > Furthermore, methods that involve multiple resources requiring
>> > > management and dependencies might become cumbersome in terms of syntax
>> > > due to `nested try-with-resources statements`:
>> > >
>> > > ```java
>> > > public class example3 {
>> > >     public static void main(String... args) throws SQLException {
>> > >         String jdbcUrl = "jdbcUrl";
>> > >         String username = "username";
>> > >         String password = "password";
>> > >
>> > >         try (Connection conn = DriverManager.getConnection(jdbcUrl,
>> > > username, password)) {
>> > >             String selectSQL = "selectSQL";
>> > >             String studentID = "studentID";
>> > >
>> > >             try (PreparedStatement statement =
>> > > conn.prepareStatement(selectSQL)) {
>> > >                 statement.setString(1, studentID);
>> > >
>> > >                 try (ResultSet result = statement.executeQuery()) {
>> > >                     String studentName = "not found";
>> > >
>> > >                     while (result.next()) {
>> > >                         studentName = result.getString(1);
>> > >                     }
>> > >
>> > >                     System.out.println(studentName);
>> > >                 }
>> > >             }
>> > >         }
>> > >     }
>> > > }
>> > >
>> > > ```
>> > >
>> > > This proposal introduces a shorthand declaration that eliminates the
>> > > scope limitations of `try-with-resources statement`. It enables
>> > > variable declaration, assignment, and usage within the same scope,
>> > > reduces indentation levels for improved code readability (particularly
>> > > in `nested try-with-resources statements`), and defers the timing of
>> > > resource release to the system, ensuring resource disposal before the
>> > > scope in `try-with-resources statement` is declared ends. (Similar to
>> > > Golang's `Defer statement` + resource
>> > > close[https://go.dev/tour/flowcontrol/12], C#'s `using
>> > > declarations`[https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/proposals/csharp-8.0/using],
>> > > and Swift's `Defer statement` + resource
>> > > close[https://docs.swift.org/swift-book/documentation/the-swift-programming-language/statements/#Defer-Statement])
>> > >
>> > >
>> > > ## Description
>> > >
>> > > The format of the `simplified try-with-resources statement` is as follows:
>> > >
>> > > ```java
>> > > try (resources);/* Change '{}' to ';'. */
>> > >
>> > > ```
>> > >
>> > > It can be observed that omitting `{}` reduces the indentation, thus
>> > > enhancing code readability by not introducing new scopes. Moreover,
>> > > variable declarations can be positioned either before or after the
>> > > `simplified try-with-resources statement`:
>> > >
>> > > ```java
>> > > public class example1_simplified {
>> > >     public static void main(String... args) throws
>> > > FileNotFoundException, IOException {
>> > >         try (BufferedReader reader = new BufferedReader(new
>> > > FileReader("studentName.txt")));
>> > >         String studentName = "not found";
>> > >
>> > >         while ((studentName = reader.readLine()) != null) {
>> > >             break;
>> > >         }
>> > >
>> > >         System.out.println(studentName);
>> > >     }
>> > > }
>> > >
>> > > ```
>> > >
>> > > Because it automatically converts the `simplified try-with-resources
>> > > statement` to an equivalent `try-with-resources statement` after
>> > > compilation:
>> > >
>> > > ```java
>> > > public class example1_converted {
>> > >     public static void main(String... args) throws
>> > > FileNotFoundException, IOException {
>> > >         try (BufferedReader reader = new BufferedReader(new
>> > > FileReader("studentName.txt"))) {/* Change ';' back to '{'. */
>> > >             String studentName = "not found";
>> > >
>> > >             while ((studentName = reader.readLine()) != null) {
>> > >                 break;
>> > >             }
>> > >
>> > >             System.out.println(studentName);
>> > >         }/* Add '}' before the current scope ends. */
>> > >     }
>> > > }
>> > >
>> > > ```
>> > >
>> > > Because the scope of `simplified try-with-resources statement` extends
>> > > until the current scope is terminated, the variable `studentName`,
>> > > which originally couldn't be resolved in `example1`, can now be
>> > > resolved successfully. This allows variable declaration, assignment,
>> > > and usage within the same scope, ensuring that resource close
>> > > operations occur before the current scope ends.
>> > >
>> > > Invoking multiple dependent `simplified try-with-resources statements`
>> > > is similar to regular variable declarations, thereby avoiding
>> > > excessive nesting.
>> > >
>> > > ```java
>> > > public class example3_simplified {
>> > >     public static void main(String... args) throws SQLException {
>> > >         String jdbcUrl = "jdbcUrl";
>> > >         String username = "username";
>> > >         String password = "password";
>> > >
>> > >         try (Connection conn = DriverManager.getConnection(jdbcUrl,
>> > > username, password));
>> > >         String selectSQL = "selectSQL";
>> > >         String studentID = "studentID";
>> > >
>> > >         try (PreparedStatement statement = conn.prepareStatement(selectSQL));
>> > >         statement.setString(1, studentID);
>> > >
>> > >         try (ResultSet result = statement.executeQuery());
>> > >         String studentName = "not found";
>> > >
>> > >         while (result.next()) {
>> > >             studentName = result.getString(1);
>> > >         }
>> > >
>> > >         System.out.println(studentName);
>> > >     }
>> > > }
>> > >
>> > > ```
>> > >
>> > > It will be converted to equivalent `nested try-with-resources
>> > > statements` after compilation:
>> > >
>> > > ```java
>> > > public class example3_converted {
>> > >     public static void main(String... args) throws SQLException {
>> > >         String jdbcUrl = "jdbcUrl";
>> > >         String username = "username";
>> > >         String password = "password";
>> > >
>> > >         try (Connection conn = DriverManager.getConnection(jdbcUrl,
>> > > username, password)) {/* Change ';' back to '{'. */
>> > >         String selectSQL = "selectSQL";
>> > >         String studentID = "studentID";
>> > >
>> > >         try (PreparedStatement statement =
>> > > conn.prepareStatement(selectSQL)) {/* Change ';' back to '{'. */
>> > >         statement.setString(1, studentID);
>> > >
>> > >         try (ResultSet result = statement.executeQuery()) {/* Change
>> > > ';' back to '{'. */
>> > >         String studentName = "not found";
>> > >
>> > >         while (result.next()) {
>> > >             studentName = result.getString(1);
>> > >         }
>> > >
>> > >         System.out.println(studentName);
>> > >     }}}}/* Add the corresponding number (in this case, 3) of '}'
>> > > before the current scope ends. */
>> > > }
>> > >
>> > > ```
>> > >
>> > > After code formatting:
>> > >
>> > > ```java
>> > > public class example3_formatted {
>> > >     public static void main(String... args) throws SQLException {
>> > >         String jdbcUrl = "jdbcUrl";
>> > >         String username = "username";
>> > >         String password = "password";
>> > >
>> > >         try (Connection conn = DriverManager.getConnection(jdbcUrl,
>> > > username, password)) {
>> > >             String selectSQL = "selectSQL";
>> > >             String studentID = "studentID";
>> > >
>> > >             try (PreparedStatement statement =
>> > > conn.prepareStatement(selectSQL)) {
>> > >                 statement.setString(1, studentID);
>> > >
>> > >                 try (ResultSet result = statement.executeQuery()) {
>> > >                     String studentName = "not found";
>> > >
>> > >                     while (result.next()) {
>> > >                         studentName = result.getString(1);
>> > >                     }
>> > >
>> > >                     System.out.println(studentName);
>> > >                 }
>> > >             }
>> > >         }
>> > >     }
>> > > }
>> > >
>> > > ```
>> > >
>> > > Comparing the code structures of `example3_simplified` and
>> > > `example3_formatted`, it is evident that the usage of `simplified
>> > > try-with-resources statement` enhances code readability by
>> > > significantly reducing nested indentation levels. Additionally, it can
>> > > be observed that in the converted `nested try-with-resources
>> > > statements`, resource close actions will be executed in reverse order
>> > > of declaration, just before the current scope ends.
>> > >
>> > > If coupled with `JEP 445: Unnamed Classes and Instance Main Methods
>> > > (Preview)`[https://openjdk.org/jeps/445], it becomes convenient to
>> > > create a simple, small-scale program:
>> > >
>> > > ```java
>> > > String jdbcUrl = "jdbcUrl";
>> > > String username = "username";
>> > > String password = "password";
>> > > String selectSQL = "selectSQL";
>> > > String studentID = "studentID";
>> > >
>> > > void main() throws SQLException {
>> > >     try (Connection conn = DriverManager.getConnection(jdbcUrl,
>> > > username, password));
>> > >     try (PreparedStatement statement = conn.prepareStatement(selectSQL));
>> > >     statement.setString(1, studentID);
>> > >
>> > >     try (ResultSet result = statement.executeQuery());
>> > >     String studentName = "not found";
>> > >
>> > >     while (result.next()) {
>> > >         studentName = result.getString(1);
>> > >     }
>> > >
>> > >     System.out.println(studentName);
>> > > }
>> > >
>> > > ```
>> > >
>> > >
>> > > ## Alternatives
>> > >
>> > > * One approach to mitigate the impact on readability caused by deeply
>> > > `nested try-with-resources statements` is to consolidate multiple
>> > > resources within the same `try-with-resources statement` as much as
>> > > possible. However, formatting multiple resources with line breaks can
>> > > also disrupt visual clarity, and furthermore, it's not always feasible
>> > > to close many resources simultaneously:
>> > >
>> > > ```java
>> > > public class alternative1 {
>> > >     public static void main(String... args) throws SQLException {
>> > >         String jdbcUrl = "jdbcUrl";
>> > >         String username = "username";
>> > >         String password = "password";
>> > >         String selectSQL = "selectSQL";
>> > >         String studentID = "studentID";
>> > >
>> > >         try (Connection conn = DriverManager.getConnection(jdbcUrl,
>> > > username, password);
>> > >                 PreparedStatement statement =
>> > > conn.prepareStatement(selectSQL)) {
>> > >         /* In this case, there are only 2 resources, but if there are 3 or more,
>> > >            it will become even less aesthetically pleasing. */
>> > >
>> > >             statement.setString(1, studentID);
>> > >
>> > >             try (ResultSet result = statement.executeQuery()) {
>> > >             /* ResultSet 'result' cannot be closed simultaneously with
>> > > PreparedStatement 'statement'
>> > >                because PreparedStatement 'statement' needs parameter
>> > > configuration first. */
>> > >                 String studentName = "not found";
>> > >
>> > >                 while (result.next()) {
>> > >                     studentName = result.getString(1);
>> > >                 }
>> > >
>> > >                 return studentName;
>> > >             }
>> > >         }
>> > >     }
>> > > }
>> > >
>> > > ```
>> > >
>> > > * Alternatively, creating a separate method to confine the scope of
>> > > managed resources within the method can be done, but it necessitates
>> > > 'naming' the new
>> > > method[https://www.karlton.org/2017/12/naming-things-hard/]. This
>> > > approach simply shifts complexity to the interior of additional
>> > > method, which may not be conducive to creating simple, small-scale
>> > > programs:
>> > >
>> > > ```java
>> > > public class alternative2 {
>> > >     public static void main(String... args) throws SQLException {
>> > >         String selectSQL = "selectSQL";
>> > >         String studentID = "studentID";
>> > >         String studentName = getStudentName(selectSQL, studentID);
>> > >
>> > >         System.out.println(studentName);
>> > >     }
>> > >
>> > >     public static String getStudentName(String sql, String studentID)
>> > > throws SQLException {
>> > >         String jdbcUrl = "jdbcUrl";
>> > >         String username = "username";
>> > >         String password = "password";
>> > >
>> > >         try (Connection conn = DriverManager.getConnection(jdbcUrl,
>> > > username, password)) {
>> > >             try (PreparedStatement statement = conn.prepareStatement(sql)) {
>> > >                 statement.setString(1, studentID);
>> > >
>> > >                 try (ResultSet result = statement.executeQuery()) {
>> > >                     String studentName = "not found";
>> > >
>> > >                     while (result.next()) {
>> > >                         studentName = result.getString(1);
>> > >                     }
>> > >
>> > >                     return studentName;
>> > >                 }
>> > >             }
>> > >         }
>> > >     }
>> > > }
>> > >
>> > > ```
>> > >
>> > > /* GET BETTER EVERY DAY */


More information about the amber-dev mailing list