I think you mentioned that at https://ask.clojure.org/index.php/9775/better-stacktraces-for-protocol-methods?show=9775#q9775
Out of curiosity, what’s the technical reason why recur
is disallowed in catch
and finally
? I just came across this for the first time today and my random googling hasn’t provided a great answer so far
So what I’m hearing is that we’ll have a good solution when project loom comes out 👀 :troll:
potentially due to GC? https://clojure.atlassian.net/browse/CLJ-31
Yeah, I found that issue in my googling. I don’t really understand it though, and strangely enough the example doesn’t even have a try
in it
from the info, I'm guessing that recuring across either binding
or try
was causing some objects to not be garbage collected so both of those were disallowed
Logically try pushes an exception handler onto a stack(popped after the try completes), so recurring across it grows that stack
Clj-31 was I think my first clojure patch, and it didn't get the analysis correct so it would complain if put you put a loop in a finally until someone else fixed it
curious if that is a hard restriction or not
like, in my head i feel like there is a way to rewrite the try to pop off that exception before recurring
like if this
(loop [x 10]
(if (= x 0)
"done"
(recur (dec x))))
got translated to this
Object x = 10;
Object out;
while (true) {
if (x == 0) {
out = "done";
break;
}
else {
x = x - 1;
continue;
}
}
but does that make sense?
(don't know yet, i'm noodling)
(it doesn't)
if recur is the supposed to act like a tail call in a language with tco
(loop [x "abc10"]
(try (Integer/parseInt x)
(catch NumberFormatException e
(recur (.substring x 1 (count x))))))
then given tco and a fn like (fn f [x] (if x (throw (Exception. "foo")) (try (f (not x)) (catch Throwable t 1))))
, is the self call to f a tail call or not, and how does that square with the exception handling
a catch technically could be a tail, if there is no finally
Object x = "abc10";
Object out;
while (true) {
try {
out = Integer.parseInt((String) x);
break;
}
catch (NumberFormatException e) {
x = x.substring(x, x.length());
continue;
}
}
hmm yeah...
unless you put the "finally stack" on the heap
I am not saying it isn't possible, because once you compile things down to jvm bytecode, the way exception handlers work is they are in effect for a range of instructions, say instructions 5-10 in a method
and you can you know, jump around
but logically, in the expression based high level language we actually program in it doesn't make sense
(finallys don't actually exist at the bytecode level, the java compiler and the clojure compiler just simulate them using exception handlers)
recur is not a goto jump, it is intended work like tco would if we had general tco
I have a dusty book on jvm bytecode that I should maybe get around too i guess
moving where the stack is kept in memory doesn't change that fact that the stack is growing
yeah
the issue isn't really unique to exception handlers, it is an issue of mixing dynaimc extents with jumps, like if you were writing asm, pushed an argument on to the stack, had several more instructions, and then popped it, but one of the intervening instructions jumped back to somewhere before the first push
and you can see in this masters thesis somewhat wrote on adding tail calls natively to the jvm, in his definition of what constitutes a tail call, if has an exception handler around it, or a synchronized block, it isn't a tail call https://ssw.jku.at/Research/Papers/Schwaighofer09Master/schwaighofer09master.pdf
(a sychronized block is a try/finally in disguise)
I also rewrote the exception handling code for core.async's go macro a while back, and screwed that up the first time too (I forget, I didn't balance pushes/pops correctly so finally didn't work right or something)
I always find this stuff interesting even if I probably don't understand everything.
The thing about core.async's go macro is it is basically a continuation passing transform(the way it is written it talks about basic blocks and whatever, but as a knight of the eastern calculus I say ni to that)
cps style makes tail calls extremely clear, a tail call is when a function calls another function with the same continuation
And the way you model exceptions in cps is often as a "double barreled" continuation, basically two continuations, one for the normal case and one for exceptions
And for a function call inside a try/catch the exception continuation is the new catch, not the same continuation as outside of the try, so it can't be a tail call
http://matt.might.net/articles/oo-cesk/ matt might has a lot of great posts covering this kind of thing
Why do (/ 1 0.0)
and (apply / [1 0.0])
give different results in Clojure?
Feel free to follow up here: https://github.com/babashka/babashka/issues/747
I guess the logic is in clojure.lang.Numbers somewhere
Additional data point:
user=> (let [f /] (f 1 0.0))
Execution error (ArithmeticException) at user/eval140 (REPL:1).
Divide by zero
Yeah, it's definitely related to inlining
hm, I’m wondering if I want to emulate this in my interpreter because it handles both cases the same way as (/ 1 0.0)
at the moment
Maybe a case of Hyrum's law
As Alex suggested in the above linked issue: it might be because of type info available as either Long or Object
when dealing with credentials for an api, is it a good idea/common practice to make the credentials a type rather than a map just to prevent accidental printing(/repl logging) of sensitive data? Do you typically put all credential data together in a type like this, even though only a subset of the credential data will actually be sensitive (such as username+password)? Are there any other common guidelines for dealing with credentials in clojure?
> when dealing with credentials for an api, is it a good idea/common practice to make the credentials a type rather than a map just to prevent accidental printing(/repl logging) of sensitive data?
Might be, anyway I'd make sure to explicitly override print-method
for the given defrecord so that one is not relying on a random behavior
An alternative approach is walk
ing the hashmaps and removing known-bad or suspected-sensitive keys (e.g. :password
). Of course you'd have to remember to invoke this, unless abstracting it away via e.g. the logging setup
see also: https://github.com/vlaaad/blanket
maybe you don't need the library, but just look at the concept — you don't even need a custom defrecord, just :type
metadata
I just noticed a possible bug in the FnCache implementation from clojure.core.cache. Instead of creating a BasicCache, shouldn't miss, evict and seed create an FnCache? see https://github.com/clojure/core.cache/blob/ee699021b984df182359648312042b79d05cc506/src/main/clojure/clojure/core/cache.clj#L148
Yep, @seancorfield is correct in that FnCache still needs sematics. I have some notes in a notebooks somewhere about this but haven't moved them to a ticket. That said, a FnCache should come out of miss/evict/seed
@seancorfield if you agree, I'll open a jira
FnCache
has never been implemented -- there's an open Jira about what its semantics are meant to be.
ah ok
There are a few open Jiras about incomplete semantics in the original lib as I inherited it...
Maybe @fogus can speak to FnCache
?