Fork me on GitHub
#beginners
<
2020-08-19
>
Ben Sless07:08:45

Hi all, should this happen?

(if (Boolean. "false") "true" "false") 
;; => "true"
(= (Boolean. "false") false)
;; => true

noisesmith07:08:20

@ben.sless never use the Boolean constructor

noisesmith07:08:39

unlike java, clojure's if uses an identity check on Boolean/FALSE rather than checking the value

noisesmith07:08:06

you can use Boolean/valueOf in place of the constructor to get the correct result

noisesmith07:08:57

(or wrap the Boolean constructor call in a call to clojure.core/boolean, but valueOf is more direct)

Ben Sless07:08:28

Thanks @noisesmith, don't worry, I'm not using it 🙂

Ben Sless07:08:45

A colleague asked me that question and I realized I had no idea, so I came here to ask the experts

noisesmith07:08:43

I probably don't need to go around using the language of a priest about this - of course I mean that there's no case I know of where using the Boolean constructor makes sense in clojure code - there's always a simpler and more correct solution

noisesmith07:08:19

counter examples or proof of my statement both welcome haha

Ben Sless07:08:27

Every rule as an exception, but it may take a while to find it

noisesmith07:08:43

now we have the pragmatic choice - which wastes more time, looking for a constructive use of the Boolean constructor in clojure code or short circuit assuming that it's never valid 😆

Ben Sless07:08:37

Life needs an element of mystery, I think. Label it under "There Be Dragons" until a brave soul ventures out and returns with a useful answer 😄

ska10:08:31

(true? (Boolean. "false"))
;; => false

ska10:08:55

But the documentation of if comes to the rescue:

if
 (if test then else?)
Special Form
  Evaluates test. If not the singular values nil or false,
  evaluates and yields then, otherwise, evaluates and yields else. If
  else is not supplied it defaults to nil.
The constructed object may be = to false, it prints as false, but it's not the "singular value[...] false". Intuitiv? Probably not. Relevant for real life? Neither, I'd say. At least, I've not hit that problem in the past 10+ years.

andy.fingerhut13:08:35

One way in which I think it is relevant for real life is that I am fairly sure that it improves the performance of Clojure code execution for if to behave as it does, versus also taking the false branch for other values = to false

Brandon Stubbs12:08:15

Hey, I had a question on destructuring:

(let [{:keys [region]} {:region "x"}]
    region)
Could I bind region to another name, for instance src-region

rpkarlsson12:08:26

Perhaps:

(let [{src-region :region} {:region "x"}]
    src-region)
?

Brandon Stubbs12:08:17

Oh yes great thank you exactly what I was after 😄

👍 3
zackteo12:08:32

Hi guys, what version of openjdk should I be running? Is there any reason for me not to go from openjdk 8 to 11 ? 😮

practicalli-johnny15:08:52

Java 11 is a very safe choice for running Clojure. It's been around long enough that any bugs have been fixed. Make sure your build tools are up to date and of course test your apps before changing Java versions in production

zackteo12:08:32

I understand that both are LTS versions and from state of clojure https://clojure.org/news/2020/02/20/state-of-clojure-2020 ... > Clojure itself has been using Java 8 as the baseline JVM for a couple years and will continue to do so (while also supporting newer versions of Java). When running Clojure, we recommend Java 8 or 11 right now. So in that sense is perfectly good for me to switch to java 11?

jumar18:08:33

I’ve been running in dev with JDK 14 even and it works well; JDK 11 in prod for a while and no issues. Note that LTS is a concept that only makes sense when talking about particular distributions (in particular Oracle’s Jdk) and their guarantees - you cannot say JDK11 in general is lts

dharrigan12:08:49

I use Clojure with JVM 11 in production. Zero issues.

dakra12:08:01

Is there an easy way to use deps.edn :main-opts where I can say where exactly the command line argument from the user goes? E.g. I have this in my deps.edn aliases

:uberjar {:extra-deps {seancorfield/depstar {:mvn/version "1.0.97"}}
            :main-opts ["-m" "hf.depstar.uberjar" ...USER-ARG-HERE... "-C" "-m" "myapp.core"]}
Then I would just like to call something like clojure -A:uberjar myappUberJar.jar

seancorfield16:08:28

I guess it would be a good enhancement to allow the JAR file to be specified in any order with the options. Can you create an issue on my depstar repo on GitHub about that so I don't forget?

seancorfield16:08:38

@daniel415 Also, what did you think of Alex's suggestion for a -X entry point? I'm not sure that would be easier for users but it is another option to consider.

dakra16:08:50

I think the map entry point is probably a good idea in general but for my use-case where I want that the user has to type as little as possible nothing beats -A:uberjar xxx.jar I guess.

seancorfield18:08:06

Yup. Working on it right now.

seancorfield19:08:54

@daniel415 that is implemented on *develop* if you want to depend on depstar via :git/url and :sha to test it out.

seancorfield20:08:35

It will accept the JAR file name and options in any order now. You can also specify the JAR file name with -J or --jar if you want 🙂

dakra21:08:15

That was fast 🙂 I just tested it

:uberjar {:extra-deps {seancorfield/depstar {:git/url ""
                                               :sha "c461ba830e069edf0f011169c18595a4a8f917fc"}}
            :main-opts ["-m" "hf.depstar.uberjar" "-C" "-m" "myapp"]}
and now I can just do the clojure -A:uberjar myapp.jar like I wanted. Thanks for the fast help.

seancorfield21:08:10

Thank you for testing that and confirming it works!

Alex Miller (Clojure team)13:08:47

no, that's not an available feature

dakra13:08:51

ok, thanks.. I guess I'll just write in the readme that the user has to append the -C and -m options themself 😉

dharrigan13:08:51

I think #babashka has some sort of uberjar'ing facility...a recent addition I think

dakra13:08:33

Nice. babashka is really cool. But I need a "real" java uberjar und not a bb uberjar. Very nice nevertheless 🙂

borkdude13:08:16

Maybe you could ask @seancorfield to make the uberjar name itself also a normal CLI arg

borkdude13:08:29

With backwards compatibility of course

Alex Miller (Clojure team)13:08:35

better, I would suggest a map entry point compatible with -X and overrides

borkdude14:08:13

@daniel415 Creating an uberjar with babashka that's runnable with Clojure:

borkdude@MBP2019 /tmp $ mkdir -p uberjar/src
borkdude@MBP2019 /tmp $ cd uberjar
borkdude@MBP2019 /tmp/uberjar $ echo "(ns foo (:gen-class)) (defn -main [& args] (prn :hello))" > src/foo.clj
borkdude@MBP2019 /tmp/uberjar $ clojure -m foo
:hello
borkdude@MBP2019 /tmp/uberjar $ mkdir -p classes
borkdude@MBP2019 /tmp/uberjar $ clojure -e "(require 'foo) (compile 'foo)"
foo
borkdude@MBP2019 /tmp/uberjar $ ls classes
foo$_main.class                      foo$fn__153.class                    foo$loading__6721__auto____151.class foo.class                            foo__init.class
borkdude@MBP2019 /tmp/uberjar $ bb -cp $(clojure -Spath):classes -m foo --uberjar /tmp/foo.jar
Building uberjar: /tmp/foo.jar
borkdude@MBP2019 /tmp/uberjar $ ( cd /tmp; java -jar foo.jar )
:hello

🙌 3
borkdude14:08:30

The crux: you need to compile the main class yourself with Clojure, since babashka doesn't do compilation

borkdude14:08:39

Note: I've just merged the uberjar feature to master, it's not released as of now, but there are pre-release binaries in #babashka_circleci_builds

David Pham15:08:11

When we have an atom and we add a watch to it, what happens to the watch if the atom gets garbage collected?

Alex Miller (Clojure team)15:08:31

it gets garbage collected too

Alex Miller (Clojure team)15:08:56

watches are not active threads, they are side effects triggered on change

David Pham15:08:54

even in ClojureScript?

noisesmith16:08:07

especially in cljs, since there's no extra threads there

David Pham16:08:30

Thanks a lot!

Endre Bakken Stovner18:08:41

I am having newb problems with sente. I want to push from the client to the server. I send a message in a connected repl with (chsk-send! :sente/all-users-without-uid [:h/h "Hooooo!"]). I have two event-handlers on the client side:

(defmethod -event-msg-handler :chsk/recv
  [{:as ev-msg :keys [?data]}]
  (js/alert (str "Push event from server woop wopp: " ?data)))


(defmethod -event-msg-handler :h/h
  [{:as ev-msg :keys [?data]}]
  (js/alert (str "correctamundo " ?data)))
It is always :chsk/recv which receives the message, not the :h/h one. This is the alert printed: Push event from server woop wopp: [:h/h "Hooooo!"] Any guesses what I am doing incorrectly? Am I misunderstanding something? I expect :h/h to handle it, not :chsk/recv See thread for info needed to reproduce.

🏁 3
Endre Bakken Stovner18:08:06

Repo is here: https://github.com/endrebak/everflow

git clone [email protected]:endrebak/everflow.git
cd everflow
lein shadow watch app
# new tab
lein run
# new tab
lein repl :connect 7000
(require 'everflow.routes.home)
(in-ns 'everflow.routes.home)
@connected-uids
(chsk-send! :sente/all-users-without-uid [:h/h "Hooooo!"])

noisesmith18:08:06

IIRC all messages have the :chsk/recv key, other keys are for metadata describing the state transitions of the connection

noisesmith18:08:29

eg. you'd use them to hook in a callback for when you get a connection, time out a connection, etc.

noisesmith18:08:52

you want your own "routing" which can just be a cond or a multimethod, inside the recv handler

noisesmith18:08:06

the repl result you show is consistent with this theory

Endre Bakken Stovner18:08:11

So I am misunderstanding the use then. I should handle the different cases like you said 🙂 Thanks

noisesmith18:08:42

this is based on a memory of N years ago when I last used it, but your repl printouts are consistent with this theory

noisesmith18:08:16

I made a router (like the one ring uses) to handle the messages, if I were implementing it today I'd use multimethods instead

noisesmith18:08:39

one multimethod for the various types of event, another for the :chsk/recv event

Endre Bakken Stovner18:08:08

You might be right, but it is a bit weird, because on the server side I successfully have methods like

(defmethod -event-msg-handler :everflow/button
[ev-msg] (println :slightly_smiling_face:?data ev-msg)))
that correctly handle the clicks from this JS code:
#(chsk-send! [:everflow/button "Hooo!"])

noisesmith18:08:29

this might be a dumb client/server asymmetry - try debugging the two sides separately and using less cljc

Endre Bakken Stovner18:08:50

(Deleted a message which was incorrect) You might be right. The official client example has no example of -event-msg-handler with a different namespace than :chsk/. I should ask on the issues page :)

noisesmith18:08:17

i might have made an adaptor in the cljc layer that accounted for the asymmetry, but it was in a closed source library I wrote 5 years ago and no longer have (TBH the idea I'd never look at that code again felt like a relief :D)

Endre Bakken Stovner18:08:15

Lol, I feel that way about all my code XD Actually, in the wild I find one example of arbitrarily namespaced event handlers on the client side (2014): https://github.com/seancorfield/om-sente/blob/master/src/cljs/om_sente/core.cljs#L120 But another one more or less uses your approach (2016): https://github.com/danielsz/system-websockets/blob/master/src/cljs/demo/core.cljs#L46 I will ask on the issues page 🙂

Endre Bakken Stovner19:08:56

By setting the option :wrap-recv-evts? to false when calling sente/make-channel-socket-client! my original code works. Thanks!

noisesmith19:08:48

nice to know about this feature! @UEENNMX0T I think this also answers your question

🙏 3
Endre Bakken Stovner08:08:27

This is covered in a line in the 3. ed. Web Development with Clojure book:

The wrap-recv-evs? option specifies whether we want to receive all application messages wrapped in an outer :chsk/recv event. We'll turn this off by passing false so that our client events are structured like our server events.
It does not explain why the server and client isn't symmetric though.

Endre Bakken Stovner08:08:51

In the comments in the source it says it is there just for bkwrds compatibility and will be changed in a future release

Santiago19:08:39

I’m new to working with atoms. I need one, because I need to load and unload models from memory in my API (https://github.com/jcpsantiago/sagemaker-multimodel-clj). If a model is already in memory the API needs to reply with a 409 — this means I need to check if the model is already in the atom. If it’s not, my fun must load it. How do I avoid derefing the atom (`current_model`) to test if model-name is loaded, and then swap! ing? The code below makes it possible for the state to change between the if and the swapping, which is far from ideal.

(defn load-model!
  [model-name url current-models]
  (if (db/loaded? model-name @current-models)
    (do
      (println (str "Model " model-name " is already loaded!"))
      {:status 409
       :body ""})

    (let [xgbmodel (xgb/load-model (str url "/model.xgb"))]
      (swap! current-models conj 
             {model-name 
              {:model-name model-name :model-url url :model xgbmodel}}))))

jsn21:08:44

Atom doesn't seem to be a problem here. What do you want to happen if the second load-model! is called while the first call is being executed?

Santiago05:08:14

@UTQEPUEH4 from my understanding the following would happen: request1 checks the atom, model is not loaded so proceeds to swap!; request2 attempts to deref the atom and waits until request1 finished the swap!. I think the issue arises when both requests try to check if the model is loaded, see that it is not and then proceeded to load it -- now there will be two loading attempts. Even though the final result is the same when you look at the atom, the second request should respond with a 409 instead of 200. Am I thinking correctly? This may also all be moot I don't know :man-shrugging:

jsn08:08:38

If you want the second request to return 409 and not to start loading the model, then you should atomically mark the model-name as "loading" before you start loading the model

jsn08:08:19

you can do it using e.g. swap-vals

jsn08:08:13

I'm curious btw, why do you want the second request to return 409? Why don't you want your requests to be idempotent?

Santiago11:08:20

I need to abide by AWS's contract which says that if the model is already loaded into memory the server should reply with 409. It seems letting two requests try to load the model in a race condition would still be fine because the end effect is still the same. Adding the fact it's an edge case, I shouldn't worry too much. Is that correct? Thanks for swap-vaks though, I'll check it out

jsn11:08:56

I don't know if the race condition will be tolerated; perhaps it will, but I'd still avoid it -- it's nicer that way, and should be easy, AFAICS

jsn12:08:23

I can't help but think that the following structure would be a good match: an atom hash (by name) of future -s

Santiago17:08:45

@UTQEPUEH4 could you elaborate? I don’t know what you mean with an atom hash 😅

jsn18:08:53

(def loaded-models (atom {}))

Santiago20:08:30

ah yes that’s what current-models is, it’s just passed to that function