This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-11-21
Channels
- # announcements (10)
- # aws (10)
- # babashka (23)
- # beginners (111)
- # biff (8)
- # calva (25)
- # clj-kondo (9)
- # cljsrn (4)
- # clojure (72)
- # clojure-belgium (6)
- # clojure-europe (50)
- # clojure-germany (2)
- # clojure-nl (1)
- # clojure-norway (6)
- # clojure-uk (1)
- # datahike (3)
- # emacs (10)
- # graalvm (19)
- # graphql (3)
- # juxt (7)
- # kaocha (9)
- # malli (23)
- # nbb (20)
- # pathom (17)
- # pedestal (6)
- # polylith (11)
- # portal (8)
- # remote-jobs (3)
- # shadow-cljs (18)
- # sql (3)
- # tools-deps (20)
hi. I'm trying to use calva for evaluating my env.edn
, but it's giving me a file not found error. I tried using this with the file I'm writing the logic in and it can't find that. how would I fix this?
In the case of a string argument to slurp
representing a file path, the path will typically be relative to the working directory where the repl is running. In this case, I'd presume that to be the directory containing the src
dir, so you could provide an absolute path or a relative path based from the working dir.
thank you. I just moved the clj files into src/clj/shorturl/<env.clj, env.edn>. I'm now seeing another error: ; Unable to resolve symbol: slurp in this context
(ns shorturl.env)
(def envvars (clojure.edn/read-string (slurp "deps.edn")))
envvars
(def env [k]
(or (k envvars) (System/getenv (name k))))
(System/getenv "DATABASE_URL")
I see that the folder hierarchy has cljs
in it, but the file extension is clj
, so it's not immediately obvious whether clojure or clojurescript is being used, but I don't think cljs has slurp
(I would guess because javascript doesn't always have access to the filesystem, e.g. when in browsers)
You can take look at this https://github.com/weavejester/environ about environment
i found that the problem was having this in my deps.edn:
:aliases {:cljs
{:extra-deps
{thheller/shadow-cljs {:mvn/version "2.19.9"}
lilactown/helix {:mvn/version "0.1.8"}}}}
oh. it looks like I may need to have evaluated some require statements? the tooling is horrible
there are a lot of moving parts. IME (three weeks of Clojure) starting simple, adding one part after you understand the prior ones, is more important than in other programming environments.
I don't use Calva though so not sure exactly where your main trouble is. If it's resolving a file I recommend the suggestion to use the absolute file path as suggested above.
just note that if you package your app into a jar later you'll want to understand paths relative to your project
Is there a particular order that you recommend? Is it just the order presented in the official docs? I have previous experience in other languages and would hope to transfer as much as that experience over as possible and then fill out the clojure-specific gaps as needed. I've always assumed that this top-down approach worked best generally
Can you share some of the code (e.g. on GitHub) and the commands you're using to start the REPL? Trying to apply what you know from other languages might not help you with Clojure. I'd suggest starting minimal with a trivial project before you start adding moving parts... If you insist on doing a web-based project first, take a look at some of the getting started repos, e.g. https://github.com/seancorfield/usermanager-example or https://github.com/PrestanceDesign/usermanager-reitit-integrant-example Clojure doesn't obfuscate the web tech stack behind easy frameworks so (unfortunately for beginners) it effectively requires you to understand all the moving parts. There is definitely room for improvement but this is a feature imho.
Oh and consider voicing your frustrations at #C02CPM2R1KK. I'm sure it would greatly benefit the community if you could take the time to do that 🙂
> I'm now seeing another error: ; Unable to resolve symbol: slurp in this context
This probably means you haven't evaluated the file yet. The REPL isn't ready before this is done. (Which is a bit unfortunate, but hard to fix in some reliable way.) It's the reason why https://calva.io/try-first/ starts out with:
> You should start with loading the file you are working with. Do this with Load/Evaluate Current File and its Requires/Dependencies, ctrl+alt+c enter
.
@UEQPKG7HQ thank you. I'll review those. yeah. there's not much hiding behind abstractions with clojures, I noticed, so it's hard for me to opt into knowing the guts down the road when I'm ready. I did share everything, more or less, in this thread and there is going to be a lot of PEBCAK . I'm not sure if the high barrier to entry serves as a gating mechanism and is a feature. Honestly, I think the build tool experience is considerably easier to get into than other jvm-related languages, especially with android development, so I thank everyone for that.
@U0ETXRFEW yes. that was it. thank you.
> I'm not sure if the high barrier to entry serves as a gating mechanism and is a feature. It does not. It is not. There's a balance to be struck with other goals, but no-one is putting up any barrier on purpose. Quite the contrary, the community in general, including the language stewards, want the getting started story to be great. There's a channel on this slack where you should consider to contribute your observations: #improve-getting-started
The high barrier to entry is definitely not a feature, for sure! It's more like a side effect of striving for good design I think. Clojure has great individual components for pretty much everything but the web story in particular has a lot of moving parts and imho needs a modular project that will bring everything together with sane defaults (great for beginners) but at the same time allow you to switch/add/remove components when you better understand them and realize you need something different/more. We can take some pages from other languages' books but imo most frameworks are not very well designed (they cap out at some point), so I think Clojure people are bit reluctant to bring any of the existing solutions over to the clojure side. We're all still trying to figure it out! Like @U0ETXRFEW said nobody is gatekeeping and the community is very open to criticism and feedback. We would all like to make the tools less horrible 🙂
Hey guys, I am trying to understand dirs/files structure and bootstrap steps when I develop a simple Clojure+ClojureScript app.
First I start to implement ring
, reitit
- it was in clj
files. And I run this backend side via leiningen
.
Now I am reading reagent
docs and tutorials and there are cljs
files everywhere.
Am I right, that I should put my backend logic (application startup, routes, rendering html, db work, business logic) inside clj
files, and any "view" logic with reagent
into cljs
files and compile them via shadow-cljs
? So backend and frontend will live together side by side?
Final steps in my mind look like this:
1. Develop backend in clj
files as I would do this w/o frontend
2. Compile cljs
frontend into output js
file via shadow-cljs
3. Use that js
file in html
template, that I will render on backend part
Having .cljs
and .clj
files together in a single web app repo is fine. Folks used to separate them out as src/clj
and src/cljs
but that's not as common these days.
If you want to write any logic you need to share between the front end and back end, you can write that in .cljc
files -- which can be treated as both .cljs
and as .clj
.
is it good solution to write both front and back logic inside 1 file? I imagine a classic SPA when on backend I dont toch any frontend logic and just render initial html
and other work does compiled js
file where are some ajax requests to retrive and manage data (that stored on backend in postgresql for example)
I would keep concerns separate.
agreed, So I dont need cljc
then? I just need to separate all my clj
and cljs
files
If you have utilities around the data that is passed back and forth, those would make sense in .cljc
files.
maybe like this:
src/my-namespace/
- backend, routes etc
src/my-namespace/frontend
- frontend
> utilities around the data that is passed back and forth what do you mean?
Suppose you are writing a todo app. You will have some data that represents a todo item. You might have a number of functions that operate on that, which you might need to call on both the front end and the back end. So those can go in .cljc
files, reused by both sides.
even If I store todos in DB?
and manage functions on backend will by absolute different than on frontend
Final steps in my mind look like this:
1. Develop backend in clj
files as I would do this w/o frontend
2. Develop frontend in cljs
files as I would do this w/o backend
3. Compile cljs
frontend into output js
file via shadow-cljs
4. Use that js
file in html
template, that I will render on backend part in something like system.clj
file
Yes, persistence would be back end only. But you might have utility functions.
ah I see. Something like helpers
A lot of SPAs do a lot of logic and some fairly heavy lifting on the front end. It depends on how you choose the separate the logic.
yeah its different from project to project. Less logic on front - is better in general IMHO
Some people will disagree 🙂
Thank you Sean!🙏
I haven't done any cljs for about eight years but I hope to get back to it in the new year...
When I worked with rails on production SPA-projects, we had similar separation.
Backend was developing as a monolith, frontend was developing in a separate frontend
directory that had nmp
configs, packages etc. And final js
file was compiling and just used in rails view
Do you work with backend only last 8 years?
Yes, purely backend. Even before that. I spent about six months exploring ClojureScript stuff for some limited use internal apps, in addition to my normal backend work.
My entire career has really been backend stuff, all the way back into the early '80s. I did once write a text editor in IBM 8100 assembly language that was essentially a UI 🙂
wow 👍
I like that ClojureScript is absolutely just Clojure
the same data structure, the same DSL
There used to be a bigger gap between clj and cljs but it's much better these days.
I see
As I was a full-stack engineer most of my career, Clojure is awesome gift for me 😄
"full-stack" didn't exist when I started 🙂
And full-stack mostly means JS these days -- and I just hate JS 🙂
any UI was in terminal right?
(my background is in language designer and compilers etc so I am somewhat opinionated about languages)
The text editor was designed for SNA terminals so it was text and control codes, yeah.
maybe somewhen we will use something better than JS in browser
I did a lot of work with X11 and also Motif in the early '90s.
JS is a reasonable "assembly language" for the web.
btw, do you use linux or macos now for work and home media?
I used linux before my first macbook a lot. It was slackware most of the time
I loved it
I started using Apple machines in the early '90s, but with a parasitic variant of BSD installed -- so it was like OS X but decades earlier.
slackware + icewm (or xfce) = awesome
My career was all Unix flavors -- until Linux eventually appeared. I've been Mac OS on the desktop and *nix on the server nearly all my career. I switched to Windows over the last few years -- with WSL2 and Ubuntu for development now.
Windows is good for media/entertainment I have PC with windows at home and macbook for work 🙂
I don't do any media/entertainment on computers.
I'm someone who has ssh and terminal clients on their phones so I can manage servers on-the-go 🙂
do you use some terminal programs like to read/write mails or for other daily needs ?
Nah, that sort of stuff I've always just used whatever is best of breed on every platform.
I'm not hardcore about "everything in a terminal" -- and I was an Emacs user back in the 17.x days (early '90s).
i prefer to keep them completely separate with a /api folder for server stuff and /web for the frontend
I going to use /backend
and /frontend
dirs
I think its can be any names 🙂
and /utils
dir for cljc
helpers
Form validation in cljc for example is really nice
You can validate data on the front end in forms, and use the same validators for data coming in on your endpoints
@U017QJZ9M7W yeah its really useful. What lib do you use for such universal validations?
Years ago for http://PaddleGuru.com (still in Production!) we used prismatic Schema, but I hear that Malli is the way to go now. I can’t speak to the best choice for universal validations now but it’s definitely great to ditch any inconsistency.
> Less logic on front - is better in general IMHO > For a full stack SPA structure, you can take a look at this project. It uses Inertia.js, so almost all logic lives on back end. https://github.com/prestancedesign/pingcrm-clojure
When derefing a future with timeout, future isn’t automatically cancelled. e.g., when doing -
(deref (future (Thread/sleep 1000000) 0) 10 5)
But why isn’t it cancelled? In what use-case wouldn’t I want it to terminate?
Presumably I no longer care about the result, so doesn’t it simply takes up resources?when you're polling?
(let [f (future (Thread/sleep 5000))]
(loop []
(let [val (deref f 500 :pending)]
(println val)
(if (= val :pending)
(recur)
(println "got it!")))))
The meaning of deref
means fetch result - it's not expected to affect (mutate) the future. You are allowed to deref
from the future multiple times - if you wait long enough your deref will give you back the final value.
user> (def a (future (Thread/sleep 10000) 1))
#'user/a
user> (deref a 1000 0)
0
user> (deref a 1000 0)
0
user> (deref a 1000 0)
1
Also you can't expect all futures to be cancelled in the same way - e.g. the future may need to do work to release external resources (e.g. file/database handles).So the most common use-case for timeout is when I do want the job done, but also need an indicator as to its status. Are there any other (real-life) use cases for timeout? And it’s not the default behavior because the actual process of canceling might be different, depending on the op? Meaning it’s a technical limitation (can’t generalize the canceling procedure). So I should cancel, but it needs to be done manually.
Exactly. If you want to cancel the future then you can do it manually separately from deref
of the value. E.g. using future-cancel
or signalling e.g. using a promise
(let [cancelled? (promise)]
(future (loop [t 1]
(Thread/sleep 100)
(println t)
(when-not (deref cancelled? 0 false) (recur (inc t)))))
(Thread/sleep 1000)
(deliver cancelled? true))
Thanks. A follow-up question - suppose I have a very long list of potential resources, and I only need one. Is the following code clojurian (and considered a good practice)? Or is there a better solution to this problem?
(defn mock-api-call [_]
(Thread/sleep 1000000)
(rand-int 1000))
(defn satisfactory? [resource] (and (= resource 10) resource))
(defn conditional-future [f p c]
(reduce
(fn [acc elem]
(if (realized? p)
(reduced acc)
(let [fut (future (when-let [satisfactory-promise (f elem)]
(deliver p satisfactory-promise)))]
(conj acc fut)))) [] c))
(defn find-resource [very-long-list]
(let [resource (promise)
futures (conditional-future
(comp satisfactory? mock-api-call)
resource
very-long-list)]
(println "Resource:" (deref resource 10000 "Not Found."))
(doseq [fut futures] (future-cancel fut))))
• Seems relatively clojurian to me.
• Make sure that future-cancel
is actually stopping the API calls if that's what you need it to do (it should be, but sometimes future-cancel
is not enough) I guess the realized?
allows early termination - but the futures will probably all be launched in a few milliseconds - so probably won't help.
• Note sure if reduce
is necessary vs (doall (map (fn[x] (future )) very-long-list))
You could use (doall (cpl/upmap 8 (fn[x] ) very-long-list))
with https://github.com/clj-commons/claypoole/blob/master/src/clj/com/climate/claypoole/lazy.clj in order to not launch all API calls at once
Hello! Beginner here trying to do an assignment in Clojure - I’m struggling with clojure/spec
, idk why it’s complaining that the keys don’t exist when the error message also shows that it does
i(ns assignment.core
(:require [clojure.spec.alpha :as s]))
(s/def ::id uuid?)
(s/def ::account-id uuid?)
(s/def ::type #{:credit :debit})
(s/def ::amount pos-int?)
(s/def ::line-item (s/keys :req-un [::account-id ::amount ::type]))
;; (s/def ::line-items (s/coll-of ::line-item :min-count 2))
(s/def ::line-items (s/coll-of ::line-item :min-count 1))
(s/def ::journal-entry (s/keys :req-un [::id ::line-items]))
(defn balance? [entry]
(println (class entry))
(if-not (s/valid? ::journal-entry entry)
(throw (ex-info (s/explain-str ::journal-entry entry)
(s/explain-data ::journal-entry entry))))
)
(def ^:private uuid
(java.util.UUID/randomUUID))
(def ^:private input
{::id uuid
::line-items (list {::account-id uuid, ::amount 200, ::type :debit}
;; {::account-id uuid, ::amount 200, ::type :credit}
)})
(balance? input)
;;
;; Syntax error (ExceptionInfo) compiling at (griffin_interview/core.clj:40:1).
;; #:assignment.core{:account-id #uuid "9b524404-1c7e-4567-be1b-b4f531187d7f", :amount 200, :type :debit} - failed: (contains? % :account-id) in: [:assignment.core/line-items 0] at: [:assignment.core/line-items] spec: :assignment.core/line-item
;; #:assignment.core{:account-id #uuid "9b524404-1c7e-4567-be1b-b4f531187d7f", :amount 200, :type :debit} - failed: (contains? % :amount) in: [:assignment.core/line-items 0] at: [:assignment.core/line-items] spec: :assignment.core/line-item
;; #:assignment.core{:account-id #uuid "9b524404-1c7e-4567-be1b-b4f531187d7f", :amount 200, :type :debit} - failed: (contains? % :type) in: [:assignment.core/line-items 0] at: [:assignment.core/line-items] spec: :assignment.core/line-item
;; #:assignment.core{:id #uuid "9b524404-1c7e-4567-be1b-b4f531187d7f", :line-items (#:assignment.core{:account-id #uuid "9b524404-1c7e-4567-be1b-b4f531187d7f", :amount 200, :type :debit})} - failed: (contains? % :id) spec: :assignment.core/journal-entry
;; #:assignment.core{:id #uuid "9b524404-1c7e-4567-be1b-b4f531187d7f", :line-items (#:assignment.core{:account-id #uuid "9b524404-1c7e-4567-be1b-b4f531187d7f", :amount 200, :type :debit})} - failed: (contains? % :line-items) spec: :assignment.core/journal-entry
oh think I’ve got it.. when I declared my input
I needed to use :account-id
instead of ::account-id
…
Should we wish you "Good luck!" for your interview at Griffin?
Well, I guess I'll wish you luck on the interview 🙂
I hope this isn't considered cheating, but: any particular reason you are requiring unqualified keys in your spec yet input
uses qualified keys?
haha oops, thought I’ve removed all references of it, hope they don’t reprimand me for this :thinking_face:
I was given the spec and I’m literally new to Clojure so I have no clue.. care to elaborate? What’s the difference?
@U04CMG03TC0 They mean this:
(s/def ::line-item (s/keys :req-un [::account-id ::amount ::type]))
☝️:skin-tone-2: That defines :assignment.core/line-item
Spec that validates a hash map with unqualified keys {:account-id .. :amount .. :type ..}
using the :assignment.core/account-id
,`:assignment.core/amount`, and :assignment.core/type
Specs.Your input
uses qualified keys so those :assignment.core/line-items
hash maps have keys :assignment.core/account-id
, :assignment.core/amount
, and :assignment.core/type
.
If I have a big sequence of maps & I want to make a CSV with the keys as headers - is there a quick & idiomatic way to do so?
that actually expects vectors for rows but that's just one juxt
away from a map
(require '[ :as io] '[clojure.data.csv :as csv])
(def data ;; seq of maps of :a :b :c)
(with-open [writer (io/writer "out.csv")]
(csv/write-csv writer
(into [["a" "b" "c"]] ;; header row
(map #((juxt :a :b :c) %) data))))
something like that