Fork me on GitHub
#beginners
<
2021-09-06
>
Lukas07:09:06

Hi, I'm trying to figure out how to write a pedestal interceptor which acts as guard for a route (e.g. to early return a 405 status). I thought I had read in the official documentation something along the line "if a response is added to the context, everything else will get skipped" but I guess I confuse this, at least this doesn't do the trick. Any help with this issue is greatly appreciated 🙂

Maravedis07:09:04

I'm pretty sure it doesn't. Consider that you can have a response that is not yet content-negociated. You´d need the next interceptor to to do that.

Lukas07:09:15

ye makes sense

Lukas07:09:43

Thank you! This is what I was looking for

Fredrik11:09:40

@UP2FUP0TT do you have some code for what you were trying or? According to http://pedestal.io/guides/hello-world-content-types, your original claim about early termination seems right.

Maravedis11:09:27

Oh yeah, FVR is right. Terminate seems like an antipattern actually. Apologies.

Lukas14:09:56

@U024X3V2YN4, thanks for pointing to the article. I cannot reproduce the behavior anymore... Maybe I had some state in the repl, or I simply made a mistake in my interceptor.

olaf10:09:23

Why using case I cannot refer to a symbol that matches the expression value?

(comment
  (let [var ")"]
    (case var
      ")" 1
      2)) ; => 1
  (let [var ")"
        test ")"]
    (case var
      test 1
      2))) ; => 2

Maravedis10:09:40

From the documentation (emphasis mine): > Takes an expression, and a set of clauses. > Each clause can take the form of either: > test-constant result-expr > (test-constant1 ... test-constantN) result-expr > The test-constants are not evaluated. They must be compile-time > literals, and need not be quoted. https://clojuredocs.org/clojure.core/case

🙌 2
alexdavis11:09:35

Hi, is it possible to convert a function to a symbol (or string) and then resolve it back to a function again to use it? I have something like this

(let [operator (rand-nth ['+ '- '* '/])
      a 5
      b 3]
  (operator a b))
but the answer is always b? I’ve tried using resolve but I think I’m not quite understanding whats going on…

delaguardo11:09:14

you can combine operators you allow your users to use into a map

(def sym->op
  {'+ +
   '- -
   ...})

(def op->sym 
  (into {} 
    (map (fn [s o] [o s])) 
    sym->op)
something like that and then you can use those two maps to do lookups

4
walterl13:09:01

What's going on in your snippet: symbols, like keywords, when invoked as a function, try to look themselves up in the map given as the first argument, falling back to the second argument if the lookup failed. I.e. it's equivalent to doing (get a operator b). This happens because you selected the symbol for the operators, and not the operator functions directly. Your solution works if you drop the 's.

alexdavis13:09:26

Yes I know I could drop the quote in the snippet, but in my actual app the operators are selected from a dropdown and need to be converted to/from strings. Didn’t know thats how symbols worked when used as functions though, so thanks for explaining that!

👍 2
alexdavis11:09:13

The use case is that I have a sort of calculator where people can choose an operator and apply it to some values

Benjamin13:09:25

defroutes is a macro that def s the first arg, is there a way to tell clj-kondo about this?

Benjamin13:09:13

yea. I'll put my project there

borkdude13:09:11

clj-kondo has built-in support for that. if it doesn't work, please provide more context. There's also a #clj-kondo channel.

👀 2
Benjamin14:09:51

I guess it is quite specific but does anybody have advice about the following warning when I load a clojure lib from inside an intellij plugin? WARN: Do not use URL connection as JarURLConnection (my real issue is this exception on some windows devices: Caused by: java.lang.ClassCastException: class com.intellij.util.lang.ZipResourceFile$MyURLConnection cannot be cast to class java.net.JarURLConnection)

Benjamin15:09:12

well nvm I learned that is is a known issue that jetbrians works on

John Bradens16:09:40

What do most people use for database? A lot of tutorials I'm doing use Postgres. I also did one that uses Crux DB. I'm not familiar enough with databases to really understand why one is used over the other. What is generally suggested? I'd love to hear your opinions.

Maravedis16:09:56

That is... a very broad topic. At the very least, you'd need to indicate what's the usage. Do you need to store orders ? Adresses ? data point over time ? Something else ?

seancorfield16:09:58

There are lots of relational databases (of which PostgreSQL is one) and then there are lots of other types of database: key/value, columnar, document, graph, etc, etc. Some people use multiple types of databases together -- we use MySQL (relational) and Redis (key/value) at work -- because they address different needs. If we were starting over, we might use Crux: it's document-oriented but has native joins and bitemporality which would work well with some of the stuff we do. But if you're not familiar with databases, and you don't have a specific use case in mind, it's probably best to just use something simple (H2, SQLite -- don't require any setup) until you figure out you need more than that @U02387J8EKG

seancorfield16:09:31

I'd also be happy to dig deeper in the #sql channel if you want (that's where folks ask about relational DBs -- #datomic #crux #neo4j #asami all have their own channels).

sova-soars-the-sora17:09:50

Depends on how much data you might have. If you want a GraphQL store that plays nicely with Clojure I recommend Datomic. If you want Datomic syntax [awesome] but not a full separate process running, you can use something like Datahike which will write to a local file in a similar way. For different amounts of data, like a tiny number of users or something, you can use Duratoms that are basically persistent atoms saved to a file.

dgb2317:09:32

Perspective: custom webapps/sites, client work, small teams or solo development. My very rough/general takes: Almost can’t go wrong with relational, specifically MySQL/Postgres/SQLite. They include capabilities like storing/querying JSON as well for document/non-relational stuff. SQL is very powerful and has great support in the wider Clojure ecosystem. * If you’re on a VPS or know how to setup file storage in “the cloud” or move the database to the user or are prototyping, consider SQLite. Very easy setup and operations. Lowest complexity. * Postgres has the nicest API and semantics, some powerful extensions, well supported and documented. * MySQL is ubiqutous, fast. (99%) interchangeable with MariaDB with minor caveats. Datalog DBs such as Datomic (there is a free version), Crux and datahike have increased ergonomics in Clojure and very good philosophical fit. I have been using datahike (kind of like an SQLite for Clojure Datalog) for prototypes and personal projects and am considering to use it for a real project. Temporality is extremely useful even if you only use it for debugging. Pair that with the flexibility of EAV, Datalog rules and so on and you get a very, very attractive alternative to SQL. You can also do temporal modelling in SQL. Did that with a MySQL DB about a year ago. Since then I consider it almost a sane default. But is harder and much more verbose than in Clojure Datalog. On the other hand I recently found out about metabase, which is a very powerful application you can run on top of SQL, its open source and written in Clojure. You might consider the ecosystem around your DB and not just the DB itself. Neo4j, since it is mentioned above, is a graph db that has Clojure support as far as I know. I used it years ago (not with Clojure), was first blown away because of the nice query language and general UX as well as how nice data-modelling feels. But except for the great UI I don’t see a reason to use it over Datalog DBs in Clojure. Personally don’t like other No-SQL options generally as all the stuff I do is relational in some sense. Heard very good things of CouchDB though. Other than the above you might consider how you want to run your application. There are providers like http://railway.app and others that let you spin up Postgres (maybe the others too) in like 2mins with a couple of clicks. Cloud providers typically have their own DB and DB related solutions, some of that stuff is dirt cheap and easy to setup.

ghadi18:09:50

Datomic. All day.

❤️ 1
seancorfield18:09:54

No bias from the Cognitect employee, eh? 🙂

ghadi18:09:53

just going to say that a lot of databases "adopted" Datomic's design, for good reasons

ghadi18:09:17

fwiw I was using Datomic before becoming a Cognitect 🙂

seancorfield18:09:02

(I think Datomic is great and Cognitect have done amazing work there)

John Bradens19:09:06

Thanks everyone! This is really cool info. I think for now I'll probably stick to Postgres since I'm most familiar with it from the tutorials I've done, and my app that I'm building is pretty basic right now. I'll join the DB slack channels to learn more. Datomic sounds really interesting and I see it get brought up a lot! I'm on a tight budget so I'll have to look into the free version. For what use cases does it make sense to use Datomic? Or is it just better in general in various ways?

dgb2319:09:18

I never used Datomic specifically, as I again do small scale custom web based stuff. But after having built an app that required a temporal data model (in SQL) and then trying out Datalog, I’m 100% convinced of the power of temporal modelling and the expressivity of Datalog. As soon as need to know what happened when or even just like to know (get value out of it) then you want this. The better question is rather: when do you not need to know? It gets even more powerful when you have a (bi-temporal) model where you distinguish of system/transaction time and user-provided validity/time: https://en.wikipedia.org/wiki/Temporal_database You cannot go ‘wrong’ with Postgres though. If you are interested in all of this and have some time then I very much recommend you model some reasonably realistic temporal relations in say Postgres and then try out something like Datomic and do the same.

seancorfield19:09:12

Datomic is designed along the same principles as Clojure itself but it is fairly different from "regular" (relational) databases. These two posts might help explain how it's different and why you might use it: • https://www.infoq.com/articles/Datomic-Information-Modelhttps://s-expressions.com/2013/03/29/why-datomic/ (both from 2013 -- so Datomic has been around for a while now)

seancorfield19:09:31

Since you don't have much background in databases at all, some of that may not make much sense yet but hopefully the "pros" are appealing and some of them are self-explanatory...

John Bradens19:09:53

@U01EFUL1A8M Thanks for the further info and link. What is it about the app that required a temporal data model? Was it a certain feature? I am definitely curious and I think I'll try out the temporal relations in Postgres vs Datomic just to explore. I'll have to learn more about what temporal relations are first 🙂

John Bradens19:09:39

@U04V70XH6 Thanks for the links! I'll look into that. Yes a lot of this is over my head right now, but I'd love to expand my knowledge to be able to understand better. This is very helpful.

seancorfield19:09:10

If it's any consolation, I've been a professional software developer for about 40 years now and I really hadn't touched databases until the mid-2000s 🙂

dgb2320:09:57

They have a set of core data that changes slowly (but does change) and then regular maintenance reports from their staff/craftsmen. These reports are used to communicate the status of equipment in different locations around the country to their customers. With a temporal model we/they are now able to answer questions like: * What were the attributes X, Y, Z and the core data A, B, C of the report on date D? * Did client C get a quote to repair the thing at location L as suggested in report R at D? * Thing T actually got changed between D1 and D2 (< now), we can correct that and see which reports are invalidated betwen D1-D2. * User U expects to see X after updating the core data, what happened exactly? Thought experiment. If you didn’t use version control (git): * What would you do to keep your code in sync? * Would you feel comfortable of doing hairy/big changes? How would you go about them? * Would you value information like who did when what? I think the best way to describe the general approach of having (immutable) temporal data is “liberating”!

dgb2320:09:55

“over my head” - that was exactly my initial feeling as well, and often is when learning something new. But it sometimes is a sign of us learning something really valuable. A pragmatic prioritisation is recommended though. And give yourself time! It took me a couple of months to slowly dive into this topic, trying out stuff, reading stuff, sleeping on it.

John Bradens21:09:11

@U04V70XH6 Thank you! That does help me feel better 🙂 And @U01EFUL1A8M thanks, that's an interesting thought experiment! I find myself increasingly fascinated by this, the more I learn. I'll keep learning slowly and will take a little pressure off trying to learn so much all at once. I'm excited to start experimenting a little. Really cool!

🎉 2
JohnJ17:09:16

@U050ECB92 Curious, what Datomic's design are you referring to here specifically?

Stuart16:09:20

Hi, I have a type that my repl is reporting as [B, what is this? Is it a byte array ?

schmee16:09:28

yup, a Java byte[] :thumbsup:

Lukas19:09:36

Hey, I try to adjust a function to take process multiple transactions.

(let [[op & args] [assoc :test 1]
       db (atom {})]
   ;; function to adjust
   (apply swap! db op args)) ;; => {:test 1}
This is with what I came up with:
(defn tx-fn [tx-data]
   (let [fns (map
              (fn vec->list
                [[op & args]]
                `(~op ~@args))
              tx-data)]
     `(fn [db#] (-> db# ~@fns))))

 (let [tx [[assoc :test 1]
           [assoc :test2 2]]
       db (atom {})]
   (swap! db (eval (tx-fn tx)))) ;; => {:test 1 :test2 2}
which seems overly complicated, 🙈 is there a better way to do this?

jcromartie19:09:09

I would avoid eval for this. You don’t really need it.

jcromartie19:09:43

Given your example of [op & args] you can just call (apply op args)

Lukas19:09:32

You mean doing several swap! calls?

jcromartie19:09:35

You can use reduce inside your call to swap!

💯 2
jcromartie19:09:19

I’d say something like:

(swap! db reduce (fn [m [op & args]] (apply op m args)) txs)

noisesmith19:09:57

also, best practice is to write your function to take the type of the atom's contents as domain and range, and include no atom related code in your function itself

noisesmith19:09:45

so make (defn operate [m txs] (reduce (fn [m [op & args]] apply op m args) m txs)) then later (swap! db operate txs)

jcromartie19:09:47

So, a function like:

(defn apply-txs
  [db-state txs]
  (reduce (fn [m [op & args]] (apply op m args)) db-state))

;; usage
(swap! db apply-txs [[assoc :test 1] [assoc :test2 2]])

Lukas19:09:27

Thanks a lot for the input 🙏

Lukas19:09:50

☝️ at least I already figured that my solution is suboptimal 😂

jcromartie19:09:01

Every solution is suboptimal 🧑‍🔬

💯 2
Kristian Pedersen19:09:00

Hi! Given a chess board position, I want to return all moves that have a distance of (x = 1 and y = 2) or (x = 2 and y = 1). I have solved this in JavaScript, and I’m trying to implement it in Clojure, but my Clojure function just returns an empty list (). What am I missing? Here they are - first the JavaScript implementation, and then the Clojure one: https://gist.github.com/kristianpedersen/b55449d87bb31d733e081727b6007a9d Also, if you have any comments on readability or making the code more idiomatic, that’s of course appreciated.

jcromartie19:09:58

At first glance you could simplify the data structure generation by using something like for, e.g. … (for [row (range 8), col (range 8)] {:x col :y row})

jcromartie19:09:44

then you could have a function to look up the name of a cell, like: (defn cell-name [row col] (str (nth "ABCDEFGH" col) row))

jcromartie19:09:05

I’m also writing code with lots of move and board at the moment 😁

jcromartie19:09:17

You can write the JS move.position.x equivalent in Clojure like (-> move :position :x)

jcromartie19:09:45

I took a crack at the whole thing…

(def chess-board
  (for [row (range 8), col (range 8)]
    [col row]))

(defn distance
  [v1 v2]
  (mapv (comp #(Math/abs %) -) v2 v1))

(defn valid-moves
  [move board]
  (filter #(= #{1 2} (set (distance move %))) board))

(println (valid-moves [3 4] chess-board))

jcromartie19:09:17

That just uses [col row] vectors instead of maps. The distance function returns a vector of [dx, dy] and then you can turn that into a set and compare it to #{1 2} because the order doesn’t matter.

Kristian Pedersen19:09:32

Thanks! That’s a lot nicer to read than what I have. However, I still don’t understand why my original solution doesn’t return anything, when the (seemingly) equivalent JS code does.

Kristian Pedersen19:09:44

I would have thought that is-valid in my Clojure filter function would be returned, as it’s the last thing to get evaluated in the let statement.

Douglas Ramos19:09:58

On:

(defn get-valid-moves
  [move board]
  (filter #(= #{1 2} (set (distance move %))) board))
a more idiomatic name for get-valid-moves would be just valid-moves

🙏 2
ChillPillzKillzBillz20:09:17

late to the party... here is my solution

cljs.user=> (require '[clojure.math.combinatorics :as combo])
cljs.user=> (defn boardloc [row col xdelta ydelta]
       #_=>   (let [xloc [(+ col xdelta) (- col xdelta)]
       #_=>         yloc [(+ row ydelta) (- row ydelta)]
       #_=>         sane_xloc (filter #(<= 0 % 7) xloc)
       #_=>         sane_yloc (filter #(<= 0 % 7) yloc)]
       #_=>     (combo/cartesian-product sane_xloc sane_yloc)))

cljs.user=> (boardloc 4 4 1 2)
((5 6) (5 2) (3 6) (3 2))
cljs.user=> (boardloc 0 4 1 2)
((5 2) (3 2))
cljs.user=> (boardloc 0 7 1 2)
((6 2))

Ed22:09:43

Just a trivial thing, you can write (and (> 8 %) (< 0 %)) as (< 0 % 8) because the numerical comparison functions take variable numbers of arguments.

Kristian Pedersen04:09:24

Lots of helpful tips here, which I really appreciate! However, I still don’t understand why my Clojure didn’t work, while my JS solution did.

ChillPillzKillzBillz07:09:24

@U0P0TMEFJ I didn't know that... I am still a newbie... but thanks for the tip!! Solution modified according to the tip!!

👍 2
delaguardo07:09:41

@U02CNL6H631 but your snippet is working. Are you sure that you evaluate everything in the same repl session?

Kristian Pedersen13:09:33

I’ve no idea what I did wrong. Probably a REPL mixup. Now it works, after shutting down all VS Code instances, and creating a new Lein app. Thanks for your help and patience, everyone! 😄

👍 2
Chase21:09:38

How do I handle this integer parsing error so that I keep looping but maintain the generated random number?

Chase21:09:48

(defn guessing-game []                                                                                                    
  (println "Guess the number!")                                                                                           
  (let [number (inc (rand-int 100))]                                                                                      
    (loop []                                                                                                              
      (let [guess (try (Integer/parseInt (read-line))                                                                     
                       (catch Exception e                                                                                 
                         (do (println ": need to input a number between 1 and 100")                                       
                             (guessing-game))))]                                                                          
        (cond                                                                                                             
          (= guess number) (println "You win!")                                                                           
          (> guess number) (do (println "Too high, guess again:")                                                         
                               (recur))                                                                                   
          (< guess number) (do (println "Too low, guess again:")                                                          
                               (recur)))))))

Chase21:09:40

When I used (recur) instead of (guessing-game) I got the "not in tail position" error

noisesmith21:09:19

@U9J50BY4C return a value from the try / catch, then use a conditional on that value

noisesmith21:09:50

(let [parsed (try ... (catch Exception e ... false))] (cond (not parsed) ... (= guess number) ... ... ...))

noisesmith21:09:41

also, catch already has a do (most macros with bodies have them)

Chase21:09:04

ahh! Yeah my error handling knowledge is woefully inadequate

Chase21:09:02

I actually had tried that conditional method but was trying to do it on the actual e value. if e (prompt user for correction) (...) so I wasn't even sure exactly what e is

noisesmith21:09:23

also, I think the code would be more readable if the print before the recur were moved into the cond, under the (not parsed) condition

noisesmith21:09:47

e is a local inside catch, you don't need it

noisesmith21:09:08

(unless you need some logic to differentiate the reasons the parse would fail)

noisesmith21:09:37

I'd just do (catch Exception _ false) then put the other logic in the case, to match the other cases

noisesmith21:09:37

check the falsity before you run the conditions that use numeric ops though, to avoid "can't compare" errors

Chase21:09:21

That all works great. Thanks!

Chase21:09:58

This is what you were advising correct:

Chase21:09:07

(defn guessing-game []                                                                                                    
  (println "Guess the number!")                                                                                           
  (let [number (inc (rand-int 100))]                                                                                      
    (loop []                                                                                                              
      (let [parsed (try (Integer/parseInt (read-line))                                                                    
                        (catch Exception _ false))]                                                                       
        (cond                                                                                                             
          (not parsed) (do (println ": need to input a number between 1 and 100")                                         
                           (recur))                                                                                       
          (= parsed number) (println "You win!")                                                                          
          (> parsed number) (do (println "Too high, guess again:")                                                        
                                (recur))                                                                                  
          (< parsed number) (do (println "Too low, guess again:")                                                         
                                (recur))))))) 

Chase21:09:40

Cool. I appreciate it.

Chase21:09:52

Any resources you recommend to learn about Clojure error handling?

noisesmith21:09:41

one more thing that might or might not improve the code:

(if (= parsed number)
  (println ...)
  (do (println (case (not parsed) "..."
                      ... "..."
                      ... "..."))
      (recur)))

noisesmith21:09:26

but that might be overly dry

Chase21:09:26

I'm digging it. You would still need to use cond instead of case in this instance right?

noisesmith22:09:55

right, that was my mistake

noisesmith22:09:43

extremely dried out version:

(defn guessing-game
  []
  (let [number (inc (rand-int 100))]
    (loop [parsed false]
      (if (= parsed number)
        (println "You win!")
        (do (print (cond
                    (not parsed)  "guess a number between 1 and 100: "
                    (> parsed number)  "Too high, guess again: "
                    (< parsed number)  "Too low, guess again: "))
            (flush)
            (recur (try (Integer/parseInt (read-line))
                        (catch Exception _ false))))))))

noisesmith22:09:27

that collapses the "invalid number" and "initial prompt" into one condition

noisesmith22:09:52

and uses print / flush to allow input at the prompt

Chase22:09:40

Aight. I think I like our solution right before this one better tbh.

Chase22:09:59

I have wondered when will I need to use (flush) and when is it not necessary?

noisesmith22:09:22

println implicitly flushes after the newline, print does not send a newline

Chase22:09:01

Good stuff. I appreciate all the help