4 ms·
Note that you can do something like Typestates in Java using static inner classes that have no public constructors, though undoubtedly there are advantages to h
by lliamander 5y ago
Note that you can do something like Typestates in Java using static inner classes that have no public constructors, though undoubtedly there are advantages to having first class language support. Behold:
Scanner.OpenScanner sOpen = Scanner.open(/*some source*/);
//do some processing
Scanner.ClosedScanner sClosed = sOpen.close();
sClosed.nextLine() //type error
I sometimes do this in combination with the Builder pattern. This "typesafe builder" allows for a fluent API that guides the user as to its use.
The point is, you can still use types to represent states.
- ketralnis 5y agoIs sOpen.nextLine() after the close also a compile time error?
- lliamander 5y agoNo. That's a good point and one of those things that having built-in language support helps. But if you are using method chaining (i.e. fluent API) that matters less.
- tstack 5y agoYes, one of the advantage of language support is that "sOpen" will be consumed by the "close()" call and can't be reused. I don't think that's really possible in Java without an extra checker. But, it's usually not a problem in practice if the user sticks to using the API in a fluent manner.
- valenterry 5y agoInstead of trying to create an extra language-feature for that, I like the resource-style-solution. I.e., you don't have a "Scanner", you have "Resource<Scanner>" and you can't use it directly. You have to do this: Resource<Scanner> scannerResource = ... scannerResource.use( scanner => ... // use the scanner ) After the closing parenthesis the .close() is called automatically. This allows for a lot of nice things, such as providing methods for stacking and combining resources, thus making sure they automatically close in the correct order. Callers never have to care about closing resource explicitly. Error handling is much easier to do. Etc.
- ajkjk 5y agoI read that pseudocode and all I think is: this sounds like it's making up for a missing language feature.
- valenterry 5y agoWell, it's pseudocode, but it's close enough. Here's a runnable example: https://scastie.scala-lang.org/5klYiw6cT7yckJbn2zGmJg https://scastie.scala-lang.org/5klYiw6cT7yckJbn2zGmJg The gist is: this does everything needed for resources, including preventing errors at compile-time, being composable/reusable and handling errors correctly (you can throw an exception in the middle and the scanners will still be closed). That being said, having it as a language feature might make certain use-cases nicer and more ergonomic. However, it also makes the language much more complex; I don't think resources alone are a sufficient reason to fundamentally change the language. One thing that Rust has gotten fairly right so far is not having added too many special cases and rely on general language features mostly. For example, don't have special syntax/handling for optional/nullable values, but just use the existing macrosystem to deal with it. I hope this philosophy will continue. Rust is here to stay and these kind of decisions make a big difference in the long run.
- lliamander 5y agoI was just thinking about this earlier today! But then isn't that just the same thing as try-with-resouces?
- valenterry 5y agoVery similar but not the same. try-with-resources is essentially a "hack" that tries to emulate what I described (and it mostly succeeds with it). That way, the ecosystem doesn't have to be changed. "My" solution requires that libraries use/support this pattern, and it also requires the language to have nice syntax, something that Java isn't very good at.