clojure

New to Clojure? Try the #beginners channel. Official docs: https://clojure.org/ Searchable message archives: https://clojurians-log.clojureverse.org/
vemv 2021-02-14T04:00:46.117Z

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)?

alexmiller 2021-02-14T06:04:03.117700Z

the error triage stuff will always prefer the root cause so you would need to replace rather than wrap in the throw

p-himik 2021-02-14T06:48:10.119400Z

@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?

2021-02-14T08:14:15.120700Z

It used to be the case that empty seqs didn't exist, but it has not been the case in a long time

2021-02-14T08:19:07.120900Z

I believe Alex is trying to construct (or reconstruct) a taxonomy of concepts that most people just don't much attention to there

👍 1
2021-02-14T08:20:29.121100Z

I think he is drawing a distinction between a sequence and a seq

2021-02-14T08:21:41.121300Z

The next section goes into that

2021-02-15T17:47:49.137900Z

Hum, ya it mixes weird with the (seq [1 2]) idiom though

2021-02-15T17:48:06.138100Z

Which made sense that you either have a seq with elements in it, or nil

2021-02-15T17:48:21.138300Z

But gets confusing when a seq can be empty

2021-02-15T17:49:49.138500Z

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) ... ...)

2021-02-15T17:53:30.138700Z

I guess in the taxonomy, what is the difference between s and what seq returns?

2021-02-15T17:55:55.138900Z

I'm trying to see if like there's a new word for a seq that can't be empty?

2021-02-15T18:11:43.139100Z

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

2021-02-15T18:14:14.139300Z

but that doesn't help to make sense of the comment about (rest coll)

2021-02-15T18:15:25.139500Z

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

2021-02-14T06:48:56.119700Z

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.

2021-02-14T07:10:46.119900Z

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

2021-02-14T07:11:04.120100Z

So I don't think it be outdated

2021-02-14T07:14:40.120300Z

Though I guess Empty list is a seq, so I don't know

2021-02-14T07:14:54.120500Z

It's just maybe a confusing kind of wording on either side

william 2021-02-14T14:16:51.122900Z

how does one purge specs from the registry (all of them is fine) while doing a spec refactoring?

alexmiller 2021-02-14T14:23:28.123300Z

the registry is just an atom holding a map, so you can dump the atom

alexmiller 2021-02-14T14:24:35.123900Z

(reset! @#'clojure.spec.alpha/registry-ref {})

alexmiller 2021-02-14T14:24:39.124100Z

something like that

alexmiller 2021-02-14T14:28:17.125200Z

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

william 2021-02-14T14:30:42.125500Z

I see, thanks @alexmiller

alexmiller 2021-02-14T15:13:30.126300Z

you could do a more targeted update, or the mechanism provided by s/def is that registering a nil spec will remove

Nazral 2021-02-14T15:50:52.131900Z

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?

2021-02-15T16:15:36.136300Z

> too many files to have one agent per file name > have the futures reset an agent...

2021-02-15T16:17:24.136500Z

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)

➕ 1
2021-02-15T16:40:01.136800Z

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=&gt; (.exists (io/file "foo.out"))
false
(cmd)user=&gt; (def a (writing-agent "foo.out"))
#'user/a
(ins)user=&gt; (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=&gt; (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=&gt; (.exists (io/file "foo.out"))
true
(cmd)user=&gt; (slurp "foo.out")
"hello"
(ins)user=&gt; (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=&gt; (slurp "foo.out")
"hello, world"
(ins)user=&gt; (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=&gt; (pprint @a)
{:fresh false, :handle nil, :writer nil, :written 12}
nil

2021-02-15T16:46:25.137Z

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

2021-02-15T16:46:44.137200Z

or maybe the writer I create is doing that - I should decouple for testing

dpsutton 2021-02-15T16:51:08.137400Z

i think you need {:append true} as options to the writer

Nazral 2021-02-16T04:04:49.183800Z

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 (-&gt; 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)

vemv 2021-02-14T16:22:16.132Z

is the race condition because of concurrent writes to the same file-name? or something else?

Nazral 2021-02-14T16:26:46.132200Z

well, the futures might write to the same file yes

Nazral 2021-02-14T16:26:53.132400Z

it's determined as they are executed

vemv 2021-02-14T16:29:51.132600Z

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)

vemv 2021-02-14T16:32:18.132800Z

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)

Nazral 2021-02-14T16:32:26.133Z

Thank you (I think I have too many files to have one agent per file name, plus the file names vary)

vemv 2021-02-14T16:33:29.133200Z

> 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

dpsutton 2021-02-14T18:18:16.134700Z

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