Fork me on GitHub
#beginners
<
2022-11-21
>
shawn00:11:59

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?

Bob B01:11:34

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.

☝️ 1
quan xing01:11:11

I think you don't have a env.clj file in project's root path

shawn01:11:54

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

shawn01:11:03

so, I moved env.edn to the project root directory now

shawn01:11:54

this is env.clj btw:

shawn01:11:58

(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")

Bob B01:11:41

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)

quan xing01:11:14

You can take look at this https://github.com/weavejester/environ about environment

shawn01:11:54

is there an obvious way to fix the file hierarchy I have

shawn01:11:16

starting from scratch fixed it but I don't know what the root cause is

shawn01:11:40

actually, no, it's broken again

shawn02:11:58

this is with this file structure:

shawn02:11:15

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"}}}}

shawn03:11:25

it appears to be broken again

shawn03:11:31

oh. it looks like I may need to have evaluated some require statements? the tooling is horrible

George03:11:37

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.

☝️ 1
George03:11:50

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.

George03:11:24

just note that if you package your app into a jar later you'll want to understand paths relative to your project

George03:11:06

you can specify :paths in your deps.edn to add to your classpath which may help.

shawn05:11:06

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

pavlosmelissinos07:11:57

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.

pavlosmelissinos07:11:17

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 🙂

1
pez09:11:40

> 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.

shawn03:11:23

@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.

shawn03:11:52

@U0ETXRFEW yes. that was it. thank you.

pez07:11:39

> 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

2
pavlosmelissinos08:11:32

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 🙂

Tema Nomad03:11:28

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

seancorfield03:11:07

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.

seancorfield03:11:44

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.

Tema Nomad03:11:26

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)

seancorfield03:11:15

I would keep concerns separate.

Tema Nomad03:11:54

agreed, So I dont need cljc then? I just need to separate all my clj and cljs files

seancorfield03:11:55

If you have utilities around the data that is passed back and forth, those would make sense in .cljc files.

Tema Nomad03:11:08

maybe like this: src/my-namespace/ - backend, routes etc src/my-namespace/frontend - frontend

Tema Nomad03:11:21

> utilities around the data that is passed back and forth what do you mean?

seancorfield03:11:34

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.

Tema Nomad03:11:23

even If I store todos in DB?

Tema Nomad03:11:52

and manage functions on backend will by absolute different than on frontend

Tema Nomad03:11:53

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 cljsfiles 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

seancorfield03:11:54

Yes, persistence would be back end only. But you might have utility functions.

Tema Nomad03:11:12

ah I see. Something like helpers

seancorfield03:11:40

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.

Tema Nomad03:11:32

yeah its different from project to project. Less logic on front - is better in general IMHO

seancorfield03:11:54

Some people will disagree 🙂

Tema Nomad03:11:11

Thank you Sean!🙏

seancorfield03:11:33

I haven't done any cljs for about eight years but I hope to get back to it in the new year...

Tema Nomad03:11:53

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

Tema Nomad03:11:36

Do you work with backend only last 8 years?

seancorfield04:11:03

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.

seancorfield04:11:01

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 🙂

Tema Nomad04:11:06

I like that ClojureScript is absolutely just Clojure

Tema Nomad04:11:18

the same data structure, the same DSL

seancorfield04:11:27

There used to be a bigger gap between clj and cljs but it's much better these days.

Tema Nomad04:11:43

As I was a full-stack engineer most of my career, Clojure is awesome gift for me 😄

seancorfield04:11:57

"full-stack" didn't exist when I started 🙂

seancorfield04:11:18

And full-stack mostly means JS these days -- and I just hate JS 🙂

Tema Nomad04:11:33

any UI was in terminal right?

seancorfield04:11:42

(my background is in language designer and compilers etc so I am somewhat opinionated about languages)

seancorfield04:11:16

The text editor was designed for SNA terminals so it was text and control codes, yeah.

Tema Nomad04:11:36

maybe somewhen we will use something better than JS in browser

seancorfield04:11:37

I did a lot of work with X11 and also Motif in the early '90s.

seancorfield04:11:56

JS is a reasonable "assembly language" for the web.

Tema Nomad04:11:06

btw, do you use linux or macos now for work and home media?

Tema Nomad04:11:33

I used linux before my first macbook a lot. It was slackware most of the time

seancorfield04:11:07

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.

Tema Nomad04:11:15

slackware + icewm (or xfce) = awesome

seancorfield04:11:32

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.

Tema Nomad04:11:33

Windows is good for media/entertainment I have PC with windows at home and macbook for work 🙂

seancorfield04:11:40

I don't do any media/entertainment on computers.

seancorfield04:11:54

I'm someone who has ssh and terminal clients on their phones so I can manage servers on-the-go 🙂

Tema Nomad04:11:13

do you use some terminal programs like to read/write mails or for other daily needs ?

seancorfield04:11:50

Nah, that sort of stuff I've always just used whatever is best of breed on every platform.

seancorfield04:11:25

I'm not hardcore about "everything in a terminal" -- and I was an Emacs user back in the 17.x days (early '90s).

valerauko15:11:23

i prefer to keep them completely separate with a /api folder for server stuff and /web for the frontend

👍 1
Tema Nomad15:11:36

I going to use /backend and /frontend dirs I think its can be any names 🙂

Tema Nomad15:11:03

and /utils dir for cljc helpers

Sam Ritchie19:11:46

Form validation in cljc for example is really nice

Sam Ritchie19:11:18

You can validate data on the front end in forms, and use the same validators for data coming in on your endpoints

Tema Nomad03:11:57

@U017QJZ9M7W yeah its really useful. What lib do you use for such universal validations?

Sam Ritchie03:11:07

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.

🙏 1
Michaël Salihi04:11:01

> 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

🙏 1
Liliane Assous16:11:08

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?

Alex Miller (Clojure team)16:11:06

(let [f (future (Thread/sleep 5000))]
  (loop [] 
    (let [val (deref f 500 :pending)] 
      (println val) 
      (if (= val :pending)
        (recur)
        (println "got it!")))))

Rupert (All Street)21:11:16

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).

Liliane Assous07:11:41

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.

Rupert (All Street)08:11:38

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))

Liliane Assous11:11:23

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))))

Rupert (All Street)11:11:10

• 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))

Rupert (All Street)11:11:55

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

🙏 1
Edison Yap22:11:03

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

Edison Yap22:11:04

oh think I’ve got it.. when I declared my input I needed to use :account-id instead of ::account-id

seancorfield23:11:14

Should we wish you "Good luck!" for your interview at Griffin?

hifumi12300:11:27

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?

Edison Yap01:11:36

haha oops, thought I’ve removed all references of it, hope they don’t reprimand me for this :thinking_face:

Edison Yap01:11:11

I was given the spec and I’m literally new to Clojure so I have no clue.. care to elaborate? What’s the difference?

seancorfield17:11:25

@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.

seancorfield17:11:54

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.

Ted Ciafardini22:11:48

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?

Alex Miller (Clojure team)23:11:00

that actually expects vectors for rows but that's just one juxt away from a map

Alex Miller (Clojure team)23:11:04

(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

thanks2 1
🚀 2