Thanks for the response! Here's a repro:
~ $ clj
Clojure 1.10.2
user=> (throw (ex-info "Improved error message" {} (ex-info "Original error message" {})))
Execution error (ExceptionInfo) at user/eval1 (REPL:1).
Original error message
I want to see Improved error message
because in my domain, the Original error message
that comes from a third-party is overly concise
I understand why Original error message
is being show first - it's technically its root cause. But perhaps there's some way to hack things here (without losing the original exception either)?the error triage stuff will always prefer the root cause so you would need to replace rather than wrap in the throw
@alexmiller The page at https://insideclojure.org/2015/01/02/sequences/ says:
> (rest coll)
- returns a logical collection of the rest of elements (not necessarily a seq). Never returns nil.
However, the docstring of rest
says:
> Returns a possibly empty seq of the items after the first. Calls seq on its argument.
Is that "not necessarily a seq" part of the quote from the article outdated?
It used to be the case that empty seqs didn't exist, but it has not been the case in a long time
I believe Alex is trying to construct (or reconstruct) a taxonomy of concepts that most people just don't much attention to there
I think he is drawing a distinction between a sequence and a seq
The next section goes into that
Hum, ya it mixes weird with the (seq [1 2]) idiom though
Which made sense that you either have a seq with elements in it, or nil
But gets confusing when a seq can be empty
yes it used to be the case that (if s ... ...)
was safe, but now(for years, the change was pretty close to clojure 1.0) you need (if (seq s) ... ...)
I guess in the taxonomy, what is the difference between s and what seq returns?
I'm trying to see if like there's a new word for a seq that can't be empty?
looking at it again, I think he is distinguishing between a seq
and a lazy-seq
(not a seq and a sequence), a seq is never empty (it is a seq of something or nil), a lazy-seq can be empty, and both of those are a sequence
but that doesn't help to make sense of the comment about (rest coll)
and like the type hint on rest says it returns an ISeq, the method call it bottoms out on (ISeq.more) says it returns an ISeq
As it sounds pretty industrial, I feel there is most likely a Java framework or library for it. I'd look for that as well.
I don't have a repl right now, but can't a seq not be empty? So I think it can return an empty list or an empty lazySeq as well
So I don't think it be outdated
Though I guess Empty list is a seq, so I don't know
It's just maybe a confusing kind of wording on either side
how does one purge specs from the registry (all of them is fine) while doing a spec refactoring?
the registry is just an atom holding a map, so you can dump the atom
(reset! @#'clojure.spec.alpha/registry-ref {})
something like that
do note that spec itself installs at least one internal spec in the registry that is essential to the operation of keys* though so doing that may actually break keys* specs
I see, thanks @alexmiller
you could do a more targeted update, or the mechanism provided by s/def is that registering a nil
spec will remove
I have a loop that creates a bunch of futures that write to a set of files, which of course can create race conditions, and I am trying to figure out what would be the best way to solve this. I feel like the solution would be something like having an agent, and having the futures reset the agent with a structure like {:file-name "...", :data ...}
followed by a watcher that automatically writes data
to file-name
, does that sound reasonable?
> too many files to have one agent per file name > have the futures reset an agent...
I think we disagree about what agents are about? they are lightweight, in clojure terms at least, you can have a lot of them. and you send them actions - you wouldn't reset their file, you would close over the file handle and send them a function that makes them write. serializing writes is exactly the sort of thing agents are good at (as long as you aren't too worried about waiting for writes to finish before you move forward)
a quick example:
(require '[<http://clojure.java.io|clojure.java.io> :as io])
(defn writing-agent
[fname]
(let [handle (io/file fname)
writer (io/writer handle)]
(agent
{:fresh (.createNewFile handle)
:handle handle
:writer writer
:written 0})))
(defn append-to
[the-agent the-string]
(send the-agent
(fn [{:keys [writer] :as m}]
(.write writer the-string)
(.flush writer)
(update m :written + (count the-string)))))
(defn close
[the-agent]
(send the-agent
(fn [{:keys [writer] :as m}]
(.close writer)
(assoc m :handle nil :writer nil))))
(cmd)user=> (.exists (io/file "foo.out"))
false
(cmd)user=> (def a (writing-agent "foo.out"))
#'user/a
(ins)user=> (pprint @a)
{:fresh false,
:handle #object[java.io.File 0xff6077 "foo.out"],
:writer
#object[java.io.BufferedWriter 0x1280851e "java.io.BufferedWriter@1280851e"],
:written 0}
nil
(ins)user=> (append-to a "hello")
#object[clojure.lang.Agent 0x12a160c2 {:status :ready, :val {:fresh false, :handle #object[java.io.File 0xff6077 "foo.out"], :writer #object[java.io.BufferedWriter 0x1280851e "java.io.BufferedWriter@1280851e"], :written 5}}]
(cmd)user=> (.exists (io/file "foo.out"))
true
(cmd)user=> (slurp "foo.out")
"hello"
(ins)user=> (append-to a ", world")
#object[clojure.lang.Agent 0x12a160c2 {:status :ready, :val {:fresh false, :handle #object[java.io.File 0xff6077 "foo.out"], :writer #object[java.io.BufferedWriter 0x1280851e "java.io.BufferedWriter@1280851e"], :written 5}}]
(cmd)user=> (slurp "foo.out")
"hello, world"
(ins)user=> (close a)
#object[clojure.lang.Agent 0x12a160c2 {:status :ready, :val {:fresh false, :handle #object[java.io.File 0xff6077 "foo.out"], :writer #object[java.io.BufferedWriter 0x1280851e "java.io.BufferedWriter@1280851e"], :written 12}}]
(ins)user=> (pprint @a)
{:fresh false, :handle nil, :writer nil, :written 12}
nil
I saw some weird behavior with .createNewFile
btw- the docs say it creates a new fresh empty file only if it doesn't exist yet, but in my experiments it seems to be truncating the existing file before any writes
or maybe the writer I create is doing that - I should decouple for testing
i think you need {:append true}
as options to the writer
I see, ok I think I'll try to use something like that. So far I have one agent that has a watcher that calls
(defn gz-write-line
"Append data to gzipped target"
[target content]
(with-open [w (-> target
(<http://clojure.java.io/output-stream|clojure.java.io/output-stream> :append true)
java.util.zip.GZIPOutputStream.
<http://clojure.java.io/writer)]|clojure.java.io/writer)]>
(binding [*out* w]
(println content))))
and my different futures are writing stuff like {:target "foo.txt.gz", :data "some data"}
with (send my-agent (fn [old] {:target path :data (str/join "\n" data)}))
which admittedly sounds a bit wrong. That being said I am ok with opening and closing the file every time (if only because I might need to access these files at any point, read only)is the race condition because of concurrent writes to the same file-name
? or something else?
well, the futures might write to the same file yes
it's determined as they are executed
I think your solution is reasonable yes. The overall model you seem to be following is "parallel execution, serialized writes" which can be fine. You can have either a single agent for all writes, or have one agent per filename (using some kind of pooling mechanism)
One delicate part though is why concurrent computation can write to the same file. It seems plausible that if futures f1 anf f2 are running in parallel, f1's writes will make f2's work useless (depending on the domain it may or not matter)
Thank you (I think I have too many files to have one agent per file name, plus the file names vary)
> Thank you (I think I have too many files to have one agent per file name, plus the file names vary) agents are lightweight though, they are just objects (not threads). Their threadpool is decoupled from the agents themselves
not sure how many files you have and how big they are, but perhaps you are in the realm where you can keep all of the changes in memory and then once all of the changes are done, update the files on disk? Ie, mutate a data structure haphazardly, then flush that. also allows you to do some cleanup on the datastructure: reordering changes, omitting changes where another change clobbers it, choosing which change from clobbering you actuallly want, etc