How do you unbind a binding? Is that even possible? Say I want the var deref to fetch the root again, but I'm inside a (binding [] ...)
expression?
@didibus maybe there's a better way, but (alter-var-root #'*foo* identity)
works
The end of the binding context will have that pop thread bindings.
right, and popping twice, or failing to pop, will break things
Ya, but also, if you want to remove a binding which isn't the last, you can't because it's a stack, so I wouldn't think there'd be a function for it
Though it makes me curious about the lookup process, I would have expected having a map of stacks or something, now it seems you have to search the stack for the newest instance.kf the specific var
it's a stack of maps - each layer is a hash map of shadowings of the ones below it
Doesn't that mean it needs to traverse through the whole stack? Imagine:
(def ^:dynamic foo 1)
(binding [foo 2]
(binding [other :bar]
(binding [other :baz]
foo)))
And I'm guessing to find that there are no binding it also must traverse through it all:
(def ^:dynamic foo 1)
(binding [other :bla]
(binding [other :bar]
(binding [other :baz]
foo)))
that's what it seems like, yeah
And the structure would be like:
[{:other :baz} {:other :bar} {'other :bla} {'foo 1}]
That might explain why dynamic vars are slower than ThreadLocal
well vectors as stacks go in the opposite direction, but yeah
AH lol, I wasn't sure and actually typed it the other then flipped it π
I would have expected it to be:
{'foo [1 2]
'other [:bla :bar :baz]}
Good idea
That also made me think to look at the implementation, seems you can also do:
(.getRawRoot #'my-var)
nice, thanks!
alter-var-root doesn't change the binding inside a binding context though
oh - but it returns the identity, I see
it doesn't do the thing literally asked for - unbinding
think I said "or get the root binding"
Anyways, I just wanted a way to get the root value even in there is a thread binding
aha
I guess there is popThreadBinding too or something like that... not sure if that would unbind?
Except with that, you can't really choose which binding to remove it seems
that just crashes the repl if you didn't have a matching push-thread-bindings
it's low level and fragile
in fact binding is just a macro around push-thread-bindings / pop-thread-bindings
If its a stack though, there's probably no unbind function
(conj)
;=> []
(conj nil)
;=> nil
(conj nil 1)
;=> (1)
Kinda weird how inconsistent conj is here no?Knowing about 0 and 1 arities involvement in reducers/transducers, all I see is consistency, damn curse of knowledge!
How to tell you've written too much clojure? The above behaviour makes perfect sense to me
Could you comment the first one, I don't get it
why is it consistant to return an empty vector?
0 arity is often used to produce an initial value
why (conj) doesnβt produce () ? since (conj nil 1) gives (1). Also, why (conj nil) gives nil ?
1-arity is completing arity for transducers, e.g. something that does final post-processing. Usually it behaves as identity, but some transducers like partition-all
can add accumulated elements to a final result.
(conj)
returns vector instead of list because vectors are more useful and performant data structure than linked lists
why should it return ()?
(conj [] 1)
is [1]
, not (1)
after all
hello all, just a reminder that clojure 1.10.2-rc2 was released this week - would love to have you try it and report back here if things look good, or if you see any issues. changelog can be found here: https://github.com/clojure/clojure/blob/master/changes.md#changes-to-clojure-in-version-1102. If you do try it please hit the check mark emoji here!
been running 1.10.2-rc2 for the past few days on 3 backend services. all working normally π nothing weird observed π
I find it inconsistent that (conj) and (conj nil 1) don't both create a vector or a list personally
And preferably it be vector for both, since like you said, more useful
We have it running in QA (as of yesterday afternoon) and everything looks good so far. I expect we'll put it in production early next week (we're mostly on Alpha 4 in production right now, with one app running RC 1).
Running into an interesting issue with a deps.edn based project. I have often used Java's Thread/setDefaultUncaughtExceptionHandler
in Clojure projects. Its use seems to require that the caller be a first class java object
which is easily obtained through ns
and :gen-class
See https://stuartsierra.com/2015/05/27/clojure-uncaught-exceptions for how to use reify
with that function
Nice, that may make the tooling easier
Hope so
all clojure values other than nil are first class java objects - I think what you mean here is that it needs to implement a specific interface?
(Thread/setDefaultUncaughtExceptionHandler
(reify Thread$UncaughtExceptionHandler
(uncaughtException [_ thread ex]
(prn "ouch i died in a thread"))))
nil
user=> (.start (Thread. (fn [] (/ 1 0))))
nil
user=> "ouch i died in a thread"
Ya you are right Justin. I am not sure what the dependency is.
(to me the distinction would be eg. Scala where many constructs that exist as far as the language is concerned don't exist in a first class way in the vm - clojure intentionally doesn't do that)
In a clojure cli deps.edn project, I am not able to successfully register an exception handler without AOT compiling the class that invokes it
clojure -e "(compile 'indignant.core)"
java -cp $(clojure -Spath):classes indignant.core
Oops, no I am using reify
already. I even work at reify
π
(defn set-exception-handler
[]
(Thread/setDefaultUncaughtExceptionHandler
(reify Thread$UncaughtExceptionHandler
(uncaughtException [_ thread e]
(do
(timbre/errorf "top level exception handler caught exception on thread: %s" (.getName thread))
(timbre/error e)
Trying to understand what might cause that requirement
Re 0-arity. Look at (+)
or (*)
. They return a 'reasonable' initial value to be used for repeated application of the same function. You agree vector is fine.
Why does (+)
return a long, when (+ 0M)
returns a BigDecimal? Many of the 1-arity functions are essentially identity
. Note that (+ nil)
returns nil even though (+ nil 1)
will error. Ditto with (conj :foo)
vs (conj :foo 1)
So why doesn't (conj nil :x)
promote a nil
to a vector? I'm not aware of any other fns where nil ever becomes anything other than ()
. So nil => ()
makes sense, as it's consistent with all other functions in core where nil can be used in place of a collection.
Make a minimal repro, else it's hard for anyone to help you probably
indeed thanks
might find the problem myself that way
well that java -cp ..
line, since it ends in indignant.core
only works if you have created indignant.core as a class java can find
have you tried java -cp $(clojure -Spath) clojure.main -m indignant.core
?
that is the working example
I should try to make a small repro example
nils == empty seqs all over the place, unless there's a good reason to diverge. I'd say for (conj)
that if you actually need it, it's likely you want append-to-the-end as it's the common case, so a vector is used. But for (conj nil)
that's being very explicit (in my mind) that you want to append to the front.
Actually this is useful, as it is another invocation where the exception handler fails to register. It does not reference the precompiled indignant.core
class file.
Thread/setDefaultUncaughtExceptionHandler is extremely brittle
in this case my guess would be the difference you are seeing is between the way the clojure runtime loads and runs a main function with -m argument vs. how java loads and runs a main class
running a main class happens more directly, so there is very little code between you and the jvm, so any exceptions that happen bubble up and are "uncaught"
with -m clojure's error reporting, added somewhat recently, will catch exceptions and generate a report on them, usually in /tmp somewhere
The defaultuncaughtexceptionhandler is something you can set, for just in case, but you can never assume it will ever get called
It is the finallizer of exception handling
A good read: Clojure: the Lisp that wants to spread *"*Clojure is (slowly) eating the world" https://simongray.github.io/essays/spread.html
Ok I'll add a minimal repro in this thread for the issue that I'd like to understand: successful use of Thread/setDefaultUncaughtExceptionHandler
seems to require AOT compilation
@waffletower see my explanation above
it isn't that using Thread/setDefaultUncaughtExceptionHandler requires aot, it is that when you run via an aot'ed -main method invoked via the jvm vs running via clojure's namespace loading mechanism
won't register the handler
clj -m uncaught.core
in those cases your code is being run in different contexts
does register the handler
mkdir classes
clj -e "(compile 'uncaught.core)"
java -cp $(clojure -Spath):classes uncaught.core
they both register the handler
in the clj -m uncaught.core
the exception is caught
not in my case
it is
% clj -m uncaught.core
WARNING: When invoking clojure.main, use -M
Execution error (ArithmeticException) at uncaught.core/-main (core.clj:17).
Divide by zero
Full report at:
/var/folders/g5/1rcmn5_n4sn2scczzjtp8lyc0000gp/T/clojure-3856576569065612249.edn
% java -cp $(clojure -Spath):classes uncaught.core
exception handler caught exception on thread: main
#error {
:cause Divide by zero
:via
[{:type java.lang.ArithmeticException
:message Divide by zero
:at [clojure.lang.Numbers divide Numbers.java 188]}]
:trace
[[clojure.lang.Numbers divide Numbers.java 188]
[clojure.lang.Numbers divide Numbers.java 3901]
[uncaught.core$_main invokeStatic core.clj 17]
[uncaught.core$_main doInvoke core.clj 14]
[clojure.lang.RestFn invoke RestFn.java 397]
[clojure.lang.AFn applyToHelper AFn.java 152]
[clojure.lang.RestFn applyTo RestFn.java 132]
[uncaught.core main nil -1]]}
https://github.com/clojure/clojure/blob/master/src/clj/clojure/main.clj#L663-L667 is the code that clj -m ultimately invokes
you can see it runs you code wrapped in a try/catch with reporting stuff in the catch
again, if you read what I said previously in the channel, I explain this
I see what you are trying to say now. It gets caught in clojure main before it hits the handler
and in general, any place where you don't control the execution context of your code (callbacks, etc) might defensively wrap the execution of your code in a try/catch which will stop your exception from bubbling up
For the future, #news-and-articles is a better place to post links to blog posts etc.
so, to quote myself Thread/setDefaultUncaughtExceptionHandler is extremely brittle
I disagree, and that is probably why I didn't read what you said after very carefully.
But you have helped me to look to clojure.main and how it wraps the code execution
using a jar invocation will allow this brittleness you speak of to disappear.
nope
Ok, so if I recap: (conj nil 1) returns an empty seq, because nil and empty seq are kinda treated the same sometimes. So conjoining on nil is like asking to conjoin on empty seq. And then (conj nil) returns nil because conj is a transducer and the 1-ary for transducer for conj has to be identity. But why does (conj) return vector? It's an okay default, but so would have been return (), and that would be more consistent it seems. Are there any reason for vector instead? Is it also due to transducers? Or is there a common idiom which make this a better return?
it solves your specific case, but as I said, anytime you hand off your code to be run by someone else (callbacks, threadpools, etc) it is susceptible to this kind of thing
thank you
even if you aot compile in other cases you will still have this issue, and even with aot compiling you will still have this issue if you loading the aot compiled code via clojure.main instead of directly invoking its -main via java's main entry point stuff
The crucial point is that any try-catch (or thread-group specific exception handlers) outside of your control can stop exceptions from reaching your default exception handler.
if all you care about is the top level, you might want to look at https://docs.oracle.com/javase/7/docs/api/java/lang/Runtime.html#addShutdownHook(java.lang.Thread) instead, but that also has provisos and limitations
agreed. Being fairly new to clj
deployments without jars I did not realize that the main thread was outside of my control given the implicit clojure.main try/catch. You folks helped me see that. Thanks!
it didn't use to, until early 2019 that try/catch wasn't there and the exception would bubble up
but beginners are constantly complaining about stacktraces being ugly, and there is a lot of hand wringing in some corners about beginners, so at some point a lot of work was done to try and make error reporting friendlier, and that is how that try/catch and the writing of the full stacktrace to a file in /tmp got introduced
What's your problem exactly?
I would put an entry on http://ask.clojure.org about this personally. It could be considered a regression is some way.
At the very least, its a breaking change
You could argue it should re-throw, and not run System/exit 1
Why is ISeq
not implemented for PersistentVector
?
Or you could argue that gen-class main https://github.com/clojure/clojure/blob/master/src/clj/clojure/genclass.clj#L448 should do a similar bootstrapping to clojure.main
I didn't experience it as a breaking change, personally, as I have until now universally used jar deployments. The difference in behavior was confounding at first, and now seeing the enclosing try/catch makes perfect sense.
because a vector isn't a seq
a seq is a basically a kind of iterator, so a similar question to consider is "why does a java.util.ArrayList return an iterator when you call the iterator method on it, instead of implementing instead of implementing java.util.Iterator directly"
PersistentQueue
isn't a seq either, but it implements ISeq
:thinking-face:
that is a mistake in the type hierarchy that can never be fixed π
I still think it would be worthwhile to bring up. There have been changes in the past around consistency of the various ways Clojure bootstraps an app.
Personally, if possible, I don't see why you wouldn't want each mechanism to allow for the same basic assumptions
actually, that is not true
user=> (doseq [i (supers clojure.lang.PersistentQueue)] (prn i))
clojure.lang.IPersistentCollection
java.io.Serializable
clojure.lang.Counted
clojure.lang.IMeta
clojure.lang.Obj
clojure.lang.IHashEq
clojure.lang.Seqable
clojure.lang.IPersistentList
java.lang.Iterable
java.util.Collection
clojure.lang.IObj
java.lang.Object
clojure.lang.IPersistentStack
clojure.lang.Sequential
nil
user=>
no ISeq
no mistake
If you look at the gen-class main, its so different to the clojure.main
It doesn't bind any dynamic var, it doesn't catch top level errors and report them, etc.
user=> (instance? clojure.lang.ISeq clojure.lang.PersistentQueue/EMPTY)
false
user=>
(conj)
with zero arity was added the same time as transducers but it's not a transducer itself. But given that context:
I think it's about expectations; what would you expect (transduce (map inc) conj '(1 2 3))
to return? I'd expect some sequence of 2 3 4
(i.e. the same ordering, so a vec is the only choice no-arity).
If you do (transduce (map inc) conj () '(1 2 3))
(or with a nil
) you'll get 4 3 2
But π€· I'm just guessing. It seems all fairly reasonable to me when I come to writing code.
I don't know, seems weird that an application would run with -main from clojure.main but not genclass -main
Hm, weirdly there seems to be a divergence between Clojure and ClojureScript here. In CLJS, PersistentVector
does implement ISeq: https://github.com/clojure/clojurescript/blob/946348da8eb705da23f465be29246d4f8b73d45f/src/main/cljs/cljs/core.cljs#L6295
as I mention every time this comes up, there are a ton of differences between cljs and clj
everywhere
it is all different, you cannot assume they are the same anywhere
Reminds me of: "The United States and Great Britain are two countries separated by a common language." https://en.wikiquote.org/wiki/English_language#:~:text=The%20United%20States%20and%20Great,Reader's%20Digest%20(November%201942).&text=Variant%3A%20The%20English%20and%20the,divided%20by%20a%20common%20language.
I'm curious how the new -X exec-fn setups the context
If that's also different from clojure.main and genclass main
> Are there any reason for vector instead?
I think this is the wrong question to ask. You provide examples with nil: (conj) (conj nil) (conj nil 1)
. Imagine the reverse situation if we had (conj)
return ()
, and you thought about these examples:
(conj) => ()
(conj []) => []
(conj [] 1) => [1]
It would be fair to ask why not []
?As a Brit married to an American, I can confirm that! Even after 21 years of marriage we both still manage to say things that has the other one going "Huh?"
But my wife has picked up some Cockney rhyming slang over the years π and when she leaves me "honey do" notes, she just signs them T.
T is for Trouble. Trouble and strife = wife.
or why not #{}
since conj preverses the type of coll on set as well: (conj #{}) => #{}, (conj #{} 1) => #{1}
In ClojureScript ISeq is a protocol, not an interface, that's why I believe
With protocols you don't have hierarchies, they are more like traits, so its fine to have PersistentVector extend the protocol to ISeq, but with interfaces it would cause weird type casting issues
zero-arity conj is for creating a default value for use in transducing/reducing context, and vector is a good default in this case.
On the jvm types are very restrictive, so the type hierarchy and how you arrange it has real consequences, and the relationships between the types tends to become part of the api. JS virtual machines are very different when it comes to types. So my guess is the hierarchy and relationships are carefully thought out in clojure, and in cljs it is just whatever is faster and mostly seems to be correctly
PersistentVector is not an instance of ISeq in ClojureScript either. It just extends the protocol
(instance? ISeq [])
;;=> false
(instance? ISeq #queue [])
;;=> false
My guess is nothing is actually an instance of ISeq, because its a protocol, nothing inherits from its type
Maybe more interesting is to look at seq?
in Cljs
(seq? []) ; false
(seq? #queue []) ; true
(seq? '()) ; true
Huh. Why does the #queue
literal exist in CLJS but not in CLJ?
What benefits do structs have over maps, if you can't use defrecord/deftype? I did a small test but accessing a key from a struct doesn't seem to be any faster than from a map - is that right?
user=> (defstruct Foo :foo :bar)
#'user/Foo
user=> (def s (struct Foo 1 2))
#'user/s
user=> (time (let [s s] (dotimes [i 1000000000] (:foo s))))
"Elapsed time: 7141.330632 msecs"
nil
user=> (def s {:foo 1})
#'user/s
user=> (time (let [s s] (dotimes [i 1000000000] (:foo s))))
"Elapsed time: 6302.803225 msecs"
nil
Are structs even used nowadays? I might be remembering it wrong, but they predate records
Yes, they are replaced by deftype and defrecord, but I was considering using them for implementing something which can only be known dynamically
E.g. they are still used in some CSV lib and I might have a similar use case for it.
But if there is no significant win, I'm not sure why I should even bother
IIRC structs have the keys in a predictable order -- so using them for CSV stuff sort of makes sense
ah, using a bigger example gives the expected result.
user=> (def ks [:a :b :c :d :e :f :g :h :i :j :k :l :m :n :o :p])
#'user/ks
user=> (def Foo (apply create-struct Foo ks))
#'user/Foo
user=> (def s (struct Foo 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6))
#'user/s
user=> (time (let [s s] (dotimes [i 1000000000] (:a s))))
"Elapsed time: 10425.754936 msecs"
nil
user=> (def s (zipmap ks (range)))
#'user/s
user=> (time (let [s s] (dotimes [i 1000000000] (:a s))))
"Elapsed time: 14030.239471 msecs"
nil
since smaller maps are backed by arraysyou could use functions as tuples
you know, maybe the worst of all three options
@hiredman eh, say what?
I mean: I didn't understand where you are going with this, but I'm curious ;)
user=> (def ks [:a :b :c :d :e])
#'user/ks
user=> (def t (let [[a b c d e] ks] (fn [f] (f a b c d e))))
#'user/t
user=> (time (let [t t] (dotimes [i 1000000000] (t (fn [a & _] a)))))
"Elapsed time: 11304.311272 msecs"
nil
user=>
scott encoding https://en.wikipedia.org/wiki/Scott_encoding
I was thinking about it because closed over values become fields in fns, which seems like it might be fast to access, but there are other issues with it that make it not great
1. it is not great because it is indexed access instead of by name 2. to do it really generically you have to use apply, which of course is not fast 3. in all likely hood you'll end up closing over a collection of values instead of individual values, so you get an extra hop
https://crypto.stanford.edu/~blynn/compiler/scott.html is a neat series building a mini haskell compiler that compiles down to lambda calculus (all data types are functions) and then from lambdas to ski combinators
thanks for sharing :)