New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add scala.util.Using, for automatic resource management #6907
Conversation
bikeshedding welcome |
I would think the signature should be |
See also discussion at #6347. |
@Ichoran I guess I have two comments on that
regarding the latter point, this is what the implementation of the four resource method would look like if it returns a using(resource1) { r1 =>
using(resource2) { r2 =>
using(resource3) { r3 =>
using(resource4) { r4 =>
body(r1, r2, r3, r4)
}.get
}.get
}.get
} not horrible, but certainly uglier |
You want If you're going to have a And of course you ought to be able to for {
r1 <- using(resource1)
r2 <- using(resource2)
r3 <- using(resource3)
r4 <- using(resource4)
} yield body(r1, r2, r3, r4) which means the whole thing should be an applicative functor, or functor-like (I don't know what the non-identity version is, where |
Okay, this all works. I just have a stub implementation for the actual resource logic, but I envision it like this: class Using[R](r: => R) {
import scala.util.{Try, Success, Failure}
def apply[A](f: R => A)(implicit ev: Using.Resource[R]): Try[A] = map[A](f)(ev)
def map[A](f: R => A)(implicit ev: Using.Resource[R]): Try[A] = Try{ val myR = r; val ans = f(myR); ev.close(myR); ans }
def flatMap[A](f: R => Try[A])(implicit ev: Using.Resource[R]): Try[A] = Try{ val myR = r; val ans = f(myR); ev.close(myR); ans }.flatten
}
object Using {
trait Resource[R]{ def close(r: R): Unit }
def apply[R](r: => R): Using[R] = new Using(r)
} Then we have usage like so: case class Id[A](value: A) {}
object Id {
implicit def resourcedId[A]: Using.Resource[Id[A]] = new Using.Resource[Id[A]] { def close(r: Id[A]) = () }
}
@ for { u <- Using(Id(2)); v <- Using(Id("salmon")) } yield v.value * u.value
res2: scala.util.Try[String] = Success("salmonsalmon")
@
Using(Id(true)).flatMap{ bool => Using(Id('x')){ c => if (bool.value) c.value.toUpper else c }}
res5: scala.util.Try[Any] = Success('X') |
I'm not sure what the name of this not-exactly-a-Monad thing is, if it has one, but the usage is really nice. You just use your stuff, and the right thing happens, and exceptions are caught. |
@Ichoran my only concern is how fatal exceptions are handled. I personally think that even in the case of a fatal exception, we ought to attempt to close the resource before re-throwing the exception (especially if it's a I think it makes sense to have both, but with the Also, I would be more inclined to put the implicit |
See also @densh idea of scoped implicit lifetimes |
@Ichoran pushed the monad-ish impl you suggested. I'm open to burying the raw methods a bit more to make them less obvious |
@NthPortal - You can put the typeclasses on the constructor, but then you can't chain the applies. So it would have to be |
* @return the result of the operation, if neither the operation nor | ||
* closing the resource throws | ||
*/ | ||
def using[R: Resource, A](resource: R)(body: R => A): A = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think Using.using
is good nomenclature. Maybe Using.direct
or Using.unsafe
or Using.unwrapped
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
my only concern is that I'd prefer a method name that, if imported statically, still makes sense.
e.g. Using.direct(file.open)
makes sense, but direct(file.open)
doesn't really
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
withResource
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that more I play with it in my mind, the less happy I am with the name withResource
. something about it doesn't flow right.
what do you think of just resource
or tryWithResource
? The former works well when not imported - Using.resource
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
or another possibility: tryWith
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(for comparison)
Using.withResource(new FileInputStream("foo.txt")) { _ => () }
withResource(new FileInputStream("foo.txt")) { _ => () }
Using.resource(new FileInputStream("foo.txt")) { _ => () }
resource(new FileInputStream("foo.txt")) { _ => () }
Using.tryWithResource(new FileInputStream("foo.txt")) { _ => () }
tryWithResource(new FileInputStream("foo.txt")) { _ => () }
Using.tryWith(new FileInputStream("foo.txt")) { _ => () }
tryWith(new FileInputStream("foo.txt")) { _ => () }
I think the implementation is pretty much done at this point. The scaladoc could use some improvement, which I would appreciate input on. The implementation is ready for review. |
should this be in |
review @kmizu |
* @param f the operation to perform | ||
* @param r an implicit [[Using.Resource]] | ||
* @tparam A the return type of the operation | ||
* @throws java.lang.IllegalStateException if the resource has already been used |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am open to being convinced that the resource can be generated again using the by-name parameter, and that the Using
instance can be used arbitrarily many times.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@NthPortal Sorry. I forgot to review.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@NthPortal It looks good to me as far as I can see
JFYI I noticed it's declared in |
Good catch! I expected IntelliJ to automatically change the package statement when I moved the files, but apparently it didn't ¯\_(ツ)_/¯ I'll fix that |
Add Using utility to perform automatic resource management.
@joshlemer bonus side effect of simplifying the implicit |
I am just throwing out a suggestion but maybe a factory method Resource.fromTry[A](f: A => Try[_]): Resource[A] might come frequently in handy as well EDIT: Whoops fixed typo in the code |
@joshlemer It's not immediately apparent to me what that does |
@NthPortal something like def fromTry[A](f: A => Try[_]): Resource[A] = (a: A) => f(a).failed.foreach(throw _) |
@joshlemer what's the value of catching exceptions in a |
@NthPortal I was thinking more in the case where a user has a library which returns a Try for some IO operation, rather than itself throwing. In that case, the user would have to implement a Resource[T] which just throws any failed Try's, so it might be handy to have that case covered. |
@joshlemer aha, I see. I'm not super into it, but I'd love to hear others' thoughts on it |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very nice, thanks! Asking for a few scaladoc improvements.
* operation using resources, after which it will release the resources, in reverse order | ||
* of their creation. The resource opening, operation, and resource releasing are wrapped | ||
* in a `Try`. | ||
* |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the doc should explain what a resource is, how the Resource type class is used, what the user needs to do for non-AutoClosables.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since there are two ways of using Using
, I think this comment (which is the entry-point) should mention both, say why they exist, what the differences are.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When you say two ways, to you mean Using(foo){ _.bar }
vs for (f <- Using(foo)) yield f.bar
, or Using(foo){ _.bar }
vs Using.resource(foo){ _.bar }
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant Using(foo)
vs Using.resource(foo)
, but explaining the possibilities how to use the monadic version is also welcome.
} | ||
|
||
/** @define recommendUsing It is highly recommended to use the `Using` construct, | ||
* which safely wraps resource usage and management in a `Try`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's maybe not clear to everyone what the Using
construct is
@NthPortal is there a reason not to make |
@joshlemer not that I can think of, but I also can't think of a reason why you'd ever declare it as a parameter to something, so I don't think it makes a difference. If you want to use its result for something, just use a |
maybe it could still make M5, but I think it could wait for RC1 if it must. we aren't adding new things post-M5, but I think we should allow exceptions for new things that were already PR'ed well in advance of the M5 deadline. especially something like this that isn't being used yet and has zero effect on code that doesn't use it. |
@SethTisue I think that's a good idea. I don't know if I can push through the doc changes by tomorrow while also trying to get |
* val lines: Try[List[String]] = Using(resource1) { r1 => | ||
* r1.lines.toList | ||
* } | ||
* }}} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Ichoran what are your thoughts on removing the apply
method, so that there aren't two ways to use it - you have to use a for
comprehension (or manual map
/flatMap
)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This has the added benefit of allowing us to pass the implicit Resource
on construction
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The for-comprehension-only version probably ought to be called Use
or Used
, not Using
, if there's only that option. I generally prefer the apply version, but I don't have a strong preference.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't feel strongly about it either; I'm just concerned that some others might.
I'm going to leave it as is for now
shall we merge as-is, to get feedback from a broader group, and figure there's still time to tweak it for RC1? |
That works for me! |
(this probably should have been squashed first, oops) |
doh, my bad! |
Resolves scala/bug#11003
Resolves scala/bug#8028 (I think)
(and possibly others)