Fork me on GitHub
Bill Phillips02:04:59

Should I be using spec鈥檚 s/valid? or s/assert in my pre/post conditions?

Bill Phillips02:04:13

s/assert seems more useful, but maybe I鈥檓 doing it wrong


@jings.bill :pre/`:post` conditions -- makes more sense to me to use a predicate (`s/valid?`). I personally don't like :pre/`:post` conditions and I don't much like assertions. If I want argument checking in dev/test, I'd rather use s/fdef and instrumentation. When I want to check data for validity -- and handle that as part of my design -- I'll use s/valid? (or s/conform) in production code inside the function.

馃帀 4
Bill Phillips04:04:36

Ahh. I had no idea that was what fdef did.

Jim Newton09:04:23

Good saturday morning everyone. I'm trying to start refactoring a file, for the first time. I want to have some core functions, some testing infrastructure functions, some functions which use the core and testing infrastructure, and also some utility functions. Currently all functions are defined in the same core file, and I want to distribute the functions to the appropriate other files. As I understand the tooling for closure requires that I decare one namespace per file, whereas in Common Lisp I'd declare one namespace and have several files just use that name space with the equivalent of in-ns

Jim Newton09:04:21

The structure should be core requires util, util requires nothing, tester requires nothing, rte-tester requires tester and core.

Jim Newton09:04:56

When I create a file named rte_tester.clj which declare no function but begins with

(ns  clojure-rte.rte-tester
  (:require [clojure-rte.tester :refer :all]
            [clojure-rte.core :refer :all]
loading it causes the following error.
Syntax error (IllegalStateException) compiling at (rte_tester.clj:1:1).
random-test already refers to: #'clojure-rte.core/random-test in namespace: clojure-rte.rte-tester
What does this mean?

Jim Newton09:04:29

I already restarted cider a couple of times, and that doesn't help.


It is certainly common to have a one-to-one correspondence between file and Clojure namespaces, and some tools do expect that. It isn't a requirement of the language implementation itself, though. I wouldn't veer away from that unless you were curious whether the tooling you are using handles it, and/or really really prefer otherwise.

Jim Newton09:04:26

@andy.fingerhut yes, i was trying to embrace the standard advice and do just that. But having trouble understanding how to do it. Is there something wrong with that (ns ...) expression at the top of an otherwise empty file?


I would recommend leaving out (:gen-class) unless you know for a fact that you absolutely need it for some reason.

Jim Newton09:04:13

OK, I don't even know what :gen-class does, lein just put it in the original file and I copied it.

Jim Newton09:04:16

What does it mean?


It directs the Clojure compiler to create a Java class file with some particular Clojure-specific conventions for some things. It isn't typically needed. I can't answer in any great detail because I have so rarely encountered it.

Jim Newton09:04:07

I thought it was probably related to making a standalone application, unix level application. Because lein also defined a -main function


Do you define a function random-test in any of those namespaces?

Jim Newton09:04:25

Even if I take out the :gen-class

Jim Newton09:04:42

I define random-test only in the core. and I'm NOT redefining it in this file.


Clojure on Java will look for compiled JVM byte code in files whose names end in .class on the JVM class path, and use those if they exist, and I think there is another condition like their time stamp must be more recent than a corresponding file with suffix .clj or .cljc . Depending on the tooling you are using, there may be old .class files around created from before the time you moved functions from one namespace to another.


Deleting all of those and starting a fresh JVM could help in that case


I believe lein clean may do that, but you can also use Unix-y commands like find to check if you want to be certain

Jim Newton09:04:55

Here are the three files.

Jim Newton09:04:13

Can you take a look at the files and see if there's something obvious wrong.

Jim Newton09:04:45

I tried lein clean, and then restart-cider, but same problem occurs.


Are all of those files in a directory named clojure_rte , and that directory is inside of a directory named src in the root of your project?


When I put those files into a directory src/clojure_rte of a project template that I created using lein new clojure_rte , and then run the command lein repl , I do not see the error you showed above, but I do see other errors, e.g. warnings about vars with names having asterisks around them, but not declared dynamic (that is only a warning), but also an error about unable to resolve traverse-pattern


I did not use CIDER to get that result, just lein repl command in a terminal, in project root dir


It does appear that there is a call in your core.clj source file to traverse-pattern before it is defined.

Jim Newton09:04:30

OK, it sounds like I'm on the right track, there must be something cached somewhere.


I am not claiming that is the root cause of the behavior you are seeing, which sounds like something else. I don't use CIDER, so am not a good person to try to reproduce that aspect of it.

Jim Newton09:04:07

called before defined, yes that's something I need to work through,

Jim Newton09:04:32

it was one reason to separate into seperate files. to get a better handle on what requires what.


sure, there are typically a flurry of errors while you reorganize things

Jim Newton09:04:00

it would be cool if the clojure compiler would internally perform a topological sort of the top level forms before compiling the file.

Jim Newton09:04:54

how did you fine the used before defined? was that a warning by the loader. I don't see it.


You can use (declare traverse-pattern) early in a file if you like the order things exist in, and such a thing is necessary for mutually recursive top level functions.

Jim Newton09:04:50

this one is not mutually recursive, I'll re-order it. But how did you find it?


I saw an error message about traverse-pattern in the output of the lein repl command I typed with a fresh lein new clojure_rte template, plus your 3 files copied into src/clojure_rte directory.

Jim Newton09:04:59

I don't see any warning.

Jim Newton09:04:35

ahh, it's something which lein found, not something that load/compile found.


Typing lein repl from terminal, in project root, starts a JVM process, and with the project.clj file created in the template, there is this line: :repl-options {:init-ns clojure-rte.core} and I believe that directs Leiningen to try to load that namespace when starting a REPL


Here is the output I see when typing lein repl command:


$ lein repl
Warning: *rte-hash* not declared dynamic and thus is not dynamically rebindable, but its name suggests otherwise. Please either indicate ^:dynamic *rte-hash* or change the name. (clojure_rte/core.clj:47)
Warning: *traversal-functions* not declared dynamic and thus is not dynamically rebindable, but its name suggests otherwise. Please either indicate ^:dynamic *traversal-functions* or change the name. (clojure_rte/core.clj:48)
#error {
 :cause Unable to resolve symbol: traverse-pattern in this context
 [{:type clojure.lang.Compiler$CompilerException
   :message Syntax error compiling at (clojure_rte/core.clj:50:14).
   :data #:clojure.error{:phase :compile-syntax-check, :line 50, :column 14, :source clojure_rte/core.clj}
   :at [clojure.lang.Compiler analyze 6808]}
  {:type java.lang.RuntimeException
   :message Unable to resolve symbol: traverse-pattern in this context
   :at [clojure.lang.Util runtimeException 221]}]
 [[clojure.lang.Util runtimeException 221]
  [clojure.lang.Compiler resolveIn 7414]
  [clojure.lang.Compiler resolve 7358]
  [clojure.lang.Compiler analyzeSymbol 7319]
  [clojure.lang.Compiler analyze 6768]
  [clojure.lang.Compiler analyze 6745]
  [clojure.lang.Compiler$InvokeExpr parse 3820]
  [clojure.lang.Compiler analyzeSeq 7109]
  [clojure.lang.Compiler analyze 6789]
  [clojure.lang.Compiler analyze 6745]
  [clojure.lang.Compiler$BodyExpr$Parser parse 6120]
  [clojure.lang.Compiler$FnMethod parse 5467]
  [clojure.lang.Compiler$FnExpr parse 4029]
  [clojure.lang.Compiler analyzeSeq 7105]
  [clojure.lang.Compiler analyze 6789]
  [clojure.lang.Compiler analyzeSeq 7095]
  [clojure.lang.Compiler analyze 6789]
  [clojure.lang.Compiler analyze 6745]
  [clojure.lang.Compiler$MapExpr parse 3104]
  [clojure.lang.Compiler analyze 6797]
  [clojure.lang.Compiler access$300 38]
  [clojure.lang.Compiler$DefExpr$Parser parse 596]
  [clojure.lang.Compiler analyzeSeq 7107]
  [clojure.lang.Compiler analyze 6789]
  [clojure.lang.Compiler analyze 6745]
  [clojure.lang.Compiler eval 7181]
  [clojure.lang.Compiler load 7636]
  [clojure.lang.RT loadResourceScript 381]
  [clojure.lang.RT loadResourceScript 372]
  [clojure.lang.RT load 459]
  [clojure.lang.RT load 424]
  [clojure.core$load$fn__6839 invoke core.clj 6126]
  [clojure.core$load invokeStatic core.clj 6125]
  [clojure.core$load doInvoke core.clj 6109]
  [clojure.lang.RestFn invoke 408]
  [clojure.core$load_one invokeStatic core.clj 5908]
  [clojure.core$load_one invoke core.clj 5903]
  [clojure.core$load_lib$fn__6780 invoke core.clj 5948]
  [clojure.core$load_lib invokeStatic core.clj 5947]
  [clojure.core$load_lib doInvoke core.clj 5928]
  [clojure.lang.RestFn applyTo 142]
  [clojure.core$apply invokeStatic core.clj 667]
  [clojure.core$load_libs invokeStatic core.clj 5985]
  [clojure.core$load_libs doInvoke core.clj 5969]
  [clojure.lang.RestFn applyTo 137]
  [clojure.core$apply invokeStatic core.clj 667]
  [clojure.core$require invokeStatic core.clj 6007]
  [clojure.core$require doInvoke core.clj 6007]
  [clojure.lang.RestFn invoke 408]
  [user$eval5 invokeStatic form-init5776825426719157634.clj 1]
  [user$eval5 invoke form-init5776825426719157634.clj 1]
  [clojure.lang.Compiler eval 7177]
  [clojure.lang.Compiler eval 7166]
  [clojure.lang.Compiler eval 7166]
  [clojure.lang.Compiler load 7636]
  [clojure.lang.Compiler loadFile 7574]
  [clojure.main$load_script invokeStatic main.clj 475]
  [clojure.main$init_opt invokeStatic main.clj 477]
  [clojure.main$init_opt invoke main.clj 477]
  [clojure.main$initialize invokeStatic main.clj 508]
  [clojure.main$null_opt invokeStatic main.clj 542]
  [clojure.main$null_opt invoke main.clj 539]
  [clojure.main$main invokeStatic main.clj 664]
  [clojure.main$main doInvoke main.clj 616]
  [clojure.lang.RestFn applyTo 137]
  [clojure.lang.Var applyTo 705]
  [clojure.main main 40]]}
nREPL server started on port 52150 on host - 
REPL-y 0.4.4, nREPL 0.6.0
Clojure 1.10.1
Java HotSpot(TM) 64-Bit Server VM 1.8.0_192-b12
    Docs: (doc function-name-here)
          (find-doc "part-of-name-here")
  Source: (source function-name-here)
 Javadoc: (javadoc java-object-or-class-here)
    Exit: Control+D or (exit) or (quit)
 Results: Stored in vars *1, *2, *3, an exception in *e



Those warnings and error messages I am 99.9% sure are from the Clojure compiler, not something added by Leiningen

Jim Newton10:04:41

Thanks for the help with this namespace issue. When I start a new cider, and ask it to run the tests, they all pass.

Jim Newton10:04:13

however, i still get the error trying to load the "rte_test.clj" file 馃槥

Jim Newton10:04:09

maybe it's just something about cider?


If you are willing to make your entire project public, e.g. on Github, someone in #cider channel may be willing to take a look

Jim Newton09:04:35

how am I supposed to name top level variable definitions if not *something* ?


(a) You can ignore the warnings. They are harmless. (b) You can leave off the 'earmuffs'. (c) If you want dynamically bindable Vars, you can use (def ^:dynamic *something* ...)

Jim Newton10:04:38

Cool, I marked them dynamic for now. I need to figure out later whether they should be dynamic or not.


Not sure if you saw replied in a thread above about error messages I saw on my system. I put them there because they include a fairly long chunk of text


I am not sure, but I suspect with your familiarity with Common Lisp, and newness to Clojure, you are probably feeling like an American in the UK, occasionally using words you think are harmless but offend people, or vice versa 馃檪

Jim Newton10:04:20

Did I say something offensive? Sorry. 馃槥


Sorry, I did not mean you said anything that offended me, or anyone here. Only offensive to the software on your computer - I was making a bad analogy

Jim Newton10:04:13

we nerds sometimes get too excited with technical things.

Jim Newton10:04:53

I did recently accidentally find the clojure function cl-format I laughed.

Jim Newton10:04:26

i'm curious how complete it is. cl-format is a dsl unto itself.

Jim Newton10:04:38

@ahmed1hsn are you a cl-format user?

Jim Newton10:04:38

Where's a good place to pose cider related questions. I don't see a #cider channel here


Emacs channel is good too


Actually there is a #cider channel

Jim Newton13:04:25

in a (cond ...) we have a sequence of test consequent pairs, if test is true, then cond evaluates to the consequent, if not, the next test is considered ... is there a way to tell cond for a particular consequent I just want to return the value of the test?

Jim Newton13:04:25

if not, this might be my first excuse to write a clojure macro, cl-cond I see there is a with a buggy macro implementation provided. Here is my suggested solution

(defmacro cl-cond [[if1 then1] & others]
  ;; implementation from
  (when (or if1 then1 others)
    (let [extra-clauses# (if others `(cl-cond ~@others))]
      (if then1
        `(if ~if1 ~then1 ~extra-clauses#)
        `(or ~if1  ~extra-clauses#)))))

Ben Sless14:04:38

It's a bit ugly but this works: using metadata to tell the macroexpand to return the predicate

(defmacro cond*
  [& clauses]
  (when clauses
    (let [p (first clauses)
          return? (:return (meta p))]
      (if return?
        `(if ~p
           (cond* ~@(next clauses)))
        `(if ~p
           ~(if (next clauses)
             (second clauses)
             (throw (IllegalArgumentException.
                     "cond requires an even number of forms")))
           (cond* ~@(nnext clauses)))))))

 ^:return p1
  p2 c2
  p3 c3)

Jim Newton16:04:34

I found a but in my implementation.

  (a (f1) (f2) (f3)))
If a is true, his should return (f3) but should evaluate (f1) and (f2) . Here is my updated implementation in case anyone is interested.
(defmacro cl-cond
  "Like CL:cond.  Each operand of the cl-cond is a list of length at least 1.
   The same semantics as clojure cond, in that the return value is
   determined by the first test which returns non-false.  The
   important semantic difference is that an agument has 1, then the
   specified form is both the test and the return value, and it is
   evaluated at most once.
   Implementation from:
  [[if1 & then1] & others]
  (when (or if1 then1 others)
    (let [extra-clauses# (if others `(cl-cond ~@others))]
      (if then1
        `(if ~if1 (do ~@then1) ~extra-clauses#)
        `(or ~if1 ~extra-clauses#)))))

Adrian Smith13:04:21

Why does this fail: (doc (first (apropos "run"))) but this succeed? (doc clojure.core/dorun)

Jim Newton13:04:33

Lots of functions in clojure take a predicate. Is there a function to logically invert a predicate? For example

(filter (logically-invert seq?) items)
this would return a sequence of those elements from items which are not sequences.

Jim Newton13:04:53

found it! its called complement


Also, the opposite of filter is remove.

Jim Newton13:04:29

thanks for the useful observation

Jim Newton13:04:02

However, there are lots of functions which take a predicate, perhaps not all come in filter/remove pairs. complement will be a useful function.


Yeah, I complement is super useful. I threw in one more option specific to filter.


You can also negate the predicate with not - what complement does internally.

Jim Newton14:04:31

No, (not seq?) is false which does not test for not-a-sequence


Did you check the above link? (complement f) is actually (not (f args)).


The equivalent will be (not (seq? x)).


These two are equivalent, 1) (filter (complement even?) [1 2 3 4 5]) and 2) (filter #(-> % even? not) [1 2 3 4 5]).

Jim Newton14:04:46

Yes, but (complement f) is not (not (f args)) ... it is rather (fn [x] (not (f x))) plus some cases for other arities.

Jim Newton14:04:08

I think that's probably what you meant.


@sfyire doc is a macro which expects an unquoted symbol, not the list (first (apropos "run")) , which the doc macro will not evaluate


doc calls a function in its implementation, which you could call from the REPL, but it isn't exactly convenient to type in its built-in form, although you could of course write your own function that makes it more convenient.


(let[n(read-string(first *command-line-args*))](print(and(> n 1)(=(reduce #(* %1(if(=(mod n %2)0)0 1))1(range 2 n))1))))
I am writing program to check if no. is prime or not. And i am getting an error: Execution error. ERROR: TypeError: cljs.user.read_string is undefined


Can anyone please help?


cljs.user=> (apropos "read")
(cljs.core/*print-readably* cljs.core/spread)
there is no clojurescript read-string. But you can use the cljs.edn namespace:
cljs.user=> (require '[clojure.edn :as edn])
cljs.user=> (dir edn)


I am not sure how to use it?


ljs.user=> (doc edn/read-string)
([s] [opts s])
  Reads one object from the string s.
   Returns nil when s is nil or empty.

   Reads data in the edn format (subset of Clojure data):

   opts is a map as per
cljs.user=> (edn/read-string "bob")
cljs.user=> (edn/read-string "{:a [1 2 3] :b {:c :d}}"
{:a [1 2 3], :b {:c :d}}
cljs.user=> (edn/read-string "{:a [1 2 3] :b {:c :d}}")
{:a [1 2 3], :b {:c :d}}
cljs.user=> (type *1)


i called doc on it. it can take either a string or options and a string. it reads a string into clojure data


Can I destructure a vector and give it a name?

位ustin f(n)16:04:39

Yes you can. Just use ':as'

位ustin f(n)16:04:40

(let [[a b c :as v] [1 2 3]] (println a b c v))

位ustin f(n)16:04:52

=> 1 2 3 [1 2 3]


Thanks! I had this idea it only works with maps. And then I got the syntax wrong when trying it. So inside the vector, of course!

位ustin f(n)16:04:53

Yeah I was happy when I stumbled upon using :as in vector destructuring my first time too!


I want to generate some random data that has one decimal point, as a number type rather than a string. If I wanted a string, I'd use format . I can use rand to generate random decimal numbers, but only want one decimal place. Wrapping with float gives too many decimal places. Math/round returns a whole number, with-precision doesn't truncate the number of decimal points... Any suggestions (I dont care about rounding errors, its mock data). Thank you.

位ustin f(n)17:04:42

Something like this?

(/ (int (* 10 (rand))) 10.0)

位ustin f(n)17:04:57

Make a number, round to int, divide by 10.0 for 1 decimal

bananadance 4

Ah, now that's a nice bit of lateral thinking. Thank you.


You can use (rand-int 10) as well instead of * 10 rounded to int


Can you generate strings and then parse into floats?


There was a very nice one-liner in the thread that suits me. I also tried DecimalFormat Java class, but its still a string and a lot of code. Had to remember how to do Java Interop for about 30 seconds 馃檪

(import java.text.DecimalFormat)

(def decimal-format 
  (DecimalFormat. "#.#"))

(.format decimal-format (rand 20))


when I'm using :keys destruction on a map, can I still get the whole structure ?


ok, I can use the :as key

metal 8

is there a way to convert a string UUID to the UUID type?


postgresql is yelling at me since the JSON is passing in a stringified uuid



馃憤 4

just came across this as well


java.util.UUID cannot be cast to clojure.lang.IFn hmm




my function looks like this


(update-in item [:seller_id] (java.util.UUID/fromString (get item "seller_id")))


when i do (println (type (get item "seller_id"))) it says the type is string tho


what you are trying to do is assoc-in , not update-in


will assoc-in dup the value though?


or maybe i do not have a full grasp of when to use update-in


yeah i get this error from psql then


org.postgresql.util.PSQLException: ERROR: column "seller_id" specified more than once| Position: 66


because you're specifying it twice, once as a string key "selled_id", and once as your new keyword key :seller_id


yeah it just adds another key of the same name


{name Burton Board, description Snowboard, price 100, quantity 1, seller_id 8e85d9c3-c800-4d03-b79c-286343ad6020, :seller_id #uuid "8e85d9c3-c800-4d03-b79c-286343ad6020"}


gah! idk why ring gives me the body like that vs everything as a keyword


what you probably mean to do is update item "seller_id" #(java.util.UUID/fromString %) , or something like that


what does % mean in this situation?


cause what you wrote is a million times more readable haha


cool! ill have to read up on that


also using update gives me the original error i was having of java.lang.ClassCastException: java.util.UUID cannot be cast to clojure.lang.IFn


worth noting that you have to wrap in an anonymous fn only because it's a Java method, for regular Clojure functions you can just pass it in like (update item "seller_id" from-string)


wow ok mind blown


after you said that yuhan


i copied and pasted dmitry's code and it works


your explanation makes sense


yup, if it helps you can think of (update m k f) <=> (assoc m k (f (get m k)))

馃檹 4
Brandon Olivier22:04:02

Is there a simple way to get a formatted version of a map?

Brandon Olivier22:04:17

I'm imaginging somethign like pprint, but not tied to any output. Just a fancy string

Brandon Olivier22:04:28

nvm, solved my own problem. In cljs, it's (with-out-str (cljs.pprint/ppring coll))

Bill Phillips22:04:40

I鈥檓 loading a file in a fresh REPL, and it鈥檚 failing to find a dependency:

Syntax error compiling at (src/gascan/posts.clj:1:1).
namespace '' not found
I鈥檓 at a loss for why it鈥檚 failing to find this namespace. src/gascan/post_spec.clj exists and contains the namespace in question:
  (:require [clojure.spec.alpha :as s]))


If you start a REPL, is it automatically loading some namespaces, or no? If not, maybe you could try (require ' as the first thing in the new REPL and see if there are any errors?

Bill Phillips22:04:43

hmm, no errors. but the file still fails to load.

Bill Phillips22:04:36

gascan.core> (require '
gascan.core> (ns-interns '
Execution error at gascan.core/eval15313 (form-init6807784250795486954.clj:46).
No namespace: found


My mind isn't jumping to any theories about why that might happen...


Any stale .class files around you can try deleting? Other cached stuff? Try a fresh git clone of the same project on a different machine? etc.?


The fact that your REPL prompt is gascan.core makes me wonder whether when you typed (require ' it has already earlier tried to do that, and failed, and your require is doing nothing.


Might give more info or error if you can make (require ' be the first thing that Clojure does in the new JVM

Bill Phillips23:04:41

i put together a deps.edn so I could fire up a bare clj REPL (`lein repl` loads a lot of stuff right at the bat)

Bill Phillips23:04:52

user=> (require '
Syntax error compiling at (gascan/post_spec.clj:4:10).
No such namespace: java-time

Bill Phillips23:04:29

that was very confusing. not the first time i鈥檝e been confused by errors in cold start loading my REPL. is there a better place to find those errors than in the REPL itself?


There are at least a dozen different ways to start a Clojure REPL. Where error messages go in each case can differ. I would check carefully the output of your earlier attempts to see if there was no such error message anywhere, and if not, maybe describe how you start your REPL, and people familiar with that way of doing it might know.

Bill Phillips23:04:13

I鈥檓 starting it with a leiningen project in cider. The main REPL window doesn鈥檛 show any errors


In general, Leiningen has more stuff it tries to do for you under the hood, and so the #leiningen channel is a good place to find people expert in what kinds of things can cause this behavior, but it is so widely used that this shouldn't be a bad channel to ask, either.


You could try asking in #cider channel if startup errors maybe go to some other Emacs buffer, perhaps.

Bill Phillips23:04:41

Great. Thanks!


Hello Clojurians, I've tried to update the db in re-frame in this inelegant way

          (fn [db [_ data]]
            (assoc-in db [:tag-table :foo :current] (data "value"))
            (assoc-in db [:tag-table :foo :unit] (data "unit"))
            (update-in db [:tag-table :foo :history] conj (data "value"))))
But it doesn't seem to work. Maybe the way to do it is using a function I pass to update-in instead of conj? Like
(update-in db [:tag-table :foo] magic-function-that-updates-3-values-at-once data)


If so I am a little confused about how to write it, maybe someone can give me a tip?


Thanks in advance!

Chris O鈥橠onnell23:04:55

assoc-in and update-in don't mutate db. They return a new db value. So you're computing new db values and only returning the result of your call to update-in.

Chris O鈥橠onnell23:04:30

A common way to chain together changes to a map is the thread-first macro ->. Your event should work the way you expect if you formulate it as

  (fn [db [_ data]]
    (-> db
      (assoc-in [:tag-table :foo :current] (data "value"))
      (assoc-in [:tag-table :foo :unit] (data "unit"))
      (update-in [:tag-table :foo :history] conj (data "value")))))

Chris O鈥橠onnell23:04:55

That's syntactic sugar for the very difficult to read

  (fn [db [_ data]]
    (update-in (assoc-in (assoc-in db [:tag-table :foo :current] (data "value")) [:tag-table :foo :unit] (data "unit")) [:tag-table :foo :history] conj (data "value"))))

Chris O鈥橠onnell23:04:53

The important part of that is that each operation is being done on the result of the previous one, rather than just on db.