Fork me on GitHub
#clojure
<
2021-03-05
>
Max04:03:08

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

Max04:03:00

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

phronmophobic04:03:07

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

hiredman05:03:44

Logically try pushes an exception handler onto a stack(popped after the try completes), so recurring across it grows that stack

hiredman05:03:35

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

😁 1
emccue05:03:45

curious if that is a hard restriction or not

1
emccue05:03:33

like, in my head i feel like there is a way to rewrite the try to pop off that exception before recurring

emccue05:03:51

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;
  }
}

hiredman05:03:56

but does that make sense?

emccue05:03:15

(don't know yet, i'm noodling)

hiredman05:03:25

(it doesn't)

hiredman05:03:45

if recur is the supposed to act like a tail call in a language with tco

emccue05:03:39

(loop [x "abc10"]
  (try (Integer/parseInt x)
    (catch NumberFormatException e
      (recur (.substring x 1 (count x))))))

hiredman05:03:13

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

hiredman05:03:44

a catch technically could be a tail, if there is no finally

emccue05:03:13

Object x = "abc10";
Object out;
while (true) {
  try {
    out = Integer.parseInt((String) x);
    break;
  }
  catch (NumberFormatException e) {
    x = x.substring(x, x.length());
    continue;
  }
}

emccue05:03:22

hmm yeah...

emccue05:03:44

unless you put the "finally stack" on the heap

hiredman05:03:01

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

hiredman05:03:12

and you can you know, jump around

hiredman05:03:34

but logically, in the expression based high level language we actually program in it doesn't make sense

hiredman05:03:13

(finallys don't actually exist at the bytecode level, the java compiler and the clojure compiler just simulate them using exception handlers)

hiredman05:03:57

recur is not a goto jump, it is intended work like tco would if we had general tco

emccue05:03:22

I have a dusty book on jvm bytecode that I should maybe get around too i guess

hiredman05:03:58

moving where the stack is kept in memory doesn't change that fact that the stack is growing

hiredman05:03:16

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

hiredman05:03:58

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

hiredman05:03:36

(a sychronized block is a try/finally in disguise)

hiredman05:03:33

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)

phronmophobic05:03:04

I always find this stuff interesting even if I probably don't understand everything.

hiredman06:03:28

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)

hiredman06:03:32

cps style makes tail calls extremely clear, a tail call is when a function calls another function with the same continuation

hiredman07:03:29

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

hiredman07:03:06

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

hiredman07:03:27

http://matt.might.net/articles/oo-cesk/ matt might has a lot of great posts covering this kind of thing

Max20:03:32

So what I’m hearing is that we’ll have a good solution when project loom comes out 👀 troll

borkdude13:03:40

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

borkdude13:03:20

I guess the logic is in clojure.lang.Numbers somewhere

cassiel13:03:09

Additional data point: user=> (let [f /] (f 1 0.0)) Execution error (ArithmeticException) at user/eval140 (REPL:1). Divide by zero

borkdude13:03:37

Yeah, it's definitely related to inlining

nooga14:03:01

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

borkdude14:03:31

Maybe a case of Hyrum's law

borkdude14:03:56

As Alex suggested in the above linked issue: it might be because of type info available as either Long or Object

jjttjj17:03:16

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?

vemv17:03:54

> 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 walking 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

👍 1
vlaaad19:03:04

maybe you don't need the library, but just look at the concept — you don't even need a custom defrecord, just :type metadata

markaddleman19:03:10

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

markaddleman19:03:18

@U04V70XH6 if you agree, I'll open a jira

seancorfield20:03:03

FnCache has never been implemented -- there's an open Jira about what its semantics are meant to be.

seancorfield20:03:36

There are a few open Jiras about incomplete semantics in the original lib as I inherited it...

seancorfield20:03:10

Maybe @U050WRF8X can speak to FnCache?

fogus14:03:22

Yep, @U04V70XH6 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