This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-09-28
Channels
- # babashka (48)
- # babashka-sci-dev (7)
- # beginners (123)
- # calva (32)
- # cider (5)
- # clara (20)
- # clj-kondo (3)
- # cljdoc (2)
- # cljs-dev (1)
- # clojure (113)
- # clojure-dev (5)
- # clojure-europe (65)
- # clojure-norway (23)
- # clojure-spec (4)
- # clojure-uk (4)
- # clojurescript (33)
- # cursive (3)
- # datalevin (39)
- # datomic (2)
- # emacs (14)
- # events (1)
- # fulcro (10)
- # graphql (5)
- # humbleui (2)
- # integrant (4)
- # introduce-yourself (3)
- # jobs (1)
- # jobs-discuss (11)
- # kaocha (26)
- # leiningen (6)
- # malli (24)
- # nbb (2)
- # off-topic (69)
- # pathom (77)
- # podcasts-discuss (2)
- # reitit (8)
- # remote-jobs (2)
- # sci (17)
- # scittle (8)
- # squint (1)
- # xtdb (43)
Hi! I’m extracting some keys from a json file and get back a vector of maps
({:name "name1", :id "61fd12debd75d9413d874a42"}
{:name "name2", :id "61dd3720ac8ceb2d1f4f8419"}
{:name "name3", :id "61dd404da88a335e39ac984b"})
Now I’d like to create something like a combined lookup table, so something like
"61fd12debd75d9413d874a42" name1
"61dd3720ac8ceb2d1f4f8419" name2
...
so I can get
the id and have the name returned. But I’m somehow failing to come up with the functions needed to do that, could somebody point me in the right direction?Hi @U043ZD1K5TL . There are many ways to do that but you can take a look the reduce
function (https://clojuredocs.org/clojure.core/reduce)
(->> '({:name "name1", :id "61fd12debd75d9413d874a42"}
{:name "name2", :id "61dd3720ac8ceb2d1f4f8419"}
{:name "name3", :id "61dd404da88a335e39ac984b"})
(mapv (juxt :id :name))
(into {}))
Or this one:
(->> '({:name "name1", :id "61fd12debd75d9413d874a42"}
{:name "name2", :id "61dd3720ac8ceb2d1f4f8419"}
{:name "name3", :id "61dd404da88a335e39ac984b"})
(reduce (fn [m {:keys [id name]}]
(assoc m id name)) {}))
@U01RL1YV4P7 is it possible to use the #() form for the anonymous function or is fn
needed?
You can do that:
(->> '({:name "name1", :id "61fd12debd75d9413d874a42"}
{:name "name2", :id "61dd3720ac8ceb2d1f4f8419"}
{:name "name3", :id "61dd404da88a335e39ac984b"})
(reduce #(assoc %1 (:id %2) (:name %2))
{}))
Well, you can also destructure in let
...
(->> '({:name "name1", :id "61fd12debd75d9413d874a42"}
{:name "name2", :id "61dd3720ac8ceb2d1f4f8419"}
{:name "name3", :id "61dd404da88a335e39ac984b"})
(reduce #(let [{:keys [id name]} %2]
(assoc %1 id name))
{}))
Since juxt
is often an answer looking for a problem, I feel like I should mention it whenever it’s applicable! 🙂
(->> your-data
(map (juxt :id :name))
(into {}))
I like this approach, because it emphasizes composability of core functions, and it’s efficient
I'm trying to use this function (https://clojuredocs.org/clojure.edn/read#example-5a68f384e4b09621d9f53a79) to load an .edn file. The result should be a nested map. Here is the function:
(defn load-edn
"Load edn from an io/reader source (filename or io/resource)."
[source]
(try
(with-open [r (io/reader source)]
(edn/read (java.io.PushbackReader. r)))
(catch java.io.IOException e
(printf "Couldn't open '%s': %s\n" source (.getMessage e)))
(catch RuntimeException e
(printf "Error parsing edn file '%s': %s\n" source (.getMessage e)))))
When I run:
my-project=> (load-edn "filename.edn")
I expect this:
{:a 42 :b {:c 99 :d 101}}
Instead I get this:
Error parsing edn file 'filename.edn': No reader function for tag object
The code is copied from the document. So is there something outdated in the documentation? Or do I need to create a reader function?It's over 1 MB so I can't post all of it
Let me see what I can post....
{:acct {:init-cash 10000.0, :option-book 155181.2234612884, :cash 10000.0, :total 10000.0, :drawdown 0, :drawdown-percent 0, :last-settled-value 10000.0, :all-time-high 10000.0}, :state-logfile-directory "cached-state/", :potential-returns (), :stop-epoch 1672549200, :orders {}, :late-in-day true, :paid-this-month-already false, :enable-punishment false, :last-bar-in-day-tol 450, :epoch 1664369100, :rec ord-spot-ratio true, :strike-fraction-vec [1.0], :punishment-fn #object[clojure_trade_bot_2.punishment_functions$no_punishment 0x3753db6e "clojure_trade_bot_2.punishment_functions$no_punishment@3753db6e" ], :amount-paid-this-month 0, :log-all-triggers false, :datasource #object[next.jdbc.connection$url_PLUS_etc$reify__1995 0x2ee99820 "jdbc:<postgresql://192.168.56.1:5432/mockstockdata>"], :prev-late-in-day true, :trades-only "trades-only.csv", :early-in-day-tol 4050.0, :update-sigma #object[clojure_trade_bot_2.loop_state$fn__12571 0x5dfff935 "clojure_trade_bot_2.loop_state$fn__12571@5dfff935"], :tickers-u pdated {}, :state-logfile-format-str "cached-state/indicator-states-%s.edn", :histogram {:60 1.1, :70 1.2, :80 1.7, :90 2.5, :95 3.5, :99 5}, :pay-thresh 200000, :pickle-trigger-epoch 1663764872, :prev-p ositions {:AHC {:ticker :AHC, :entry-price 18.369068423954616, :epoch 1642691700, :qty 7.0, :direction :up, :ratio 1.0, :strike 645.0, :entry-spot 644.88}, :NEU {:ticker :NEU, :entry-price 8.498051910590 874, :epoch 1642775400, :qty 17.0, :direction :up, :ratio 1.0, :strike 342.5, :entry-spot 338.53}, :DAC {:ticker :DAC, :entry-price 1.8082271790284636, :epoch 1656603904, :qty 80.0, :direction :up, :rati o 1.0, :strike 91.5, :entry-spot 89.86}, :CRC {:ticker :CRC, :entry-price 3.1913637679909854, :epoch 1642691700, :qty 45.0, :direction :up, :ratio 1.0, :strike 113.5, :entry-spot 113.4}, :XRM {:ticker :X RM, :entry-price 4.82937735211199, :epoch 1642691700, :qty 30.0, :direction :up, :ratio 1.0, :strike 169.0, :entry-spot 169.0}, :CNS {:ticker :CNS, :entry-price 2.660726741128059, :epoch 1642775400, :qty 55.0, :direction :up, :ratio 1.0, :strike 100.5, :entry-spot 100.09}, :ROG {:ticker :ROG, :entry-price 6.8424128683807055, :epoch 1642775400, :qty 21.0, :direction :up, :ratio 1.0, :strike 272.5, :entry -spot 270.24}, :SAM {:ticker :SAM, :entry-price 13.076930201045968, :epoch 1642775404, :qty 11.0, :direction :up, :ratio 1.0, :strike 507.5, :entry-spot 476.41}, :OXM {:ticker :OXM, :entry-price 2.596691 3186364664, :epoch 1642775400, :qty 56.0, :direction :up, :ratio 1.0, :strike 102.5, :entry-spot 101.85}, :FIX {:ticker :FIX, :entry-price 2.1879612022181405, :epoch 1642775400, :qty 66.0, :direction :up , :ratio 1.0, :strike 101.0, :entry-spot 99.18}}, :prev-theoretical-profits {}, :early-termination-thresh 1.0E7, :late-in-day-tol 4050.0, :enable-trading true, :first-bar-of-day false, :sigma {}, :prev-d ay-of-week "nada", :sell-before-end-of-day true, :set-order-limits #object[clojure_trade_bot_2.loop_state$fn__12573 0x63f3fef1 "clojure_trade_bot_2.loop_state$fn__12573@63f3fef1"], :last-bar-in-day false , :update-position-performance #object[clojure_trade_bot_2.loop_state$fn__12575 0x4a52d14a "clojure_trade_bot_2.loop_state$fn__12575@4a52d14a"], :this-day-num 27, :rand-str "igy7rt3k6nc41b5a", :update-st rikes #object[clojure_trade_bot_2.loop_state$fn__12577 0x281868b8 "clojure_trade_bot_2.loop_state$fn__12577@281868b8"], :start-trading-epoch 1641048304, :prev-epoch 1609512304, :log-all-ratios true, :kee p-going-historical true, :update-account-value #object[clojure_trade_bot_2.loop_state$fn__12579 0xa0c0adf "clojure_trade_bot_2.loop_state$fn__12579@a0c0adf"], :update-orders #object[clojure_trade_bot_2.l oop_state$fn__12581 0x76f47919 "clojure_trade_bot_2.loop_state$fn__12581@76f47919"], :profits (), :current-theoretical-profits {}, :prev-early-in-day false, :physical-epoch 1664369669, :first-bar-in-day- tol 450, :sell-threshold 5, :update-positions #object[clojure_trade_bot_2.loop_state$fn__12583 0x2af03e0c "clojure_trade_bot_2.loop_state$fn__12583@2af03e0c"], :max-spread 20, :enable-early-termination t rue, :tickers-to-display (:EAF :OMN :NOMD :NKX :MSGN :CVI :NUW :COF :DXR :GWB :JEQ :HRC :CLR :ETX :QEP :ELJ :JBR :BKHU :UFS :BGB :EEA :FCN :GJR :NLSN :VCF :SKM :WNC :MT :SALT :RZB :BK :CHE :BCX :JELD :EQ T :SNDR :MUA :FFG :SITE :EGY :GLOG :HVT :PFSI :AZN :SCI :NDP :HCI :PLOW :EVR :VKQ :EFC :AWP :UNVR :HUD :OAK :RHE :JNPR :GBL :ZBH :CMA :FFA :MAS :MYJ :GSBD :FSM :SLCA :CMCM :AER :MHI :BKK :DSE :BST :MSN : FUL :AIZP :EFF :MXC :XIN :BURL :VSH :HOV :EXP :MTW :CSLT :CPF :CIVI :STG :SRCI :HNP :EVY :SAFE :BGCA :NCA :BCE :MHH :GOLF :UZA :XRX :LOR), :model-order-fill #object[clojure_trade_bot_2.loop_state$fn__125 85 0x4146ce42 "clojure_trade_bot_2.loop_state$fn__12585@4146ce42"], :indicator-fns {:indicator-1 #object[clojure_trade_bot_2.loop_state$fn__12587 0x2b767e47 "clojure_trade_bot_2.loop_state$fn__12587@2b76 7e47"], :indicator-2 #object[clojure_trade_bot_2.loop_state$fn__12589 0x7e1fe681 "clojure_trade_bot_2.loop_state$fn__12589@7e1fe681"]}, :prev-first-bar-of-day false, :prev-indicator-states {:PLOW {:iir-s tates {:iir-slow {:weights [{:b0 2.833901381447552E-6, :b1 5.667802762895104E-6, :b2 2.833901381447552E-6, :a1 -1.9966336134561717, :a2 0.9966477771275631} {:b0 4.41624156086296E-6, :b1 8.83248312172592E -6, :b2 4.41624156086296E-6, :a1 -1.9929859170614943, :a2 0.993000054856945} {:b0 0.0018797747856513227, :b1 0.0018797747856513227, :b2 0.0, :a1 -0.9962404504286974, :a2 0.0} {:b0 3.5370172737914096E-6, :b1 7.074034547582819E-6, :b2 3.5370172737914096E-6, :a1 -1.994434178813129, :a2 0.9944483268822241} {:b0 2.183838619369596E-6, :b1 4.367677238739192E-6, :b2 2.183838619369596E-6, :a1 -1.9992909798874585 , :a2 0.9993051624096118} {:b0 3.949329870503769E-6, :b1 7.898659741007538E-6, :b2 3.949329870503769E-6, :a1 -1.995456338892737, :a2 0.9954704942128065} {:b0 5.141012016325428E-6, :b1 1.0282024032650857E -5, :b2 5.141012016325428E-6, :a1 -1.9926082148627287, :a2 0.9926223499788447} {:b0 3.535540421983896E-6, :b1 7.071080843967792E-6, :b2 3.535540421983896E-6, :a1 -1.993601419630258, :a2 0.993615561791946 1} {:b0 3.543210501829165E-6, :b1 7.08642100365833E-6, :b2 3.543210501829165E-6, :a1 -1.9979263828617726, :a2 0.9979405557037805}], :states [{:sample0 27.85, :sample1 27.98, :sample2 27.93, :state0 24.20 886529629913, :state1 24.214437224859612, :state2 24.220001500922162} {:sample0 24.20886529629913, :sample1 24.214437224859612, :sample2 24.220001500922162, :state0 32.01398853180653, :state1 32.01595591 490967, :state2 32.01791210337123} {:sample0 32.01398853180653, :sample1 32.01595591490967, :sample2 32.01791210337123, :state0 31.95795727148643, :state1 31.957742112045977, :state2 31.957518737386525} {:sample0 31.95795727148643, :sample1 31.957742112045977, :sample2 31.957518737386525, :state0 31.363908659704876, :state1 31.36270930484591, :state2 31.361511719923143} {:sample0 31.363908659704876, :sa mple1 31.36270930484591, :sample2 31.361511719923143, :state0 19.662294604510556, :state1 19.664421204976005, :state2 19.666544353982843} {:sample0 19.662294604510556, :sample1 19.664421204976005, :sampl e2 19.666544353982843, :state0 21.67835902272997, :state1 21.676104840872984, :state2 21.673844232506006} {:sample0 21.67835902272997, :sample1 21.676104840872984, :sample2 21.673844232506006, :state0 31 .962869959673192, :state1 31.965876686625826, :state2 31.968899622793604} {:sample0 31.962869959673192, :sample1 31.965876686625826, :sample2 31.968899622793604, :state0 35.25621372830657, :state1 35.264 22489970006, :state2 35.27224060112758} {:sample0 35.25621372830657, :sample1 35.26422489970006, :sample2 35.27224060112758, :state0 39.175415424078636, :state1 39.18666501908188, :state2 39.197882122977 69}], :scale-vals [1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0000000001424068]}
`:punishment-fn #object[clojure_trade_bot_2.punishment_functions$no_punishment 0x3753db6e “clojure_trade_bot_2.punishment_functions$no_punishment@3753db6e” ],`
:punishment-fn #object[clojure_trade_bot_2.punishment_functions$no_punishment 0x3753db6e "clojure_trade_bot_2.punishment_functions$no_punishment@3753db6e"
],
gotcha
(try
(let [f (java.io.File/createTempFile "test" "edn")]
(spit f (pr-str {:a 1 :b inc}))
(println "file contents:\n" (slurp f))
(clojure.edn/read-string (slurp f))))
file contents:
{:a 1, :b #object[clojure.core$inc 0xacbdcab "clojure.core$inc@acbdcab"]}
Execution error at metabase.api.dashboard-test/eval152920 (REPL:74).
No reader function for tag object
gotcha
Lemme take that out...
@U11BV7MTK I took out serialized functions, and now I'm getting this error: Error parsing edn file 'cached-state/indicator-states-2022-09-27.edn': No reader function for tag object
What does that mean?
how would you read this value? #object[clojure.core$inc 0xacbdcab "clojure.core$inc@acbdcab"]
Ahhhhhhhhhhhhh
Okay!
You can serialize a hash set right? #{3 4 5 :fred}
dashboard-test=> (try
(let [f (java.io.File/createTempFile "test" "edn")]
(spit f (pr-str #{1 2 :fred}))
(println "file contents:\n" (slurp f))
(println "read contents:\n" (clojure.edn/read-string (slurp f)))))
file contents:
#{:fred 1 2}
read contents:
#{:fred 1 2}
nil
but only if you can serialize everything in the set
(let [f (java.io.File/createTempFile "test" "edn")]
(spit f (pr-str #{1 2 inc}))
(println "file contents:\n" (slurp f))
(println "read contents:\n" (clojure.edn/read-string (slurp f))))
file contents:
#{1 #object[clojure.core$inc 0xacbdcab "clojure.core$inc@acbdcab"] 2}
Execution error at metabase.api.dashboard-test/eval152930 (REPL:102).
No reader function for tag object
Thanks everybody! What wound up working was selecting only the keys from the map that were really necessary. After I did that, all the mysterious #'s went away
What is the best way to secure your first job with Clojure? I have noticed most of the open positions require about 3 years of prior experience.
#jobs-discuss is the best channel for this kind of discussion (and you'll see several similar discussions in the past if you scroll back there).
I didn't think anywhere hired for lisps - you have to infiltrate under the guise of a language like nodejs/php/ruby/python and get them to adopt it from the inside out
First job with Clojure.
@U96DD8U80 There are plenty of Clojure shops out there -- in some parts of the world. So location is important.
I mean, not anywhere near as many as Scala or Kotlin shops, but still there are definitely places hiring for Clojure roles.
@U043D8YDWQZ If you scroll back in #jobs-discuss you'll see quite a bit of advice there.
Thank you everybody.
Is there any way to get the form
that was used when creating a clojure var / top level definition in a namespace? Currently I am using tools.reader
to look at a file but that has the disadvantage of potentially going stale as a user interacts with the code in the repl but doesn’t save the code to the file.
from the most general perspective, no. function objects are compiled and do not contain their source
unless you are doing something extra, the things you eval in your repl do not get saved anywhere (but they could be if you hack your repl!)
So then, the general wisdom would be to write a macro def
that potentially stores the form etc on var metadata…
I think it would be better to make this the job of your evaluator than embed it in your program
(defmacro definitely
[symbol form]
`(def ~(with-meta symbol
(merge (meta symbol) {:form `(quote ~form)}))
~form))
The concern there is that from a library perspective that it’s potentially asking users more to configure a custom nrepl middleware or something similar. Aren’t you then also potentially pushing the complexity of macroexpansion onto your evaluator?
doesn't have anything to do with expansion
What I mean is, consider a user (programmer) defined macro
(defmacro define-name [x]
(def name-~x ~(str x))
(define-name bob)
The evaluator must now understand that (eval '(define-name bob))
will expand, no?(I apologize if I am confusing terminologies here)
if you get into wrappers around core var creators, then yes, you might need something else
user=> (defmacro definitely
[symbol form]
`(def ~(with-meta symbol
(merge (meta symbol) {:form `(quote ~form)}))
~form))
#'user/definitely
user=> (definitely x (+ 1 2))
#'user/x
user=> (meta #'x)
{:form (+ 1 2), :line 1, :column 1, :file "NO_SOURCE_PATH", :name x, :ns #object[clojure.lang.Namespace 0x312afbc7 "user"]}
user=>
Yeah, I think I will have to. My next question (and please don’t throw tomatoes): Is there any way to hot-patch def
so that I, as an end user, don’t need to use a special my-ns/def
call?
Haha damn. No way to do it on a live repl? I realize we are essentially editing a special form, so I am not surprised that this might be the case
user=> (defmacro def [a] `(+ ~a ~a))
#'user/def
user=> (def 4)
Syntax error compiling def at (REPL:1:1).
First argument to def must be a Symbol
because it sounds like you are running into the same issue #core-typed does with the potentially stale file
So, I have https://github.com/teknql/systemic that I use to make a dependency graph of systems. This makes it so that starting a system will automatically start its sub-systems..
(defsys *env*
(System/getEnvironment "ENV"))
(defsys *db-url*
(case *env*
"prod" prod-url
"dev" dev-url))
When using the dependent system directly in the macro it is easy enough to establish the dependency hierarchy. But sometimes a system calls a function that depends on a system, and then that requires specifying manual dependencies. In practice I’ve found this to be quite fragile and so I would like to automatically traverse the call tree and establish dependencies…
(defsys *env*
(System/getEnvironment "ENV"))
(defn get-db-url
[]
(case *env*
"prod" prod-url
"dev" dev-url))
(defsys *db-url*
(get-db-url))
Apologies for the silly example. Obviously you’d never really write code like this, but I hope it sort of showcases the issue.
Yeah, I am familiar with mount. Systemic was heavily inspired by it, with the core difference being that I wanted the ability to be able to run multiple isolated systems in the same repl (to allow for testing and deving in the same repl session).
But it looks like there’s no great answer. Morphe looks like an interesting avenue. Perhaps I’ll look into a per-application defn
that is built on-top of of morphe.
Will a pmap'ed evaluation retain the same order of elements that a map'ed one would? (like, (pmap inc (range 10))
- but lets assume 'inc' is a function with a lot of stuff going on under the hood, where evaluation time may be different between calls)
Yes, it will maintain order
(pmap
(fn [x]
(Thread/sleep (long (rand-int 1000)))
(inc x))
(range 10))
;; => (1 2 3 4 5 6 7 8 9 10)
I didn't mean to delete that only edit it so here goes once more: is there different syntax for reading tagged maps (i.e. not (:key-name map)
)
What do you mean by "tagged maps"?
#:foo{:bar 42}
is shorthand for {:foo/bar 42}
so you'd access that with (:foo/bar data)
like this #:tick{:beginning #time/time "06:19", :end #time/time "21:42"}
so I guess what you said applies here
user=> (println #:tick{:beginning "06:19", :end "21:42"})
#:tick{:beginning 06:19, :end 21:42}
nil
user=> (binding [*print-namespace-maps* false] (println #:tick{:beginning "06:19", :end "21:42"}))
{:tick/beginning 06:19, :tick/end 21:42}
nil
user=>
Cool, didn't know about binding
either. Thanks!
I have a function
(map (juxt :description calculate-duration-dec) day-sorted-by-project)
that returns
(["cluster debugging" 4.0] ["daily" 0.25])
and I’m trying to combine the first and second elements of all lists so I will get
cluster debugging, daily" 4.25
I tried around a bit with mapcat, but I’m not really getting back what I wantThey're all the same format [string number] and you want a single pair at the end that is [string number]?
There are a couple of ways you could do this, depending on how long the list of data is and how efficient you need it to be.
If you don't care much about either, then [(str/join ", " (map first data)) (apply + (map second data))]
is probably going to be reasonable.
If you want something more efficient, you probably want to reduce
over day-sorted-by-project
and produce a pair whose first element is a vector of the :description
values (that you can then str/join
) and whose second element is the sum.
Something like...
(let [[descriptions sum]
(reduce (fn [[ds s] day]
[(conj ds (:description day))
(+ s (calculate-duration-dec day))])
[[] 0.0]
day-sorted-by-project)]
[(str/join ", " descriptions) sum])
(untested)
thanks very much, I’ll test it out and have a closer look tomorrow. I kept thinking about needing two reduces, seems like it always turns out more complicated in my head
You could probably do the whole thing with transduce
since your reducing function could have a completing arity, but I'd need to play in the REPL for that 🙂
Hi, is there a better way to check if all selected keys's values in each maps are equal to zero?
(def x [{:a 0
:b 0
:c 1}
{:a 0
:b 0}])
(->> x
(map #(select-keys %[:a :b]))
(map vals)
flatten
(every? zero?))
#_=> true
(I guess assuming no negatives :)
I guess every? zero? probably better
Thank you all for your advice and solutions. Very useful! 👍
x.flatMap(o => [o.a, o.b]).every(i => i === 0)
come to the dark side, it's smoother here
This is almost exactly the same as the last solution!
I like juxt
more, but if I were trying to do the precise equivalent, then I’d say:
(every? zero? (mapcat (fn [{:keys [a b]}] [a b]) x))
Found a weird behavior when shadowing a clojure.core fn.
(ns ns1)
(defn print-str [] 111)
(ns ns2)
(require '[ns1])
(defn x [] (ns1/print-str))
(x) ;; => 111, everything looks fine
;; now if I change the print-str fn
(ns ns1)
(defn print-str [] 999)
;; and reexecute (x), I expect it to return the updated value 999
(ns ns2)
(x) ;; => but no, still returns the old value 111
Is my expectation wrong? If the fn has any other name that doesn't shadow a core fn, it works as expected. e.g. something-else
instead of print-str
When you evaluate the form (defn x [] (ns1/print-str))
would you not expect the invocation of x
subsequently to use the binding of ns1/print-str
that existed at that time of the binding of x
as a function?
Also what fn are you referring to when you say:
If the fn has another name, it works as expected
Yep, I expect ns1/print-str
to be looked up at the time of execution.
Sorry, that was badly worded. If I use another fn name instead of print-str
, e.g. something-else
, it works as expected.
Looks like there is some work in 1.12 related to this https://clojure.atlassian.net/browse/CLJ-2712