Fork me on GitHub
#beginners
<
2021-10-25
>
roelof05:10:38

How do I make this work

(defn get-forecast! []
  (let [postal-code (:postal-code @app-state)]
    (ajax/GET ""
      {:params {"q" postal-code
                "units" "metric" ;; alternatively, use "metric"
                "appid" "xxxxxx"
                "exclude" "minutely", "current", "daily", "alerts" }
       :handler handle-response})))
I need to exclude multiple things here

orpheus05:10:05

Just to be clear, you’re trying to exclude keys from the response of the GET call? or is exclude part of the GET schema and it’s not working?

roelof05:10:42

I try to exclude keys from the respons

roelof05:10:24

but I see now this message

roelof05:10:29

missing value for key "alerts"

orpheus06:10:17

can maybe wrap your response with dissoc?

(dissoc (ajax/GET...) "minutely" "current" "daily" "alerts")

orpheus06:10:16

(-> (ajax/GET ...)
    (dissoc "minutely" "current" "daily" "alerts")  

roelof06:10:47

hmm, is it then a part of the call

roelof06:10:53

it looks not to me

roelof06:10:17

or do I misunderstood you

orpheus06:10:27

maybe I’m misunderstanding you.. if you want to remove keys from the response map you got back from calling axios/GET you can wrap that call with dissoc.

(defn get-forecast! []
  (-> (ajax/GET ""
                {:params {"q" (:postal-code @app-state)
                          "units" "metric" ;; alternatively, use "metric"
                          "appid" "xxxxxx"
                          "alerts" "xxxx"}
                 :handler handle-response})
      (disocc "minutely" "current" "daily" "alerts")))

roelof06:10:01

no, On the website of openweather api is stated that I can use exclude to exclude parts of the respons

roelof06:10:15

but when I try it i see a error message

Fredrik08:10:51

According to https://openweathermap.org/api/one-call-api the arguments to "exclude" should be "a comma-delimited list (without spaces)".

Fredrik08:10:20

Does something like this work for you?

{"q" postal-code
 "units" "metric"
 "appid" "xxxxxx"
 "exclude" "minutely,current,daily,alerts"}

SenyorJou09:10:12

Using clj-http this call works perfect

(client/get url
            {:accept :json
             :query-params {"lat" 41.3888
                            "lon" 2.159
                            "exclude" "minutely,current,daily,alerts"
                            "appid" api-key}}))

SenyorJou09:10:48

onecall does not accept q as param

roelof11:10:20

chips, then I have to find a way to convert a place to the lat and long 😞

roelof11:10:43

and why is longtitude still not resolved here :

(defn get-forecast! []
  (let [latitude (:latitude @app-state)]
       [longtitude (:longtitude @app-state)]
  (client/get ""
              {:accept :json
               :query-params {"lat" latitude
                              "lon" longtitude
                              "exclude" "minutely,current,daily,alerts"
                              "appid" api-key}})))

Fredrik11:10:30

It's hard to tell without seeing the rest of your app. The question is why @app-state doesn't have :longitude (notice the spelling).

roelof11:10:43

it has :

(ns ^:figwheel-hooks learn-cljs.weather
  (:require
   [goog.dom :as gdom]
   [reagent.dom :as rdom]
   [reagent.core :as r]
   [clj-http.client :as client]))


(defonce app-state (r/atom {:title "WhichWeather"          ;; <1>
                            :latitude 0 
                            :longtitude 0 
                            :temperatures {:today {:label "Today"
                                                   :value nil}
                                           :tomorrow {:label "Tomorrow"
                                                      :value nil}}}))

(defn handle-response [resp]
  (let [today (get-in resp ["list" 0 "main" "temp"])
        tomorrow (get-in resp ["list" 8 "main" "temp"])]
    (swap! app-state
           update-in [:temperatures :today :value] (constantly today))
    (swap! app-state
           update-in [:temperatures :tomorrow :value] (constantly tomorrow))))

(defn get-forecast! []
  (let [latitude (:latitude @app-state)]
       [longtitude (:longtitude @app-state)]
  (client/get ""
              {:accept :json
               :query-params {"lat" latitude
                              "lon" longtitude
                              "exclude" "minutely,current,daily,alerts"
                              "appid" api-key}})))
have to rewrite a lot to make things work

Fredrik12:10:02

Thanks for sharing 🙂 So now it works?

roelof12:10:31

nope, still a red line under longtitude on both places on the get-forecast method

Fredrik12:10:58

Oh, I see. There is a syntax error in the let binding. Combine the two binding vectors into one

Fredrik12:10:36

Instead of (let [x val1] [y val2] ...) it should be (let [x val1 y val2] ...)

roelof12:10:17

now I have to think how to store things the best way in the atom. There will be 4 times a hour and four times a temperature

roelof12:10:28

but first lunch

orpheus05:10:51

Why can I do (. Math PI) but not (.PI Math)?

pithyless05:10:55

PI is not an instance method of Math , so (.PI Math) fails. The former works, because (. class member) is a special form. See https://clojure.org/reference/java_interop for detailed explanation. Also, it would be more idiomatic to use Math/PI for static fields.

orpheus06:10:30

ahh.. I can’t do it because Math isn’t an instance, it’s a Class.. got it

orpheus06:10:35

=> (def p (java.awt.Point. 10 20))
=> (.x p)
=> 10
had me thinking I could just access fields like that

Stuart12:10:03

Hi, I have some json which stores the time as epoch seconds and nanoseconds. I want to convert this to a readable format, my problem is when I call json/decode I get a timestamp in the format 1.6346575360995734E9

(->> (slurp "proxy.log")
     (str/split-lines)
     (map json/decode)
     (first))

=>  {:level  "info"
     :ts     1.6346575360695734E9
     :caller "foobarquax:434"
     :msg    "Service started."}
How can I convert the long in that notation to a datetime ? I know I can do this:
(defn update-timestamp [s n]
  (-> s
      (java.time.Instant/ofEpochSecond)
      (.plusNanos n)
      (.toString)))
But how could I get the seconds and nanoseconds portion from 1.6346575360695734E9 ??

walterl14:10:14

(defn sec-subsec [x]
  {:sec (long (Math/floor x))
   :subsec (mod x 1)})

👍 1
Stuart14:10:21

thank you! WHy is it always so obvious once someone points it out 😄

sova-soars-the-sora14:10:53

mod x 1 Ain't there just a use for everything

walterl15:10:26

Or, if you care about the float drift:

(defn sec-nanos [x]
  {:sec (long (Math/floor x))
   :nanos (-> x (* 1e9) (mod 1e9) (long))})

Oitihjya Sen13:10:04

Hello everyone! I am a beginner learning CS through JavaScript. Here are a few things I have learnt along the way, using JS: http://otee.dev/ I have heard a lot about Clojure and LISP but I have not had the chance to learn Clojure. So I was wondering if I could get the best resources to get started. I also want to understand https://twitter.com/unclebobmartin/status/1449024435881656329?s=20, as opposed to something like JavaScript. Thanks for the help, glad to be here!

sova-soars-the-sora14:10:21

Rich Hickey has a talk called "Are we there yet" on InfoQ that is highly recommended. Uncle Bob has a mixed reception on these forums but we all love Clojure so that's cool.

Oitihjya Sen15:10:14

Thank you! I'll take a look at this.

roelof14:10:14

I have a challenge where I have to display 4 hours predicition of the weather.

roelof14:10:31

This is coming from th epenweather api in a json response

roelof14:10:12

but now I wonder how can I now best store the 4 hours and the 4 temperatures in a atom so the data on the website is updated

roelof14:10:40

could i best use a map maybe ?

walterl14:10:57

Vector of maps? Atoms can hold any value.

walterl14:10:59

Are you sure you need an atom, though?

roelof14:10:09

i think so, the lessons I follow uses a atom

Stuart15:10:06

how are you finding that site for learning clojurescript ?

roelof15:10:29

till now good

roelof15:10:00

but I think they misss the part to teach to solve the part to display a 4 hours prediction

Stuart15:10:22

its not clear to me from that site how the api works... (get-in resp ["list" 0 "main" "temp"]) = today, (get-in resp ["list" 8 "main "temp"]) = tomorrow... The only thing different is the 0 > 8, but I can't see why 8 = tomorrow...

Stuart16:10:45

ah, ok, so from the site:

You can search weather forecast for 4 days (96 hours) with data every hour by city name
So in your list of json data you get back, can you jsut take the first 4. Does the data come back already sorted?

pithyless16:10:30

^ it looks like the zipcode API returns a sorted list; each entry is every 3 hours; so 8*3 = 24 hours ahead 😅

Stuart16:10:32

What's happening here is

(get-in resp ["list" 0 "main" "temp"])
Is that its going into your resp and getting the value with item "list", which is itself a collection, then 0 gets the first, then from that it gets the value for "main", which is a map, and it gets "temp" from that map. So you can use 0, 1, 2, 3 etc to get the first 4

pithyless16:10:17

the hourly forecast looks to be a different api endpoint: https://openweathermap.org/api/hourly-forecast

pithyless16:10:38

(note /forecast/hourly?q= vs /forecast?q=)

☝️ 1
pithyless16:10:55

(group-by #(get % "dt_text") (get resp "list"))
^ may I suggest something like this, to make a new map where the data is grouped by the timestamp

pithyless16:10:55

@U0EGWJE3E I just realized if these are your first steps with CLJS, I suspect the intention of the exercise is to just replace the API forecast endpoint with the forecast hourly endpoint; and change handle-response function to take into account the first 4 entries from "list" instead of the ones that are now defined as today and tomorrow.

pithyless16:10:06

Feel free to post some code if you get stuck :]

roelof16:10:59

I do also not see where the 8 is coming from

roelof16:10:17

I see this a json

{"coord":{"lon":6.7931,"lat":52.2658},"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04d"}],"base":"stations","main":{"temp":285.4,"feels_like":284.82,"temp_min":284.71,"temp_max":286.69,"pressure":1015,"humidity":82},"visibility":10000,"wind":{"speed":1.79,"deg":192,"gust":4.47},"clouds":{"all":92},"dt":1635177482,"sys":{"type":2,"id":2010748,"country":"NL","sunrise":1635142540,"sunset":1635178671},"timezone":7200,"id":2754394,"name":"Hengelo","cod":200}

Stuart16:10:42

yeah, the 8 is just an index

roelof16:10:11

the challenge I try to solve is this :

Modify the app to display a forecast for the next 4 hours

roelof16:10:30

and within this call I cannot do this

Stuart16:10:50

yeah, like we've ssaid, you need to change the api call to get hourly

Stuart16:10:55

then just take the first 4 items

roelof16:10:09

so as far as I see I need to call this api key}

roelof16:10:50

oke, so you are saying I only have to store 4 temps

Stuart16:10:56

have you seen (get-in) before ? Here is where the 8 is coming from (I've tried to simplify a bit)

(let [resp {"list" [{:main {:temp 55 :rain false}}
                    {:main {:temp 33 :rain true}}
                    {:main {:temp 22 :rain false}}]}]
  (get-in resp ["list" 1 :main :temp]))
This returns 33, can you see why ? It gets "list" then the item at index 1, then :main then :temp of that item

roelof16:10:27

yep, I see

pithyless16:10:45

This api looks like the one you want: key}

pithyless16:10:09

where in "list" it returns a vector of items, one per hour

roelof16:10:18

oke, so I use the wrong api call

roelof16:10:36

aha, I use the free one

pithyless16:10:13

oh, I didn't realize one of them was paid...

roelof16:10:01

I think I can use the forecasr5 one

roelof16:10:22

oke, but still one thing I do not see

pithyless16:10:38

> Modify the app to display a forecast for the next 4 hours Well, I suppose you could "interpret" this to mean: take the first and second entry in the "list" - that will give you time now and 3 hours from now

pithyless16:10:48

^ using the free forecast5

roelof16:10:19

in the orginal one this is used

defonce app-state (r/atom {:title "WhichWeather"          ;; <1>
                            :latitude 0
                            :longtitude 0
                            :temperatures {:today {:label "Today"
                                                   :value nil}
                                           :tomorrow {:label "Tomorrow"
                                                      :value nil}}}))
to store 1 temp of today

roelof16:10:41

how do I change it so it can store 4 temps

pithyless16:10:20

So, first consider where you will get 4 temps from? Because the forecast5 returns a forecast for every 3 hours

pithyless16:10:23

Are you connected to a REPL?

roelof16:10:36

oke, if it's right I can also "store" the current one and the one for 3 hours

roelof16:10:59

you mean a figtweel repl or a "standard" one

roelof16:10:33

I my head I have to display the current one , over 1 hour, over 2 hours and so on

roelof16:10:00

but I think if I understand the current one and over 3 hours I can adapt things

☝️ 1
pithyless16:10:21

Try to run something like this on the response:

(map #(get % "dt_txt") (get resp "list"))

pithyless16:10:50

You'll notice the API returns something every 3 hours; so either you need a different API endpoint or to re-think the problem

roelof16:10:31

moment, I have to rewrite my call

roelof16:10:06

im going crazy

roelof16:10:11

`

clj꞉learn-cljs.weather꞉>(get-forecast!)
; Syntax error compiling at (output.calva-repl:38:25).
; Unable to resolve symbol: get-forecast! in this context

roelof16:10:28

im in the right namespace

roelof16:10:21

(ns ^:figwheel-hooks learn-cljs.weather
 (:require
   [goog.dom :as gdom]
   [reagent.dom :as rdom]
   [reagent.core :as r]
   [clj-http.client :as client]))


(defonce app-state (r/atom {:title "WhichWeather"          ;; <1>
                            :latitude 0
                            :longtitude 0
                            :temperatures {:today {:label "Today"
                                                   :value nil}
                                           :tomorrow {:label "Tomorrow"
                                                      :value nil}}}))

(defn handle-response [resp]
  (let [today (get-in resp ["list" 0 "main" "temp"])
        tomorrow (get-in resp ["list" 8 "main" "temp"])]
    (swap! app-state
           update-in [:temperatures :today :value] (constantly today))
    (swap! app-state
           update-in [:temperatures :tomorrow :value] (constantly tomorrow))))

(def api-key "fa9930ab5e2a87f9770d1c90d68f9da1")

(defn get-forecast! []
  (let [latitude (:latitude @app-state)
        longitude (:longitude @app-state)]
  (client/get ""
              {:accept :json
               :query-params {"q" "hengelo"

pithyless16:10:36

Make sure you evaluated the file

roelof16:10:13

I do not did that

pithyless16:10:39

are you in Calva? > Do this with Load Current File and Dependencies, ctrl+alt+c enter.

roelof16:10:11

yep, im in calva on a deps.edn and figwheel repl

pithyless16:10:55

did it work to load file and run the function?

roelof16:10:43

Evaluating file: weather.cljs
; Syntax error (FileNotFoundException) compiling at (weather.cljs:1:1).
; Could not locate goog/dom__init.class, goog/dom.clj or goog/dom.cljc on classpath.
; Evaluation of file weather.cljs failed: class clojure.lang.Compiler$CompilerException

🙌 2
pithyless17:10:23

so, the problem is Calva thinks it is compiling Clojure code, not ClojureScript code

pithyless17:10:49

this usually means that something went wrong with how the editor connected to the repl

pithyless17:10:17

I don't use Calva (or figwheel) myself, but this description in the README may be helpful: https://github.com/PEZ/fresh-figwheel-main#note-about-using-this-with-calva

pithyless17:10:08

I know this may seem like swimming upstream, but I encourage you to try to sort out the environment issues and get a working REPL in Calva first - it will make the rest of your learning journey far more enjoyable. There is also a #calva channel where you can ask questions if you get stuck.

roelof17:10:00

im crazy now

No such namespace: clj-http.client, could not locate clj_http/client.cljs, clj_http/client.cljc, or JavaScript source providing "clj-http.client" (Please check that namespaces with dashes use underscores in the ClojureScript file name) in file src/learn_cljs/weather.cljs

   1  (ns ^:figwheel-hooks learn-cljs.weather
   2    (:require
   3     [goog.dom :as gdom]
   4     [reagent.dom :as rdom]
   5     [reagent.core :as r]
   6     [clj-http.client :as client]))
          ^---
   7  
   8  
   9  (defonce app-state (r/atom {:title "WhichWeather"          ;; <1>
  10                              :latitude 0
  11                              :longtitude 0

pithyless17:10:31

What does your deps.edn look like?

roelof17:10:29

got it working

roelof17:10:44

use ajax.core again

roelof17:10:17

oke, I see now

(get-forecast!)
#object[Object [object Object]]

roelof17:10:29

so I think I have to use str

roelof17:10:32

even this is not helping

require '[clojure.pprint :as pp]
                #_=>          )
nil
learn-cljs.weather=> (pp/pprint(get-forecast!))
#object[Object [object Object]]

pithyless17:10:31

@U0EGWJE3E OK, so here's the thing

pithyless17:10:59

your function (get-forecast!) is probably returning a promise

pithyless17:10:07

but let's take a step back

pithyless17:10:43

in your weather.cljs file add a comment form:

(comment

  (+ 2 2)

#__)

pithyless17:10:16

and try to eval that (+ 2 2) via ctrl+enter - does that work for you?

pithyless17:10:44

you can even make sure it's evaling in JS by doing something silly like: (+ "2" 2)

roelof17:10:34

I do not think so , I never see 4 as answer

pithyless17:10:51

> Evaluate Current Top Level Form (defun), alt+enter.

pithyless17:10:07

hmm, do you see an error?

roelof17:10:09

I did now got the repl with clojure -A:fig:build

pithyless17:10:15

OK, so you can eval in the repl window, but not in the file directly?

roelof17:10:31

looks like it

pithyless17:10:43

But this works? > Do this with Load Current File and Dependencies, `ctrl+alt+c enter`.

pithyless17:10:03

Have you tried this, where you don't start a repl in the terminal - but instead you allow Calva to start it for you and follow these instructions: https://github.com/PEZ/fresh-figwheel-main#note-about-using-this-with-calva

roelof17:10:46

I will try that

roelof17:10:28

then I see this in my repl

clj꞉user꞉> 
; Evaluating file: weather.cljs

roelof17:10:45

and no prompt anymore

roelof17:10:29

maybe because of this

; Execution error (FileNotFoundException) at user/eval5624 (REPL:1).
Could not locate figwheel/main/api__init.class, figwheel/main/api.clj or figwheel/main/api.cljc on classpath.

pithyless17:10:35

yeah, that would do it;

pithyless17:10:42

what does your deps.edn look like?

roelof18:10:11

`

{:deps {org.clojure/clojure {:mvn/version "1.10.0"}
        org.clojure/clojurescript {:mvn/version "1.10.773"}
        reagent {:mvn/version "0.10.0" }
        cljs-ajax {:mvn/version "0.8.1"}}
 :paths ["src" "resources"]
 :aliases {:fig {:extra-deps
                  {com.bhauman/rebel-readline-cljs {:mvn/version "0.1.4"}
                   com.bhauman/figwheel-main {:mvn/version "0.2.11"}}
                 :extra-paths ["target" "test"]}
           :build {:main-opts ["-m" "figwheel.main" "-b" "dev" "-r"]}
           :min   {:main-opts ["-m" "figwheel.main" "-O" "advanced" "-bo" "dev"]}
           :test  {:main-opts ["-m" "figwheel.main" "-co" "test.cljs.edn" "-m" "learn-cljs.test-runner"]}}}

roelof18:10:34

that is the one given in the course

pithyless18:10:34

So, figwheel is there under aliases:

com.bhauman/figwheel-main {:mvn/version "0.2.11"}

pithyless18:10:46

> then I see this in my repl > clj꞉user꞉>  > ; Evaluating file: weather.cljs

pithyless18:10:54

^ which step in the process is this?

pithyless18:10:19

after #1 - #5 following that guide?

roelof18:10:22

after #5 and I choose the dev I see the error message

roelof18:10:56

the part you posted is after doing ctrl+alt+c enter``

roelof18:10:09

after # 5 of course

pithyless18:10:57

perhaps you need to add something like to your project?

pithyless18:10:20

just change {:main fresh-figwheel-main.core} to your namespace

pithyless18:10:59

so: {:main learn-cljs.weather}

roelof18:10:50

that one looks like this : `

^{:watch-dirs ["src"]
  :css-dirs ["resources/public/css"]
  :auto-testing true
   }
{:main learn-cljs.weather}

pithyless18:10:51

Yep, seems dev.cljs.edn is a figwheel-main thing: https://figwheel.org/

roelof18:10:29

he, I miss here the core part

pithyless18:10:31

yep, as in it worked; or it was already there? :)

pithyless18:10:56

(I'm doing a lot of guessing as a non-calva and non-figwheel user :P)

roelof18:10:01

no idea, i added it

pithyless18:10:38

OK, let's hope that's what was missing to get #5 to work

roelof18:10:29

I will start up a repl again

pithyless18:10:52

yep; do it per the instructions via Calva start + jack-in and not from the terminal

roelof18:10:14

yep, and no luck

roelof18:10:19

yep and a second time it seems to work

pithyless18:10:52

> Connected session: cljs, repl: dev ??

pithyless18:10:10

Or Connected session: clj

roelof18:10:33

and 3th time no luck

roelof18:10:51

Connected session: clj
; TIPS: 
;   - You can edit the contents here. Use it as a REPL if you like.
;   - `alt+enter` evaluates the current top level form.
;   - `ctrl+enter` evaluates the current form.
;   - `alt+up` and `alt+down` traverse up and down the REPL command history
;      when the cursor is after the last contents at the prompt
;   - Clojure lines in stack traces are peekable and clickable.
clj꞉user꞉> 
; Creating cljs repl session...
; Connecting cljs repl: deps.edn + Figwheel Main...
;   The Calva Connection Log might have more connection progress information.
; Starting cljs repl for: deps.edn + Figwheel Main...
; Execution error (FileNotFoundException) at user/eval5624 (REPL:1).
Could not locate figwheel/main/api__init.class, figwheel/main/api.clj or figwheel/main/api.cljc on classpath.
; Failed starting cljs repl
; Jack-in done.
clj꞉user꞉> 

roelof18:10:41

I choose that one because there is no choice clojure + figwheel main

pithyless18:10:47

it looks like that may have changed in the meantime and figwheel is explicitly required

pithyless18:10:24

Does Calva give an option where you can in fact specificy deps aliases?

pithyless18:10:53

If so, let's do the opposite of: > 3. Don't pick an alias <- This trips many people up, the :fig alias doesn't start things in the way Calva needs And let's tell it to use alias :fig (which includes the correct dependency)

pithyless18:10:03

Perhaps that will work? 🤞

roelof18:10:11

you are a genius

roelof18:10:18

; Starting cljs repl for: deps.edn + Figwheel

roelof18:10:10

finanly

clj꞉cljs.user꞉> 
; Evaluating file: weather.cljs
nil

pithyless18:10:40

Can we eval some code in a (comment ...) in our file?

roelof18:10:39

yep, that one gives nil

roelof18:10:15

and if I only do the (+ 2 2) part I see 4

pithyless18:10:32

right, evaling (comment (+ 2 2)) will give nil, but if you put your cursor anywhere inside (+ 2 2) and run alt+enter you should see 4

roelof18:10:10

but this :

(def api-key "xxxx")

(defn get-forecast! []                                     ;; <3>
  (let [postal-code (:postal-code @app-state)]
    (ajax/GET ""
      {:params {"q" postal-code
                "units" "imperial" ;; alternatively, use "metric"
                "appid" api-key}
       :handler handle-response})))

(get-forecast!)

roelof18:10:18

still gives a object 😞

pithyless18:10:42

awesome; as an up-and-coming Clojure developer you can now remember: no one writes code directly in the REPL window; you should just work in the files and namespaces directly; and things like (get-forecast!) keep inside a (comment ) so it doesn't get evaluated accidentally

pithyless18:10:13

so, we've come full-circle and can now work on our problem :)

roelof18:10:25

and when I do this in repl

(pp/pp (get-forecast!)) 
; WARNING: Use of undeclared Var learn-cljs.weather/  at line 1 <cljs repl>
; #object[SyntaxError SyntaxError: missing name after . operator]
; figwheel$repl$eval_javascript_STAR__STAR_@http://localhost:9500/cljs-out/dev/figwheel/repl.js:767:24
; @http://localhost:9500/cljs-out/dev/figwheel/repl.js:815:56
G__12961__2@http://localhost:9500/cljs-out/dev/cljs/core.js:36159:106
G__12961@http://localhost:9500/cljs-out/dev/cljs/core.js:36426:20
figwheel.repl.ws_connect.cljs$core$IFn$_invoke$arity$variadic/</<@http://localhost:9500/cljs-out/dev/figwheel/repl.js:1069:30
goog.events.EventTarget.prototype.fireListeners@http://localhost:9500/cljs-out/dev/goog/events/eventtarget.js:285:23
goog.events.EventTarget.dispatchEventInternal_@http://localhost:9500/cljs-out/dev/goog/events/eventtarget.js:383:26
goog.events.EventTarget.prototype.dispatchEvent@http://localhost:9500/cljs-out/dev/goog/events/eventtarget.js:196:34
goog.net.WebSocket.prototype.onMessage_@http://localhost:9500/cljs-out/dev/goog/net/websocket.js:114:8

pithyless18:10:29

REPL is probably in the wrong namespace; but feel free to ignore that - just eval code from the file and Calva keeps track of which namespace it should be in

roelof18:10:32

oke, so we getting some I think json which we destruct with the handle method

pithyless18:10:44

so here is my suggestion:

pithyless18:10:08

(comment
  (def raw-data (atom nil))
)

pithyless18:10:14

let's eval something like that

pithyless18:10:36

then we can change our function handler to something like this:

pithyless18:10:53

:handler (fn [resp] (reset! raw-data resp))

roelof18:10:04

that gives

#'learn-cljs.weather/raw-data

pithyless18:10:25

after you change the function, eval it as well

pithyless18:10:37

(that will update the definition of the function)

pithyless18:10:46

and then we can add some more code:

pithyless18:10:09

(comment
  (get-forecast!)
  
  @raw-data
)

pithyless18:10:21

so evaling the first thing will now run our new version of the function

pithyless18:10:36

and evaling the second should deref our data and we should see everything the API was returning

pithyless18:10:24

does that make sense? did it work?

roelof18:10:59

the handler change gives this :

#'learn-cljs.weather/get-forecast!

pithyless18:10:16

yep; just the compiler reporting that it updated the var

roelof18:10:25

this is not working

(comment
  (get-forecast!)
  
  @raw-data
)


(defn get-forecast! []                                     ;; <3>
  (let [postal-code (:postal-code @app-state)]
    (ajax/GET ""
      {:params {"q" postal-code
                "units" "metric" ;; alternatively, use "metric"
                "appid" api-key}
       :handler (fn [resp] (reset! raw-data resp))})))

roelof18:10:52

on the comment get-forecast and @raw-data is unknown

roelof18:10:13

on the get-forecast raw-data is now not known

pithyless18:10:03

try something like this:

(def raw-data (atom nil))

(defn get-forecast! ..)

(comment

  (get-forecast!)

  @raw-data
)

pithyless18:10:20

I think Calva clojure-lsp linter is complaining about order of things in the file

pithyless18:10:46

^ just make sure you always eval the top-forms when you add them so the REPL also knows about them

roelof18:10:12

this is given nil

(comment
  (def raw-data (atom nil)))
  

(defn get-forecast! []                                     ;; <3>
  (let [postal-code (:postal-code @app-state)]
    (ajax/GET ""
      {:params {"q" postal-code
                "units" "metric" ;; alternatively, use "metric"
                "appid" api-key}
       :handler (fn [resp] (reset! raw-data resp))})))

(comment
  (get-forecast!)
  
  @raw-data
)

pithyless18:10:37

when evaling the comment right? but what about when evaling the forms inside the comment?

pithyless18:10:07

try (def raw-data without the comment

pithyless18:10:17

and make sure you eval it first, then get-forecast!

pithyless18:10:33

so it updates the right pointer

pithyless18:10:14

(def raw-data (atom nil)) ;; <1>
  

(defn get-forecast! []                                     ;; <2>
  (let [postal-code (:postal-code @app-state)]
    (ajax/GET ""
      {:params {"q" postal-code
                "units" "metric" ;; alternatively, use "metric"
                "appid" api-key}
       :handler (fn [resp] (reset! raw-data resp))})))

(comment
  (get-forecast!)  ;; <3>
  
  @raw-data   ;; <4>
)

pithyless18:10:19

<order of eval>

roelof18:10:40

then I see this error on #3

; #object[Error Error: No protocol method IDeref.-deref defined for type undefined: ]

pithyless18:10:06

ah! it must have failed on @app-state

pithyless18:10:39

maybe start by reloading the the entire file

pithyless18:10:48

and then do 1 - 4

pithyless18:10:20

ah, I know the problem..

roelof18:10:37

i did that on see on the get-forecast a object

roelof18:10:48

and on @raw-data nil

pithyless18:10:22

perhaps you need to wait a bit and try to eval @raw-data again

pithyless18:10:37

it's an asynchronous http request, right?

roelof18:10:23

I think so because it s from a ajax lib

pithyless18:10:43

you can always try something like:

:handler (fn [resp] (println "Done!") (reset! raw-data resp))

roelof18:10:49

so now I have to wait till I see done

pithyless18:10:58

BTW, there is also an :error-handler if things go wrong, that by default should be printing to the browser console

roelof18:10:47

hmm,. I still do not see done

pithyless18:10:05

do you see anything in the browser Console window?

pithyless18:10:41

btw, you can test if you will see anything by just running (println "hello") and seeing if it shows up in the REPL or in the browser console window

roelof18:10:37

nope, I see only this as last in the repl or browser console #object[Object [object Object]]

pithyless18:10:09

OK, try to run this: (enable-console-print!) and then test hello again

roelof18:10:31

then I see something on the repl console

pithyless18:10:59

nice, so you can try running (get-forecast!) and seeing if DONE shows up

roelof18:10:16

then I see : hello world nil

roelof18:10:23

on the repl console

pithyless18:10:44

also this is a good time to mention: you can just always add (enable-console-print!) to your CLJS project (e.g. your main function) to make sure printing works as expected

roelof18:10:47

I see then only

cljs꞉learn-cljs.weather꞉> 
#object[Object [object Object]]

pithyless18:10:56

I have a sneaking suspicion, something may be going wrong with the request; let's add

:error-handler (fn [{:keys [status status-text]}] (println "error: " status " " status-text))

roelof19:10:05

nothing changed

roelof19:10:48

if I change app-state by a city I see output

roelof19:10:32

a very very big json fie

pithyless19:10:44

ah, so it must have been returning a valid 200 response; but no data

pithyless19:10:35

awesome; so that's our raw data

pithyless19:10:58

as long as you don't refresh the entire file, you can work and experiment on it without calling the API again

pithyless19:10:12

just by always derefing @raw-data

roelof19:10:25

and I know that the dt is a unix time stamp

roelof19:10:50

now I have to figure out which one I need

roelof19:10:03

i need 1 and maybe 21

roelof19:10:29

but to come back to my orginal question

roelof19:10:12

can I then both do

(swap! app-state
           update-in [:temperatures :today :value] (constantly today))

roelof19:10:41

but that means if I want to store 5 temps I also need 5 seperate places in my atom

roelof19:10:05

not very good if I ever want to change it to for example show 21 temps

pithyless19:10:38

I'd take a step back and first consider the kind of data I want to parse; I'd probably start with this:

(map-indexed (fn [idx item] [idx (get item "dt_txt")]) (get @raw-data "list"))

pithyless19:10:18

that should easily tell you what the data distribution looks like (without the big JSON blob)

roelof19:10:09

oke, and where schould I place that ?

pithyless19:10:31

just in a (comment ...) and eval it :)

roelof19:10:53

under the second comment instead of @rawdata ?

pithyless19:10:13

anywhere after you define raw-data is fine

pithyless19:10:50

since it's in a comment field it won't be normally evaled, so think of it like your personal scratchpad for taking notes and thinking through the problem

roelof19:10:00

then I see all normal dates instead of unix times

pithyless19:10:34

yeah, you can use "dt" if you prefer the epochs, but the normal dates seem nicer to work with

pithyless19:10:52

so you can answer questions like: how many items were returned and which one of these am I interested in?

roelof19:10:12

there are 39 entries

pithyless19:10:43

it explains why originally index 0 and 8 was used; but then the question is which one of these makes sense for me now?

roelof19:10:17

oke, in 4 hours from now it's 26-10-2021 0:14

roelof19:10:35

so 9 or 10

roelof19:10:07

9 is midnight and 10 is 3 o clock in the eveing

roelof19:10:42

this is working in blocks of 3 hours so I think not the right api

pithyless19:10:57

erm, isn't it the second entry for midnight?

roelof19:10:33

nope

[0 "2021-10-25 21:00:00"]
 [1 "2021-10-26 00:00:00"]
 [2 "2021-10-26 03:00:00"]
 [3 "2021-10-26 06:00:00"]
 [4 "2021-10-26 09:00:00"] 

roelof19:10:41

and it's now 21:18

roelof19:10:09

but to come back to my original question

roelof19:10:39

if iI find the right api I can update the handler and I have solved this challenge according to you

pithyless19:10:31

yep, or perhaps the original intention was not to look for a different API but to answer the question: what is the prediction now and in roughly 3-4 hours (vs in 24 hours)

roelof19:10:42

so no four entries to let see all temps of a period of 4 hours

roelof19:10:56

but only 2

pithyless19:10:10

yeah, seems kind of a stretch since that hourly API seems to be behind a paywall

roelof19:10:20

I can do a hourly one so over 4 hours with this one https://openweathermap.org/api/one-call-api

roelof19:10:13

I have then a extra call to convert a zipcode to a lat and lon

pithyless19:10:08

OK, so one more thing I would mention before I leave you: it would be a shame to waste all this REPL potential to just go back to modifying state everywhere

pithyless19:10:39

so I recommend learning to develop code where you do an API call and store it somewhere temporarily, like we've done with @raw-data

roelof19:10:55

but to make things challenging. to display all 4 with the knowledge I have to make 4 update-in

roelof19:10:00

and not use a collection

pithyless19:10:01

next, write functions that take the value of that response and return new data

pithyless19:10:19

and then write a final function that calls swap! or update!

pithyless19:10:43

b/c those middle functions you can test interactively in the REPL without actually changing the app-state

roelof19:10:57

oke, time to leave here almost time to sleep

pithyless19:10:00

> but to make things challenging. to display all 4 with the knowledge I have to make 4 update-in so how do you want to keep track of this?

pithyless19:10:01

perhaps:

:temperatures [{:timestamp "..." :value ...}
               {:timestamp "..." :value ...}]

roelof19:10:21

I thought of this :

(defonce app-state (r/atom {:title "WhichWeather"          ;; <1>
                            :latitude 0
                            :longtitude 0
                            :temperatures {:current {:label "Current"
                                                   :value nil}
                                           :oneHour {:label "1 hour"
                                                     :value nil}

}}))

pithyless19:10:51

that's fine, but won't scale great if you want to store 4 or 20 hours

roelof19:10:05

yep, I know

roelof19:10:21

oke I have to use a list

roelof19:10:47

or a map I think where I use the hour as key and the temp as value

pithyless19:10:47

if you want to go down that route, I'd probably do:

:temperatures {"timestamp": {:label "...", :value ...}
               "timestamp": {:label "...", :value ...}}

pithyless19:10:01

where timestamp can be that string timestamp or the integer epoch

pithyless19:10:16

yeah, or just hour as key and temp as value

pithyless19:10:19

all valid options

roelof19:10:31

oke, but that means if I have twenty one I have add some 20 entries

roelof19:10:50

but as I said time to sleep here

roelof19:10:12

I fall almost at asleep behind the computer 😛

pithyless19:10:13

sure, but you would write a function that returns that entire datastructure; and then you need only one update!

pithyless19:10:34

^ I meant swap!

pithyless19:10:39

OK, good night and good luck :)

roelof19:10:59

first tommorow time to try to make it work with 2 entries and another api

👍 1
roelof19:10:12

where I can exactly use 4 hours

roelof19:10:39

and you thanks for all the patience. We were very long busy to come to this point

pithyless19:10:54

I'm just glad we got your env setup; I hope it will pay off handsomely. Make sure to find some youtube videos where people talk about "REPL driven development" and you'll start to understand why Clojure developers are so keen on making sure your setup works :)

roelof05:10:05

sorry, I need your help one more time I have this respons

"hourly": [
    {
      "dt": 1618315200,
      "temp": 282.58,
      "feels_like": 280.4,
      "pressure": 1019,
      "humidity": 68,
      "dew_point": 276.98,
      "uvi": 1.4,
      "clouds": 19,
      "visibility": 306,
      "wind_speed": 4.12,
      "wind_deg": 296,
      "wind_gust": 7.33,
      "weather": [
        {
          "id": 801,
          "main": "Clouds",
          "description": "few clouds",
          "icon": "02d"
        }
      ],
      "pop": 0
    },

roelof05:10:37

How do I change this

(map-indexed (fn [idx item] [idx (get item :dt)]) (get @raw-data :hourly))
to give me a list of dates again

roelof05:10:24

I can do this (map-indexed (fn [idx item] [idx (get item "dt")]) (get @raw-data "hourly"))

roelof05:10:35

but then I see all the dates in unix time

pithyless06:10:40

@U0EGWJE3E if I understand correctly, you don't get back "dt_txt" so you can't retrieve it from the map directly; but you can try converting the epoch time to an actual date if you want to make it more readable

roelof06:10:02

yep, please

pithyless06:10:37

(js/Date. (* 1000 (get item "dt")))

roelof06:10:48

that looks a lot better

([0 #inst "2021-10-26T06:00:00.000-00:00"]
 [1 #inst "2021-10-26T07:00:00.000-00:00"]
 [2 #inst "2021-10-26T08:00:00.000-00:00"]
 [3 #inst "2021-10-26T09:00:00.000-00:00"]
 [4 #inst "2021-10-26T10:00:00.000-00:00"]
 [5 #inst "2021-10-26T11:00:00.000-00:00"]
 [6 #inst "2021-10-26T12:00:00.000-00:00"]
 [7 #inst "2021-10-26T13:00:00.000-00:00"]
 [8 #inst "2021-10-26T14:00:00.000-00:00"]
 [9 #inst "2021-10-26T15:00:00.000-00:00"]

roelof06:10:11

So I need now 2 and 6 because it is now 8.13 here

pithyless06:10:32

No, I think the API is returning time in UTC

pithyless06:10:01

(it's 6:14 now in UTC time; you're in a +2 timezone)

roelof06:10:31

then I need 1 and 5 🙂

pithyless06:10:10

got to run; good luck, but feel free to post questions here (or on #beginners)

pithyless06:10:41

BTW, if you haven't tried it yet; try running Calva: Fire up the "Getting Started" REPL in vscode - there are a couple of files that give you good hints on how to use calva, paredit, rich comments, etc

roelof06:10:22

I use calva also on solving exercism challenges

pithyless06:10:27

anywho, I'm off for a while

roelof09:10:36

and im back from sport

sheluchin17:10:17

How do people feel about vertically aligning maps?

deactivateduser17:10:07

I personally like them as I find them substantially more readable, though my experience has been that this isn’t considered idiomatic.

👍 1
deactivateduser17:10:47

Though now I think about it, what exactly do you mean by “vertical alignment”? I was envisaging something like this:

{
  :first-key            "first value"
  :second-key           "second value"
  :key-with-a-long-name "this is a longer value"
}

deactivateduser17:10:30

(I’ve heard this referred to as “tabular alignment” elsewhere, fwiw)

sheluchin17:10:31

@U0MDMDYR3 yep, that's what I meant.

deactivateduser17:10:01

Cool. Ok then I like it and also use it in let forms etc.

☝️ 1
deactivateduser17:10:53

But then as a solo programmer these days I don’t have to worry so much about conforming to the herd’s opinions on things like this. 😉 If I were still working in a team I’d line up with whatever the team’s standards are, however.

pithyless17:10:53

Some people drink tea, others coffee; some put milk in their tea while others consider that disgusting. If we can't get people to agree on that, what hope is there for non-enforceable vertical alignment? :) I'm a fan, but I would never put milk in my tea.

deactivateduser17:10:41

@U05476190 without ratholing, I’m an equal opportunity hot beverage enjoyer - I’ll drink (and enjoy) any/all of the above, depending on my mood. 😉

deactivateduser17:10:53

Just no sugar. Sugar is the devil. 😉

😂 1
Alex Miller (Clojure team)17:10:43

I was going to say "bemused" but I realize that might not be helpful

hiredman17:10:19

"my alignment chart goes from lawful good to chaotic evil, I can't find vertical on there"

Alex Miller (Clojure team)17:10:26

I say, if it helps you, then go for it!

Alex Miller (Clojure team)17:10:53

I think all of the choices are within the range of reasonable style :)

sheluchin17:10:02

I never bothered in Python; just wanted to see how they do when in Rome 🙂

sheluchin17:10:34

I think maybe Cursive does it by default so there's a good chunk of repo code out there that does use vertical alignment.

dpsutton17:10:09

it can occasionally introduce larger diffs and have had nasty merge conflict before. But overall i do find it aids in reading. On your own projects find out what you prefer. On a shared codebase its usually match existing style

☝️ 1
sheluchin17:10:08

Ah, on the point of larger diffs, it bothers me that Clojure convention does not put a line break after the last item in a collection and its closing paren. It leads to lines showing up in diffs when they had nothing to do with the change. Just to nitpick a little..

Alex Miller (Clojure team)17:10:40

I put that fault on diffs :)

sheluchin17:10:18

Yes, true. I've also wished for a semantic git diff 🙂

Alex Miller (Clojure team)17:10:03

with git, I typically use --word-diff=color when looking at diffs, which often helps

pithyless17:10:03

In certain spots where I'd like that hanging paren (e.g. the end of a rich-comment-form or some really big EDN vector), I've done something like this:

(lots
 of
 stuff
 #__)

sheluchin17:10:46

Looks like using a ,) is quite popular.

pithyless17:10:19

I've found some linters and prettifiers remove the comma; but none touch my #__

pithyless17:10:27

also one spot where vertical alignment is suboptimal is when destructuring in a let, because that would tend to push everything far to the right

pavlosmelissinos17:10:34

Maybe what we need is an s-expression-aware git

👍 2
dpsutton17:10:37

Someone is working on that I think. Difftastic? Wilfred Hughes I think

pavlosmelissinos17:10:58

Oh wow, I thought it would be a lisp-specific thing but it even supports languages with actual syntax. That's crazy

sheluchin17:10:27

Yep, pretty cool. Thanks for the link.

1
Jim Strieter18:10:45

I am trying to understand this syntax: (defn f [{:keyword1 "some-str" :keyword2 "another-str"}] ...) What does this accomplish?

hiredman18:10:02

nothing, that isn't valid, the inner most [] are likely supposed to be {}

hiredman18:10:36

and even then it doesn't make sense as a binding form

hiredman18:10:23

https://clojure.org/guides/destructuring is maybe the feature you are trying to undestand

hiredman18:10:32

even with the curly braces it still isn't valid

user=> (defn f [{:keyword1 "some-str" :keyword2 "another-str"}])
Syntax error macroexpanding clojure.core/defn at (REPL:1:1).
{:keyword1 "some-str", :keyword2 "another-str"} - failed: vector? at: [:fn-tail :arity-n :bodies :params] spec: :clojure.core.specs.alpha/param-list
({:keyword1 "some-str", :keyword2 "another-str"}) - failed: Extra input at: [:fn-tail :arity-1 :params] spec: :clojure.core.specs.alpha/param-list
user=>

Jim Strieter18:10:37

Here is an actual function (in bold) with a bit of context: (def routes [["/" html-handler] ["/echo/:id" {:get (fn [{{:keys [id]} :path-params}] (response/ok (str "&lt;p&gt;The value is: " id "&lt;/p&gt;\n")))}] ["/api" {:middleware [wrap-formats]} ["/multiply" {:post (fn [{{:keys [a b]} :body-params}] (response/ok {:result (* a b)}))}]]]) Outer function (routes) is a ring routing handler - I think. Anonymous function inside is the one with the curious syntax, but I see the same syntax over & over in Sotnikov

hiredman18:10:55

yeah, that is destructuring

Jim Strieter18:10:12

How does destructuring work?

hiredman18:10:31

which is basically a dsl for pulling apart datastructures and binding names to various parts of them

hiredman18:10:54

I linked you to the http://clojure.org destructuring guide

👍 1
Jim Strieter19:10:37

I'm looking at the page @hiredman linked to a few minutes ago Midway through, there is this example of destructuring:

(let [{{:keys [class weapon]} :joe} multiplayer-game-state]
  (println "Joe is a" class "wielding a" weapon))
;= Joe is a Ranger wielding a Longbow
What is this syntax called: {{:keys [class weapon]} :joe} ?

hiredman19:10:41

That is destructuring

hiredman19:10:14

It is nested associate destructuring

hiredman19:10:51

The outer {} introduces destructuring of the outer most map, the inner introduces destructuring of the inner map

Jim Strieter19:10:59

That's what I was looking for 🙂

hiredman19:10:59

The inner most destructuring is using the :keys shortcut for binding names that are keys, the outermost destructuring is not using the shortcut, because it is not binding the value of the key :Joe but further destructuring the value

Jim Strieter19:10:24

When you use the :keys shortcut for binding names that are keys, what exactly does that mean? For instance, does that mean anything that's a key gets bound to the same name without the :? a = (:a my-map) b = (:b my-map) etc?

manutter5119:10:33

(let [{:keys [foo bar]} {:foo "hello" :bar "world"}]
  (str foo ", " bar))
==> "hello, world"

manutter5119:10:17

And just for context, I've been doing Clojure for a good few years now, and I would write that as

(let [player (:joe multiplayer-game-state)
      {:keys [class weapon]} player]
      ...)

manutter5119:10:21

I can understand the nested destructuring, but I almost never use it in everyday programming because I want future maintainers (including me) to be able to read my code as easily as possible, and nested destructuring forces me to stop and think.

Jim Strieter19:10:28

I'm not saying it's good or bad. I'm only trying to understand an example.

manutter5119:10:35

Right, I just like to bring this up as context, since this is #beginners. Sometimes when I'm just starting out, I have trouble knowing what's "this is how we do things" and what's just "here's a cool feature that you may not use very often but it's good to know anyway."

Jim Strieter11:10:40

Absolutely makes sense. On the one hand I appreciate the mathematical brilliance of it. On the other hand, too many layers get unreadable very quickly.

Colin P. Hill21:10:04

If I have to make a series of transformations to a single value, but its position in the forms varies (so I can’t easily use -> or ->>), what would be an idiomatic way to flatten out the stack of transformations? Right now I’m just repeatedly re-binding to the same symbol in a let block, but it feels a little clunky.

dpsutton21:10:44

you can use (as-> ...) to thread where you like. Although good advice to figure out why its position changes so much and perhaps do a little cleanup

Colin P. Hill21:10:05

Oh perfect, didn’t know about that one, thanks

Colin P. Hill21:10:27

I know the need for this is a bit of a smell, but there’s only so much cleanup I can do while I’m in this file

Colin P. Hill21:10:08

Well, doing that refactor made it easier to see a way I could do it with ->, so it’s a wash 😆 Was still useful as that intermediate refactor though

seancorfield21:10:55

as-> can be useful for a single problematic step in the middle of an otherwise very clean -> pipeline but should be used on its own (the "weird" argument order is specifically for use inside ->).

dpsutton21:10:41

did stuart write about this? I've heard the advice but never understood the reasoning

dpsutton22:10:37

> Outside of ->, the arguments to as-> appear in the order value name rather than name value, making it unlike anything else in Clojure. > Therefore, I can confidently say one should never use as-> by itself,

dpsutton22:10:41

i don't buy it

seancorfield22:10:19

as-> was specifically designed to work inside -> pipelines.

seancorfield22:10:40

That's what Rich said about it on the mailing list when it was originally introduced (years ago now).

seancorfield23:10:35

(-> thing
    (process)
    (as-> sym
          (do-stuff :to sym 42))
    (more-processing))

dpsutton23:10:05

I get that. But I don’t see how that gives any evidence to it’s wrong to use it outside of a threading context

2
dpsutton23:10:23

To me it seems to fit it’s original purpose and others just fine

seancorfield23:10:24

I'm tired of arguing with people about this.

seancorfield23:10:41

Using as-> on its own is just wrong.

dpsutton23:10:56

I didn’t mean to be contentiousness

seancorfield23:10:08

Go search the mailing list for Rich's discussion about it, when it was introduced.

seancorfield23:10:26

I wish they'd put this in the bloody docstring so folks wouldn't keep arguing about it 😞

Colin P. Hill12:10:03

I was curious about this. I think I found the mailing list conversation in question: https://groups.google.com/g/clojure/c/67JQ7xSUOM4/m/AoxwLmfDJNwJ

🙏 1
Colin P. Hill12:10:23

It does explain the purpose of the macro as being insertion into a more conventional threading pipeline. It does not give a substantive reason that other uses would be wrong.

yi21:10:45

i have `[1 0 1 0 1 0 1]`. how to transform that into binary `2r1010101`?

Alex Miller (Clojure team)22:10:32

what type do you want at the end? a string? an integer?

yi22:10:58

first a want the literal 2r1010101 , then I'll convert that to decimal

Alex Miller (Clojure team)22:10:35

that is a format that can be read, but nothing ever prints that way as a literal

🙌 1
Alex Miller (Clojure team)22:10:52

(Long/parseLong (apply str bits) 2) will give you the decimal integer

yi22:10:43

thank you!

Alex Miller (Clojure team)22:10:51

you can use (Long/toBinaryString 85) to get "1010101"

Alex Miller (Clojure team)22:10:44

(read-string (str "2r" (apply str bits))) would be another approach (but this is really doing more steps to do the same thing above)