What is the easiest way to get the Clojure Reflector to throw the underlying exception, even though it is not declared to do so?
Oh, and is this a good idea?
Stu
This method might come in handy here:
http://james-iry.blogspot.com/2010/08/on-removing-java-checked-exceptions-by.html
> Oh, and is this a good idea?
I'd say it's good as long as you stay in clojure.
When calling from java you'll have to deal with undeclared checked
exceptions. You can still catch those (as detailed in the article),
but undeclared checked exceptions are arguably worse that declared
checked exceptions ;)
--
__________________________________________________________________
Herwig Hochleitner
Yup, if the unwrapping is automatic, I'd be in favor of using a
ClojureRuntimeException instead of the current generic
RuntimeException wrapper.
--
Sean A Corfield -- (904) 302-SEAN
An Architect's View -- http://corfield.org/
World Singles, LLC. -- http://worldsingles.com/
Railo Technologies, Inc. -- http://www.getrailo.com/
"Perfection is the enemy of the good."
-- Gustave Flaubert, French realist novelist (1821-1880)
> --
> You received this message because you are subscribed to the Google Groups "Clojure Dev" group.
> To post to this group, send email to cloju...@googlegroups.com.
> To unsubscribe from this group, send email to clojure-dev...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/clojure-dev?hl=en.
>
>
--
And what is good, Phaedrus,
And what is not good—
Need we ask anyone to tell us these things?
I suppose, you are referring to "chucked" exceptions (thanks, James
Iry for coining that term :-)
When I laid out my argument against throwing chucked exceptions,
because of it making the life of interop users harder, I realized that
you can get a checked exception from a IFn.invoke() anyways. After all
you can always do a (throw (Exception.)) in clj code anyway. Not to
mention non-reflective interop calls from clojure.
So IMO that makes the case for "chucking" exceptions in Reflector.java
pretty solid.
It leaves the question how to catch chucked exceptions from java.
Following the approach of James Iry, I propose having
public class Util {
public static <T extends Throwable> void declareThrows(Class<T> c) throws T {}
public static <T1 extends Throwable, T2 extends Throwable> void
declareThrows(Class<T1> c1, Class<T2> c2) throws T1, T2 {}
// ... ad nauseum ...
// ... also as a convenience ...
public static <T extends Throwable> Object invokeThrowing(Class<T>
c, IFn fn, Object... args) throws T { return fn.invoke(args); }
}
so we can do
try {
Util.declare(IOException.class);
return slurp.invoke(io_file.invoke("~", ".soup"));
} catch (IOException e) { return "no soup"; }
or in this common case
try {
return Util.invokeThrowing(IOException.class, slurp,
io_file.invoke("~", ".soup"));
} catch (IOException e) { return "no soup"; }
I have taken the freedom to implement this approach and attach a patch
to http://dev.clojure.org/jira/browse/CLJ-855
kind regards
--
__________________________________________________________________
Herwig Hochleitner
Best,
Constantine Vetoshev
this is a real problem for interop, both ways.
having multiple patches is a drag, hard to tell which one to go with.
it seems like the sneaky throw patch is the one to use? dunno
Other things being equal, I think that the patch with better
performance should go in. Perhaps someone with a better understanding
of the JVM speed tradeoffs of the two approaches could chime in?
Best,
Constantine Vetoshev
I've provided two different solutions to the patches because they
represent different trade-offs. I don't have a strong opinion as to
which would be preferable.
(1) The 'unwraps' patch hews closest to the current implementation,
specifically the 8fda34e4c77 change which chose to wrap checked
exceptions in RuntimeExceptions as close to the source as possible so
as not to need to declare 'throws Exception' all over the place.
The essence of the 'unwraps' patch is just to teach Clojure's try form
to automatically recognize and unwrap such exceptions before passing
them on to the catch clauses.
(2) The 'sneaky throws' patch undoes the 8fda34e4c77 decision to wrap
all checked exceptions, but still manages to be rid of the 'throws
Exception' problem.
It seems clear to me that 'sneaky throws' will have better performance
since it doesn't incur the wrap/unwrap overhead nor does it incur the
overhead of a more complex try form. (Does this even matter? We are,
after all, talking about *exception* handling.)
I consider 'unwraps' less risky since it layers on top of what is
already there, which I assume to be solid.
I'd prefer a solution like 'sneaky throws', but I think it requires
someone who remembers what the big picture was when they wrote
8fda34e4c77. (You'll see the JIRA issue mentions some questions.)
8fda34e4c77, for the curious:
https://github.com/clojure/clojure/commit/8fda34e4c77cac079b711da59d5fe49b74605553
// Ben
> Best,
> Constantine Vetoshev
Just sending another reminder about CLJ-855. It has been an
outstanding problem since 1.3 came out, and a patch has been available
for three months now. Judging by activity on the original thread
(http://groups.google.com/group/clojure/browse_thread/thread/23997560754c8242/e3e58fccdbd92a0c),
quite a few people care about seeing this fixed for Clojure 1.4. Could
someone in core please comment?
Best,
Constantine Vetoshev
Any code out there that is catching RuntimeException and unwrapping it
will be broken by both sneaky-throw and try-unwraps as they stand -
which might cause problems.
I'd lean toward try-unwraps but based on the existing RuntimeException
and allowing any such existing code to continue to work (by not
unwrapping if there's a catch that can catch RuntimeException) but
others may prefer a more complete fix.
If we're going to break that code, I think I'd prefer sneaky-throw so
that try remains "clean" (and so that Clojure code can't leak weird
wrapped exceptions to any enclosing non-Clojure code - I live in a
polyglot world so I care about that).
Sean
I think that fixing breaks introduced by either patch will be easy —
certainly much easier than discovering the problem while migrating to
Clojure 1.3, and then being forced to write workarounds everywhere.
> If we're going to break that code, I think I'd prefer sneaky-throw so
> that try remains "clean" (and so that Clojure code can't leak weird
> wrapped exceptions to any enclosing non-Clojure code - I live in a
> polyglot world so I care about that).
I also prefer sneaky-throw.
I agree sneak-throw is the clear choice, if we could just someone from
core to pay attention.
> --
> You received this message because you are subscribed to the Google Groups "Clojure Dev" group.
> To post to this group, send email to cloju...@googlegroups.com.
> To unsubscribe from this group, send email to clojure-dev...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/clojure-dev?hl=en.
>
--
On Thu, Jan 12, 2012 at 2:43 PM, Sean Corfield <seanco...@gmail.com> wrote:Any code out there that is catching RuntimeException and unwrapping itwill be broken by both sneaky-throw and try-unwraps as they stand -which might cause problems.I think that fixing breaks introduced by either patch will be easy —certainly much easier than discovering the problem while migrating toClojure 1.3, and then being forced to write workarounds everywhere.If we're going to break that code, I think I'd prefer sneaky-throw sothat try remains "clean" (and so that Clojure code can't leak weirdwrapped exceptions to any enclosing non-Clojure code - I live in apolyglot world so I care about that).I also prefer sneaky-throw.
I agree sneak-throw is the clear choice, if we could just someone from
core to pay attention.
Type hints do help, at least in my use cases. Thank you for the suggestion, Stu.
To use my original example:
(defn broken-catch [filename]
(try (java.io.FileReader. filename)
(catch java.io.FileNotFoundException fnfe
"FileNotFoundException caught")
(catch RuntimeException ioe
(str "RuntimeException caught; cause: "
(.getCause ioe)))))
(defn working-catch [^String filename]
(try (java.io.FileReader. filename)
(catch java.io.FileNotFoundException fnfe
"FileNotFoundException caught")
(catch RuntimeException ioe
(str "RuntimeException caught; cause: "
(.getCause ioe)))))
This works as expected in Clojure 1.3.0:
user=> *clojure-version*
{:major 1, :minor 3, :incremental 0, :qualifier nil}
user=> (broken-catch "/etc/passwdXYZ")
"RuntimeException caught; cause: java.io.FileNotFoundException:
/etc/passwdXYZ (No such file or directory)"
user=> (working-catch "/etc/passwdXYZ")
"FileNotFoundException caught"
Best,
Constantine
Q1. Reflector catches Exception, but seems most interested in
its cause. The cause, if Error is thrown directly, if present and
Exception it's possibly wrapped in an RTE and then thrown.
This implies to me that Reflector is expecting the code it's calling
at that point to throw wrapped exceptions. So maybe, it's expecting to
catch wrapped exceptions from Clojure code, but then why isn't it
catching RTE? If the exceptions it thinks might bubble up here aren't
coming from Clojure code (which wraps all checked exceptions), then
why is it blindly assuming the cause must be more important than the
actually thrown exception? It seems sloppy, but maybe I'm not
understanding the thinking behind it. Thus my initial question.
If I knew that the intent was to catch RTEs produced by Clojure
wrapping checked exceptions, then I could just sneaky-throw the caught
exception and be done with it.
Q2: The fact that Var.dissoc catches and then returns an exception
rather than throwing it still strikes me as very odd, though I think I
figured out that it doesn't matter. (TL;DR: scroll down a bit).
//-- Var.java
static IFn dissoc = new AFn() {
@Override
public Object invoke(Object c, Object k) {
try
{
return RT.dissoc(c, k);
}
catch(Exception e)
{
return Util.runtimeException(e);
}
}
};
//-- RT.java
static public Object dissoc(Object coll, Object key) {
if(coll == null)
return null;
return ((IPersistentMap) coll).without(key);
}
Presumably Var.dissoc is expected to behave like RT.dissoc (return the
new version of the PersistentMap), so why would it suddenly *return*
an Exception object? It's used once, in bindRoot(), which passes it to
the alterMeta:
//-- Var.java
//binding root always clears macro flag
synchronized public void bindRoot(Object root){
validate(getValidator(), root);
Object oldroot = this.root;
this.root = root;
++rev;
try
{
alterMeta(dissoc, RT.list(macroKey));
}
catch (Exception e)
{
throw Util.runtimeException(e);
}
notifyWatches(oldroot,this.root);
}
//-- AReference.java
synchronized public IPersistentMap alterMeta(IFn alter, ISeq args) {
_meta = (IPersistentMap) alter.applyTo(new Cons(_meta, args));
return _meta;
}
- The oddity is dissoc.invoke(c,k) returning, rather than throwing an Exception.
- This occurs if RT.dissoc(c, k) throws and Exception
- This occurs (most likely) if c doesn't implement IPersistentMap
- This means that the alterMeta method must have been called on an
AReference whose _meta is not an IPersistentMap
- But, that's impossible since we know statically taht
AReferenced._meta is always an IPersistentMap.
So, the oddity in Var.dissoc could only observed if the implementation
of IPersistentMap used for AReference._meta had an error in its
without method, causing it to throw an exception. That seems unlikely,
and even it occured it would be an internal error in Clojure (all bets
are off).
So, to get back to the original point: Var.dissoc is *odd*, but
probably harmless. I can find no good reason for its oddness, since I
think I've just established that the error scenario will not occur in
practice. So, following the principle of least astonishment, I'd
suggest it throw, rather than return, the exception.
So, I've answered Q2 myself to my satisfaction. I'd appreciate some
illumination of Q1 since my mind reading skills are lacking.
// Ben
> 2. Does type-hinting to avoid reflection succeed as a workaround? If so we
> should document this somewhere for people who are stuck on existing
> versions.
>
> Stu
>
> Stuart Halloway
> Clojure/core
> http://clojure.com
>
Exceptions from java reflection are generally wrapped in some thing like invocation target exception
Ah, that's a good thought, but wouldn't it make more sense to
specifically catch the specific exceptions thrown by reflection?
// ben
It does avoid the bug in the case where the wrapping is introduced by
Reflector.javaAFAIK there are a lot of other places, where such
wrapping might be introduced.
> 1. The comment thread on the ticket
> (http://dev.clojure.org/jira/browse/CLJ-855) appears to end with open
> questions. Are these answered?
Not really, because the only question that really is left unanswered is:
Do we allow the undoing of the checked-exceptions - ragequit patch
https://github.com/clojure/clojure/commit/8fda34e4c77cac079b711da59d5fe49b74605553
along with the breakage that will ensue.
The other issues (and reasons why that is desirable) might be obvious
from previous discussion, but I'll try to summarize for your
convenience.
When it was decided to have RuntimeExceptions everywhere in clojure,
it supposedly made life easier for polyglot users.
You didn't need to prepare for CheckedExceptions everywhere anymore.
Except when you had to, because of someone deciding to (throw
(Exception.)) in a clojure code.
Only now you have to pull special tricks to even be _able_ to catch
that Exception.
The reflection code only highlights that fact.
So: Do we want to undo the exception wrapping?
This is not as bad as it sounds, as we can get away with throwing
checked exceptions, without declaring them;
Even from plain Java; Also catching.
Otherwise we have picked a fight against corner cases. Is this the clojure.
Some more biased points:
## Keep it the way it is
- Play nice with java users most of the time
- Take a small performance hit
- Make life messy for clojure users just wanting the original exception
- Still won't solve all cases of people expecting an Exception and
getting it wrapped
-TODO: Lay out clear rules which exceptions to wrap, which ones to
leave, apply those rules to Reflector.java
## Sneakily throw (and catch) undeclared checked exceptions
- Don't play nice with java users, instead give them the tools to handle it
- Take no performance hit
- Give clojure users the exceptions they expect, straightforward semantics
- Might be a hard sell to the core team, who appeared to be very glad
to get rid of Checked Exceptions.
-TODO: Greenlight it
===============
I'm in favor of option two, especially since it's possible to give FNs a method:
somefn.invokeChecked<IOException>(arg);
to declare a checked exception on a function call, which seems fit for
an interface to a dynamic language.
where a checked exceptions might be thrown any time (but mostly won't).
===============
Punch line: If we don't do it now, we should definitely do it in 2.0
(it's called semantic versioning, maybe you've heard of it)
(How much code is being written to call IFn directly from Java? This is a serious question, not an attempt to dismiss.)
- Cosmin
> --
> You received this message because you are subscribed to the Google Groups
> "Clojure Dev" group.
> To view this discussion on the web visit
> https://groups.google.com/d/msg/clojure-dev/-/J2sp3aMtxk8J.
>
> To post to this group, send email to cloju...@googlegroups.com.
> To unsubscribe from this group, send email to
> clojure-dev...@googlegroups.com.
> For more options, visit this group at
> http://groups.google.com/group/clojure-dev?hl=en.
--
Cosmin Stejerean
http://offbytwo.com
This is how we do it at World Singles (Var.invoke) and we're relying
heavily on it for integrating Clojure into our platform.
--
Sean A Corfield -- (904) 302-SEAN
An Architect's View -- http://corfield.org/
World Singles, LLC. -- http://worldsingles.com/
"Perfection is the enemy of the good."
For exception handling, we either catch it in the Clojure code or, if
it escapes to the calling code, treat it as a generic "Sorry,
something went wrong! Our team have been notified..." so we don't much
care about wrapped / unwrapped exceptions outside Clojure, only inside
Clojure.
From the Java side, programming against interfaces doesn't help when the interfaces don't declare any exceptions, but throw checked exceptions, and you want catch them.
The Clojure side is pretty bad, too. Here's a field report. We finally had the resources to take a good look at upgrading to 1.3, and we ran into lots of problems with the RTE wrapping. We do a lot of Java interop. The problem with wrapping exceptions in RuntimeException is that it changes the semantics of exceptions. When someone throws EOFException they mean "Holy crap! EOFException!" not "RuntimeException RuntimeException EOFException" As I said before, it isn't possible to know from the Clojure side whether code you call might go through the Reflector and have its exceptions wrapped.
You would think that you could just catch Exception and use clojure.stacktrace/root-cause, except in our case we're using Futures, and when an exception occurs it gets wrapped in an ExecutionException or a CancellationException, and we want to find out whether we have an ExecutionException or a CancellationException. If we use root-cause it unwraps all the way down past the ExecutionExceptionn/CancellationException. We could certainly just unwrap the layers of RuntimeExceptions, but what if we wanted to catch a RuntimeException?
It seems like every one of our catch clauses is having to get updated, and they are having to be updated to do something like catch Exception then some kind of cond based on the class of the root cause, which is really just duplicating what a catch is supposed to do in the first place.
In the general case, we can't be sure whether we will get a wrapped exception or an unwrapped exception. It's not fair for Clojure to make assumptions about how easy it will be for users to unwrap since in some cases we may not want to unwrap all the way down, or we may want to catch a RuntimeException.
And checked exceptions are a fabrication of the Java compiler, so I'm still at a loss to understand why this change was made. It makes Clojure's Java code not have to declare that they throw Exception? Is there some benefit to either the Java side or the Clojure side or both. I would like to understand the tradeoffs.
It's possible that something like sneaky throw or "chucking" exceptions would make things better from the Clojure side to not have to deal with if wrapping is occuring or how far down you want to unwrap. However, I'm still in favor of reverting this commit. I see only breakage and no benefit.
Is there some benefit?
Is there a better way to deal with this in my Clojure code?
Paul
And checked exceptions are a fabrication of the Java compiler, so I'm still at a loss to understand why this change was made. It makes Clojure's Java code not have to declare that they throw Exception? Is there some benefit to either the Java side or the Clojure side or both. I would like to understand the tradeoffs.
It's possible that something like sneaky throw or "chucking" exceptions would make things better from the Clojure side to not have to deal with if wrapping is occuring or how far down you want to unwrap. However, I'm still in favor of reverting this commit. I see only breakage and no benefit.
Is there some benefit?
Is there a better way to deal with this in my Clojure code?
Some of the original conversation is at http://clojure-log.n01se.net/date/2011-03-21.html. But that does not seem to be the beginning. Chas, do you (or anybody else) have links to more of the conversation?
Is there a better way to deal with this in my Clojure code?1.4 beta 2 with sneaky throw is sliding through the maven gullet as we speak. Once it is out, please test it and let us know what problems it causes/solves.
And checked exceptions are a fabrication of the Java compiler, so I'm still at a loss to understand why this change was made. It makes Clojure's Java code not have to declare that they throw Exception? Is there some benefit to either the Java side or the Clojure side or both. I would like to understand the tradeoffs.
It's possible that something like sneaky throw or "chucking" exceptions would make things better from the Clojure side to not have to deal with if wrapping is occuring or how far down you want to unwrap. However, I'm still in favor of reverting this commit. I see only breakage and no benefit.
Is there some benefit?Some of the original conversation is at http://clojure-log.n01se.net/date/2011-03-21.html. But that does not seem to be the beginning. Chas, do you (or anybody else) have links to more of the conversation?
Is there a better way to deal with this in my Clojure code?1.4 beta 2 with sneaky throw is sliding through the maven gullet as we speak. Once it is out, please test it and let us know what problems it causes/solves.
Great! I think I can live with sneaky throw, since I guess it is essentially what would happen if more of the Clojure compiler/runtime were written in Clojure.
This was going to be a huge blocker to Sonian being able to upgrade to 1.4. The wrapping was the root of our problems. I'm glad that it is worked out. We'll be working against 1.4-beta2. Are there concrete plans for when there might be a final 1.4 release?
Thanks to everyone involved!
Ie:
public static <T extends Throwable> Object
declareExceptionsAndInvoke(IFn callable, Object... args) throws T {
callable.applyTo(Arrays.asList(args));
}
This would allow Java-side code to do (normally it can't catch
Exception, because javac doesn't believe it to be thrown within the
catch block):
void example() {
Var theNs = RT.var("clojure.core", "the-ns"); // throws Exception
if ns doesn't exist
try {
RT.<Exception>declareExceptionsAndInvoke(theNs, "IdontExist");
}
catch(Exception e) {
// I can catch this
}
}
> This was the most critical issue I was aware of blocking 1.4. What's the next most critical?
Maybe not so critical, but still nice to have:
CLJ-103 Incorrect error with if-let
CLJ-196 *file* returns "NO_SOURCE_PATH", but the doc says it should be nil
CLJ-683 broken example in ns docstring
CLJ-917 clojure.core/definterface is not included in the API docs
CLJ-835 defmulti doc string doesn't mention needing to be passed a var for the value of :hierarchy
CLJ-788 Add source and line members and getters to CompilerException
CLJ-939 Exceptions thrown in the top level ns form are reported without file or line number
CLJ-940 Passing a non-sequence to refer :only results in uninformative exception
And my own personal favourite:
CLJ-860 Add ability to disable locals clearing
> Stuart Halloway <stuart....@gmail.com> writes:
>
>> This was the most critical issue I was aware of blocking 1.4. What's the next most critical?
>
Also really up to Clojure/core team whether these are critical...
These are already marked as screened:
CLJ-886 java.io/do-copy can garble multibyte characters
CLJ-852 IllegalArgumentException thrown when defining a var whose value is calculated with a primitive fn
CLJ-369 Check for invalid interface method names
CLJ-924 Error reporting of invalid digit in unicode character literal
These have unscreened patches that fix bugs:
CLJ-870 clojure.string/replace behaves unexpectedly when \ or $ are part of the result string
(the most recent patch also fixed CLJ-753 and CLJ-905)
CLJ-881 Problem with the "cl-format" function from the clojure.pprint
(the bug is that cl-format throws an exception for a perfectly legal call, data-dependent)
CLJ-931 Syntactically broken clojure.tests/are tests succeed
It sounds like the following would be nice so that cljs.core and all other namespaces can have a useful / symbol:
CLJ-873 Allow the function / to be referred to in namespaces other than clojure.core
Andy
public static <T extends Throwable> void thrown(Class<T> c) throws T {} Var slurp = RT.var("clojure.core", "slurp"); try {thrown(FileNotFoundException.class);
System.out.println(slurp.invoke("/etc/Idontexist")); } catch (FileNotFoundException fnfe) {}Is there a better way to deal with this in my Clojure code?1.4 beta 2 with sneaky throw is sliding through the maven gullet as we speak. Once it is out, please test it and let us know what problems it causes/solves.
Great! I think I can live with sneaky throw, since I guess it is essentially what would happen if more of the Clojure compiler/runtime were written in Clojure.
This was going to be a huge blocker to Sonian being able to upgrade to 1.4. The wrapping was the root of our problems. I'm glad that it is worked out. We'll be working against 1.4-beta2. Are there concrete plans for when there might be a final 1.4 release?
Thanks to everyone involved!I will hold my warm fuzzy feeling til I hear back from your testing... :-)