This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2021-10-25
Channels
- # announcements (14)
- # aws (1)
- # babashka (23)
- # beginners (442)
- # calva (50)
- # chlorine-clover (1)
- # cider (32)
- # clojure (124)
- # clojure-europe (35)
- # clojure-france (5)
- # clojure-gamedev (5)
- # clojure-nl (2)
- # clojure-portugal (3)
- # clojure-uk (4)
- # clojurescript (56)
- # conjure (5)
- # cursive (24)
- # datalevin (1)
- # datomic (57)
- # fulcro (35)
- # helix (15)
- # holy-lambda (8)
- # introduce-yourself (1)
- # jobs (5)
- # kaocha (1)
- # lsp (99)
- # malli (10)
- # music (1)
- # off-topic (22)
- # pathom (38)
- # podcasts-discuss (10)
- # polylith (10)
- # reitit (1)
- # releases (1)
- # remote-jobs (4)
- # shadow-cljs (18)
- # spacemacs (6)
- # tools-build (22)
- # vim (66)
- # xtdb (22)
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 hereJust 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?
can maybe wrap your response with dissoc
?
(dissoc (ajax/GET...) "minutely" "current" "daily" "alerts")
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")))
no, On the website of openweather api is stated that I can use exclude to exclude parts of the respons
According to https://openweathermap.org/api/one-call-api the arguments to "exclude" should be "a comma-delimited list (without spaces)".
Does something like this work for you?
{"q" postal-code
"units" "metric"
"appid" "xxxxxx"
"exclude" "minutely,current,daily,alerts"}
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}}))
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}})))
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).
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 workOh, I see. There is a syntax error in the let
binding. Combine the two binding vectors into one
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
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.
=> (def p (java.awt.Point. 10 20))
=> (.x p)
=> 10
had me thinking I could just access fields like thatHi, 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
??mod x 1
Ain't there just a use for everything
Or, if you care about the float drift:
(defn sec-nanos [x]
{:sec (long (Math/floor x))
:nanos (-> x (* 1e9) (mod 1e9) (long))})
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!
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.
Thank you! I'll take a look at this.
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
till now I have only learned to write clojurescript like this : https://www.learn-clojurescript.com/section-1/lesson-8-capstone-weather-forecasting-app/
but I think they misss the part to teach to solve the part to display a 4 hours prediction
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...
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?^ it looks like the zipcode API returns a sorted list; each entry is every 3 hours; so 8*3 = 24 hours ahead 😅
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 4the hourly forecast looks to be a different api endpoint: https://openweathermap.org/api/hourly-forecast
(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@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
.
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}
the challenge I try to solve is this :
Modify the app to display a forecast for the next 4 hours
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 itemhttps://openweathermap.org/api/hourly-forecast vs https://openweathermap.org/forecast5
> 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
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 todaySo, first consider where you will get 4 temps from? Because the forecast5 returns a forecast for every 3 hours
Try to run something like this on the response:
(map #(get % "dt_txt") (get resp "list"))
You'll notice the API returns something every 3 hours; so either you need a different API endpoint or to re-think the problem
`
clj꞉learn-cljs.weather꞉>(get-forecast!)
; Syntax error compiling at (output.calva-repl:38:25).
; Unable to resolve symbol: get-forecast! in this context
(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"
are you in Calva?
> Do this with Load Current File and Dependencies, ctrl+alt+c enter
.
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
so, the problem is Calva thinks it is compiling Clojure code, not ClojureScript code
this usually means that something went wrong with how the editor connected to the repl
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
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.
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
even this is not helping
require '[clojure.pprint :as pp]
#_=> )
nil
learn-cljs.weather=> (pp/pprint(get-forecast!))
#object[Object [object Object]]
@U0EGWJE3E OK, so here's the thing
But this works? > Do this with Load Current File and Dependencies, `ctrl+alt+c enter`.
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
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.
`
{: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"]}}}
So, figwheel is there under aliases:
com.bhauman/figwheel-main {:mvn/version "0.2.11"}
I notice that repo has this file: https://github.com/PEZ/fresh-figwheel-main/blob/master/dev.cljs.edn
that one looks like this : `
^{:watch-dirs ["src"]
:css-dirs ["resources/public/css"]
:auto-testing true
}
{:main learn-cljs.weather}
Yep, seems dev.cljs.edn
is a figwheel-main thing: https://figwheel.org/
yep; do it per the instructions via Calva start + jack-in and not from the terminal
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꞉>
it looks like that may have changed in the meantime and figwheel is explicitly required
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)
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
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!)
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
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
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
and evaling the second should deref our data and we should see everything the API was returning
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))})))
try something like this:
(def raw-data (atom nil))
(defn get-forecast! ..)
(comment
(get-forecast!)
@raw-data
)
^ just make sure you always eval the top-forms when you add them so the REPL also knows about them
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
)
when evaling the comment right? but what about when evaling the forms inside the comment?
(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>
)
then I see this error on #3
; #object[Error Error: No protocol method IDeref.-deref defined for type undefined: ]
you can always try something like:
:handler (fn [resp] (println "Done!") (reset! raw-data resp))
BTW, there is also an :error-handler
if things go wrong, that by default should be printing to the browser console
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
nope, I see only this as last in the repl or browser console #object[Object [object Object]]
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
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))
as long as you don't refresh the entire file, you can work and experiment on it without calling the API again
can I then both do
(swap! app-state
update-in [:temperatures :today :value] (constantly today))
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"))
that should easily tell you what the data distribution looks like (without the big JSON blob)
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
yeah, you can use "dt" if you prefer the epochs, but the normal dates seem nicer to work with
so you can answer questions like: how many items were returned and which one of these am I interested in?
it explains why originally index 0
and 8
was used; but then the question is which one of these makes sense for me now?
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"]
if iI find the right api I can update the handler and I have solved this challenge according to you
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)
I can do a hourly one so over 4 hours with this one https://openweathermap.org/api/one-call-api
see this page : https://openweathermap.org/api/one-call-api
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
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
but to make things challenging. to display all 4 with the knowledge I have to make 4 update-in
b/c those middle functions you can test interactively in the REPL without actually changing the app-state
> 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?
perhaps:
:temperatures [{:timestamp "..." :value ...}
{:timestamp "..." :value ...}]
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}
}}))
if you want to go down that route, I'd probably do:
:temperatures {"timestamp": {:label "...", :value ...}
"timestamp": {:label "...", :value ...}}
sure, but you would write a function that returns that entire datastructure; and then you need only one update!
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 :)
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
},
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 againI can do this (map-indexed (fn [idx item] [idx (get item "dt")]) (get @raw-data "hourly"))
@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
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"]
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
and there's also this - https://github.com/PEZ/rich4clojure
I personally like them as I find them substantially more readable, though my experience has been that this isn’t considered idiomatic.
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"
}
(I’ve heard this referred to as “tabular alignment” elsewhere, fwiw)
@U0MDMDYR3 yep, that's what I meant.
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.
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.
@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. 😉
I was going to say "bemused" but I realize that might not be helpful
"my alignment chart goes from lawful good to chaotic evil, I can't find vertical on there"
I say, if it helps you, then go for it!
I think all of the choices are within the range of reasonable style :)
I think maybe Cursive does it by default so there's a good chunk of repo code out there that does use vertical alignment.
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
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..
I put that fault on diffs :)
with git, I typically use --word-diff=color
when looking at diffs, which often helps
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
#__)
I use #_()
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
Oh wow, I thought it would be a lisp-specific thing but it even supports languages with actual syntax. That's crazy
I am trying to understand this syntax: (defn f [{:keyword1 "some-str" :keyword2 "another-str"}] ...) What does this accomplish?
https://clojure.org/guides/destructuring is maybe the feature you are trying to undestand
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=>
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 "<p>The value is: " id "</p>\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
How does destructuring work?
which is basically a dsl for pulling apart datastructures and binding names to various parts of them
Oh cool
Thanks! 🙂
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} ?The outer {} introduces destructuring of the outer most map, the inner introduces destructuring of the inner map
That's what I was looking for 🙂
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
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?
(let [{:keys [foo bar]} {:foo "hello" :bar "world"}]
(str foo ", " bar))
==> "hello, world"
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]
...)
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.
I'm not saying it's good or bad. I'm only trying to understand an example.
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."
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.
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.
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
Oh perfect, didn’t know about that one, thanks
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
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
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 ->
).
did stuart write about this? I've heard the advice but never understood the reasoning
> 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,
as->
was specifically designed to work inside ->
pipelines.
That's what Rich said about it on the mailing list when it was originally introduced (years ago now).
(-> thing
(process)
(as-> sym
(do-stuff :to sym 42))
(more-processing))
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
I'm tired of arguing with people about this.
Using as->
on its own is just wrong.
Go search the mailing list for Rich's discussion about it, when it was introduced.
I wish they'd put this in the bloody docstring so folks wouldn't keep arguing about it 😞
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
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.
what type do you want at the end? a string? an integer?
that is a format that can be read, but nothing ever prints that way as a literal
(Long/parseLong (apply str bits) 2)
will give you the decimal integer
you can use (Long/toBinaryString 85)
to get "1010101"
(read-string (str "2r" (apply str bits)))
would be another approach (but this is really doing more steps to do the same thing above)