Fork me on GitHub
#beginners
<
2021-01-18
>
Mno09:01:29

If you do a get request it works correctly though?

Mno09:01:38

Very odd, don't think I can guess an answer

lassemaatta09:01:01

I'm guessing the indentation is wrong? I think you need to give create-default-handler as the second parameter to ring-handler perhaps?

Fra23:01:07

@UC506CB5W apologies for the late reply. Yes, it was in the wrong position

Fra23:01:26

create-resource-handler too

Fra23:01:29

(def handler
  (reitit/routes
    (reitit/create-resource-handler
      {:path "/resources/"})
    (reitit/ring-handler
      (reitit/router routes)
      (reitit/create-default-handler
        {:not-found
         (constantly (response/not-found "404 - Not Found"))
         :method-not-allowed
         (constantly (response/method-not-allowed "405 - Method not allowed"))
         :not-acceptable
         (constantly (response/not-acceptable "406 - Not acceptable"))}))))

Fra23:01:36

this way it works, thanks 👍

Yang Xu09:01:52

Hi, I want to pass the PersistentVector to Clojure as a parameter when I invoke it from Java. How to do that?

PersistentVector fiedls = PersistentVector.create(Keyword.intern(null, "d"));
mergeSelect.invoke(m, fiedls);

(defn ms [m fields]
  (println (-> m (merge-select  fields) sql/format)))

Alex Miller (Clojure team)14:01:32

Don’t call that - you should consider PV to be Clojure internals. Either use Clojure.read() to read a literal vector or obtain the vector function with Clojure.var() and .invoke() it.

Yang Xu11:01:08

Thank you first, But I wonder why cannot use class of Clojure in Java? And I saw some project using it directly.

Alex Miller (Clojure team)13:01:24

As a concrete class, it’s considered part of Clojure’s internals

Alex Miller (Clojure team)14:01:34

the official Java API for invoking Clojure is the clojure.java.api.Clojure class. https://clojure.github.io/clojure/javadoc/clojure/java/api/Clojure.html

Yang Xu02:01:39

Thank you again.

evocatus09:01:16

Can macro be recursive?

delaguardo09:01:00

(defmacro rec-example [n]
  (when (not= n 0)
    `(do ~n (rec-example ~(dec n)))))
as an example

evocatus09:01:11

Look what I did:

(defn square [x]
  (* x x))

(defmacro pow [x n]
  (cond (zero? n) 1
        (even? n) `(square (pow ~x ~(/ n 2)))
        :else `(* ~x (pow ~x ~(dec n)))))

evocatus09:01:30

it's Newton-Raphson fast exponentiation

delaguardo09:01:45

but why macro?

delaguardo09:01:51

looks good btw

😀 3
evocatus09:01:37

classic algorithm is not tail-recursive and I couldn't write a tail-recursive version for some time

evocatus09:01:55

but it's possible

evocatus09:01:26

but I think a more elegant way would be to generate a sequence (lazy?) of numbers to multiply and then just reduce *

evocatus10:01:34

no, actually it needs to be some kind of a stack to have logarithmic number of multiplications

evocatus10:01:17

basically we square x and then optionally multiply once by x

jaihindhreddy10:01:44

@UEQDV142J The caveat to your macro version is that it will work only with numbers. For example, the following will fail:

(def x 2)
(def n 7)
(pow x n) ;; will blow up
Here's the same thing as a function, and a macro on top optimising some very simple cases:
(defn pow-fn [x n]
  (loop [base x
         prod 1
         exp n]
    (if (zero? exp)
      prod
      (recur
        (* base base)
        (if (even? exp)
          prod
          (* prod base))
        (quot exp 2)))))

(defmacro pow [x n]
  (cond (zero? n) 1
        (= x 1) 1
        (zero? n) 0
        (= n 1) x
        (= n 2) `(let [x# ~x] (* x# x#))
        :else `(pow-fn ~x ~n)))
This way, if the base or the exponent is 0 or 1 (the literal), evaluation happens at compile time. Similarly, if the exponent is the literal 2, then it macroexpands into a multiplication (with let used so that any side effects only happen once). For example, check out (macroexpand '(pow 3 2))

😮 3
evocatus10:01:48

Great! But I believe you mistyped in the macro. Third line in cond form should be (zero? x) 0

💯 3
jaihindhreddy10:01:35

Oops! Good catch.

Piotr Brzeziński12:01:10

Hello 🙂. Following the “Living Clojure” I’ve ran into another issue that I’m unable to resolve. I am setting up clojurescript in my project and I was asked to run lein trampoline cljsbuild repl-rhino in my project root. It downloaded bunch of things and then failed when trying to build (I suppose?)

Exception in thread "main" Syntax error compiling at (cljsbuild/repl/rhino.clj:1:1).
.....
Caused by: java.io.FileNotFoundException: Could not locate cljs/repl/rhino__init.class, cljs/repl/rhino.clj or cljs/repl/rhino.cljc on classpath.
does rhino have to be added as one of dependencies/plugins? My deps look like that
:dependencies [[org.clojure/clojure "1.10.0"]
                   [compojure "1.6.1"]
                   [ring/ring-defaults "0.3.2"]
                   [ring/ring-json "0.5.0"]
                   [org.clojure/clojurescript "1.10.764"]]

didibus23:01:12

Rhino is deprecated, and has been removed in newer versions of ClojureScript

didibus23:01:24

It was removed in version v1.10.738

didibus00:01:00

The only REPLs now are browser and node

didibus00:01:04

> Given Closure Compiler & Library’s recent pace of change, we’ve decided to focus our energies on the essential browser and Node.js REPLs. We’ve also begun enrichening the CLI & REPL APIs to ease third party efforts to target these JavaScript environments.

Piotr Brzeziński13:01:13

Sorry, I haven’t noticed your answer. Thanks for looking into my problem!

Christian12:01:40

If I have something like

(vector (* 2 (range 1 100)))
Will the calculation of this will be done in parallel? I don't know how to pose the question. I want want to know, if I have to put in special measures to make clojure use the full potential of my machine with multiple threads, cores and cpus?

Mno13:01:16

fairly certain it’s not done in parallel/concurrently

Max Deineko13:01:16

If you mean map , then no, it will be applied sequentially:

(map #(do (print %) (* 2 %)) (range 1 10))
;; 123456789
;; => (2 4 6 8 10 12 14 16 18)
For simple cases of parallelization you could use pmap

Mno13:01:10

ohhh today I was reminded of the existence of pmap

👍 3
Mno13:01:24

I suppose if you want to do truly concurrent stuff you could take a look at: https://clojure.org/about/concurrent_programming or Rich’s http://2.hr talk on the matter: https://www.youtube.com/watch?v=nDAfZK8m5_8

👀 3
Mno13:01:53

maybe even core.async, manifold, Java threads, etc… but that’s a bit overkill

Christian16:01:11

I was mainly interested in knowing how much concurrency is applied automatically behind the scenes

Christian16:01:25

Thanks for the link, I'll take a look

Max Deineko16:01:09

I don't think concurrency/parallelization can be applied behind the scenes as long as we allow for functions with side effects. Unless of course we never want determinism guarantees 🙂 But even then, I'd be surprised if performance would not soon become a problem in behind-the-scenes-auto-parallelized computations (but happy to be shown otherwise)..

Christian20:01:24

There is a ton of pure functions though. In my naiv understanding these are pretty inviting to be run in parallel. Maybe I should spend more time on the basics, but it with cpus commonly offering dozens of threads and cores it seems like a waste when everything runs sequential

Max Deineko02:01:38

My main point was that we generally need to know and choose whether "concurrency is applied automatically behind the scenes" to write correct programs -- whether parallel map or sequential map is the default one is less crucial in this regard.

Max Deineko02:01:22

Btw, it looks like pmap is superceded by clojure.core.reducers, which might be the closest to what you were looking for.

Max Deineko03:01:40

Last, automatic parallelization in general is a hard problem afaik (not sure about theoretical computability/limits thereof atm) -- orchestration of parallel tasks incurs additional costs, one has to avoid congestion etc.. I made a little experiment:

(ns my.scratchpad
  (:require [clojure.core.reducers :as r]))

(defn mod-pow [b e m]
  (int
   (.modPow (BigInteger/valueOf b)
            (BigInteger/valueOf e)
            (BigInteger/valueOf m))))

(for [[name size tr] [["A" 10000000 inc]
                      ["B" 1000000 #(mod-pow 1234 (- 0 5 (* % %)) 10001)]]]
  (let [d (vec (range size))
        _ (println "--" name "--")
        v1 (time (->> d (mapv tr)))                       ; sequential evaluation
        v2 (time (->> d (r/map tr) r/foldcat (into [])))  ; reducers, default granularity
        v3 (time (->> d (r/map tr) (r/fold 1 into conj))) ; reducers, granularity 1
        v4 (time (->> d (pmap tr) (into [])))]            ; pmap
    (= v1 v2 v3 v4)))

;; -- A --
;; "Elapsed time: 233.065434 msecs"
;; "Elapsed time: 1506.866011 msecs"
;; "Elapsed time: 6245.779895 msecs"
;; "Elapsed time: 11999.532676 msecs"
;; -- B --
;; "Elapsed time: 2189.691216 msecs"
;; "Elapsed time: 1185.890224 msecs"
;; "Elapsed time: 1663.882219 msecs"
;; "Elapsed time: 2980.890022 msecs"

;; => (true true)
Here we simply map two functions (one very cheap, one slightly less so) over a collection. And it might very well be that I've missed something. But assuming that r/map and r/fold implement parallelization which is reasonably effective and general, the observed computation time would mean that depending on e.g. the size of the collection and how effectively we can compute our function parallelization can be either notably beneficial or notably detrimental to performance. And we don't necessarily know anything about those factors up until computation time. So which method of computation should one choose in this case? I would expect that in general much more information is needed than what is available to the compiler (or we'd need to place stricter constraints on the language) to automatically parallelize computations with predictable performance.

👀 3
Christian07:01:13

Thank you, that was very interesting for me.

👍 3
Hagenek12:01:51

I am getting some strange error from compojure, must be something silly. Here is my code:

(defn splash []
  {:status 200
   :headers {"Content-Type" "text/plain"}
   :body "Hello from Heroku"})

(defroutes app []
  (GET "/" [] splash)
  (ANY "*" []
       (route/not-found (slurp (io/resource "404.html")))))

(defn -main [& [port]]
  ; (setup-state)
  (let [port (Integer. (or port (env :port) 5000))]
    (jetty/run-jetty (site #'app) {:port port :join? false})))
And output from terminal:
➜  clojure-getting-started git:(main) ✗ lein run
2021-01-18 13:51:54.389:INFO::main: Logging initialized @3562ms to org.eclipse.jetty.util.log.StdErrLog
2021-01-18 13:51:54.802:INFO:oejs.Server:main: jetty-9.4.12.v20180830; built: 2018-08-30T13:59:14.071Z; git: 27208684755d94a92186989f695db2d7b21ebc51; jvm 11.0.7+10
2021-01-18 13:51:54.865:INFO:oejs.AbstractConnector:main: Started ServerConnector@6432fa28{HTTP/1.1,[http/1.1]}{0.0.0.0:5000}
2021-01-18 13:51:54.865:INFO:oejs.Server:main: Started @4039ms
2021-01-18 13:52:05.203:WARN:oejs.HttpChannel:qtp197080602-17: /
java.lang.IllegalArgumentException: Key must be integer
	at clojure.lang.APersistentVector.invoke(APersistentVector.java:294)
	at compojure.core$routing$fn__2329.invoke(core.clj:185)
	at clojure.core$some.invokeStatic(core.clj:2701)
	at clojure.core$some.invoke(core.clj:2692)

roelof13:01:53

Do not kmow if this is the right channel but if there is here a exercism mentor can he/she look at my bob challenge . I almost wait for a week for mentoring

pavlosmelissinos13:01:30

you could also check out #code-reviews

Ilmari Pohjola13:01:43

Hello everyone! I'm new here. Could anyone recommend any tutorial on how to publish a VERY simple web service on any cheap server using Clojure. F.E.. I have done one locally using Ring and some other libraries and I can return json as a response (this one https://github.com/anan44/it-starts-with-clojure). So it works in local server, but I want to apply it globally. Basically I just want expose a simple API (no need for Database or such). I am very uneducated in web-programming and Clojure (I'm a mathematician), so my questions might not even make any sense (please don't get frustrated), but I do have some decent experience in coding OOP-languages generally.

3
Ilmari Pohjola13:01:58

Heroku seems to be a good place to start.

👍 3
jumar13:01:50

The typical advice is to "build an uberjar" and "run it wherever you like". You can just spin up a virtual machine at any provider like AWS, Digital Ocean, Hetzner (quite limited but cheaper) - but it depends on what kind of project this is. Does it have to be always available? Is it just for you and/or friends or something more serious?

Ilmari Pohjola14:01:38

It is just for learning purposes. I have built an uberjar, but the parts "run it wherever you want" and "spin up a virtual machine" are something, that I don't understand. Also in my current code I have specified a port for the server. I have no idea, what effect this has if I just "insert" the uberjar to some other environment than my own laptop. By the way. How do you paste code neatly here. I've seen others do it.

Ilmari Pohjola14:01:54

I don't expect anyone to answer all these questions, and was hoping there would be some tutorial project you could point me to.

Ilmari Pohjola14:01:48

All tutorials that I have found which say "from start to the end - complete etc..." usually miss this part. I have found this "real" deployment -part somewhere else, but they have used some Visual Studio -machinery which basically hides the magic and prevent the understanding (although later on I would use them). I have written an application with VS-code and run it with Leiningen right now. It runs locally. Now I just want to make it so, that I can send the http-requests from the internet. I do believe "complete web-application" -tutorials should always include this part.

Ilmari Pohjola14:01:36

(def app
    (ring/ring-handler
     (ring/router
      [endpoints/math-routes]
      {:data {:muuntaja   m/instance
              :middleware [params/wrap-params
                           muuntaja/format-middleware
                           coercion/coerce-exceptions-middleware
                           coercion/coerce-request-middleware
                           coercion/coerce-response-middleware]}})
     (ring/create-default-handler)))

(defonce running-server (atom nil))

(defn start
  []
  (when (nil? @running-server)
    (reset! running-server (jetty/run-jetty #'app {:port  3000
                                                   :join? false})))
  (println "Server running in port 3000"))

Ilmari Pohjola14:01:47

This is the essential part. If I just build an uberjar, and "place it" to f.e. Azure App Service with some Azure -instructions, should it be ready to go then?

jumar16:01:37

Yes, you should. THe cloud offerings are vastly different. One way is to: 1. register an account at AWS / Digital Ocean / Hetzner / whatever 2. Create a machine from a template (e.g. Ubuntu version x.y) 3. SSH into the machine 4. Build your uberjar 5. Copy the uberjar to the machine 6. Run it via java -jar my-uberjar.jar 7. Make sure to expose the port on the instance firewall (e.g. AWS uses "Security groups" for that) 8. Check if you can access it This is of course a lot of manual work so for anything more serious you want to improve and consider other offerings. One option is to have Azure App Service where you can define a dockerfile to run your service (it can be a really simple wrapper around the uberjar)

Ilmari Pohjola09:01:15

Thanks a lot!! Yeah I am going to use Azure App Service later, but maybe the manual steps will help me to understand some of the magic of web-development.

Ilmari Pohjola13:01:58

I tried to deploy a very simple Api to Heroku, but the heroku instructions used compojure, and the tutorial I followed did not. I ran into lot's of difficulties. Currently the app crashes, and the stack trace shows the problem. There seems to be a syntax error in ring/util/servlet.clj:1:1 if I understand correctly. However I have not touched or even found the ring source code, so how can there be a syntax error? Is it likely that I am missing a depency? Picked up JAVA_TOOL_OPTIONS: -Xmx300m -Xss512k -XX:CICompilerCount=2 -Dfile.encoding=UTF-8 2021-01-24T12:54:12.831812+00:00 app[web.1]: Syntax error (ClassNotFoundException) compiling at (ring/util/servlet.clj:1:1). 2021-01-24T12:54:12.831836+00:00 app[web.1]: javax.servlet.AsyncContext

Ilmari Pohjola13:01:26

(ns mutable-app.web   `(:require`    `[ring.adapter.jetty :as jetty]`    `[environ.core :refer [env]]`    `[ring.middleware.params :as params]`    `[reitit.ring.middleware.muuntaja :as muuntaja]`    `[muuntaja.core :as m]`    `[reitit.ring.coercion :as coercion]`    `[reitit.ring :as ring]`    `[endpoints]))` (def app   `(ring/ring-handler`    `(ring/router`     `[endpoints/math-routes]`     `{:data {:muuntaja   m/instance`             `:middleware [params/wrap-params`                          `muuntaja/format-middleware`                          `coercion/coerce-exceptions-middleware`                          `coercion/coerce-request-middleware`                          `coercion/coerce-response-middleware]}})`    `(ring/create-default-handler)))` (defn -main [& [port]]   `(let [port (Integer. (or port (env :port) 5000))]`     `(jetty/run-jetty #'app {:port  port`                             `:join? false})))`

Ilmari Pohjola13:01:05

(ns endpoints (:require [ring.util.http-response :as response])) (def addition  ["/addition" {:get (fn [request]                                      `(let [params (:query-params request)`                                            `x (Long/parseLong (get params "x"))`                                            `y (Long/parseLong (get params "y"))]`                                        `(response/ok {:total (+ x y)})))}])` (def math-routes   `["/math"`    `addition])`

Ilmari Pohjola13:01:15

And the project file (defproject mutable-app "1.0.0-SNAPSHOT"   `:description "FIXME: write description"`   `:url "http://mutable-app.herokuapp.com"`   `:license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"`             `:url "https://www.eclipse.org/legal/epl-2.0/"}`   `:dependencies [[org.clojure/clojure "1.10.1"]`                  `[ring "1.8.1"]`                  `[metosin/reitit "0.4.2"]`                  `[metosin/ring-http-response "0.9.1"]`                  `[ring/ring-jetty-adapter "1.2.2"]`                  `[ring/ring-devel "1.2.2"]`                  `[environ "0.5.0"]]`   `:min-lein-version "2.0.0"`   `:plugins [[environ/environ.lein "0.2.1"]]`   `:uberjar-name "mutable-app-standalone.jar"`   `:profiles {`       `:production {:env {:production true}`        `:uberjar {:aot :all}}})`

Ilmari Pohjola13:01:19

And finally the procfile: web: java $JVM_OPTS -cp target/mutable-app-standalone.jar clojure.main -m mutable-app.web

jumar06:01:22

There might be something bad with the dependencies - like version conflicts. You can use lein deps :tree to investigate that. Also please include the whole stacktrace - you only pasted a very small portion of it. Slack tip: Use tripe backticks for longer code blocks:

<your code here>

Andrei Stan14:01:38

Hi guys, i'm struggling to get out from this problem i have: I am trying to write a function to get from:

(let [data ([{:id 1 :client-data [{:tag "client data here"}]}
               {:id 1 :document-data [{:tag "docs all around"}]}
               {:id 1 :document-data [{:tag "docs here"}]}
               {:id 1 :document-data [{:tag "docs there"}]}]
              
              [{:id 2 :client-data [{:tag "client2 data here"}]}
               {:id 2 :document-data [{:tag "docs2 all around"}]}
               {:id 2 :document-data [{:tag "docs2 here"}]}
               {:id 2 :document-data [{:tag "docs2 there"}]}])]
    ...........
    )
a new set of data like this form:
{
 {:client-data [{:tag "client data here"}]
   :document-data [[{:tag "docs all around"}]
                  [{:tag "docs here"}]
                  [{:tag "docs there"}]]
 {:client-data [{:tag "client2 data here"}]
  :document-data [[{:tag "docs2 all around"}]
                  [{:tag "docs2 here"}]
                  [{:tag "docs2 there"}]]
                                     
}
Thanks

Max Deineko14:01:18

Not a complete solution (+ should the result really be a map?), but something hopefully helpful:

(->>
 [{:id 1 :client-data [{:tag "client data here"}]}
  {:id 1 :document-data [{:tag "docs all around"}]}
  {:id 1 :document-data [{:tag "docs here"}]}
  {:id 1 :document-data [{:tag "docs there"}]}]
 (map #(select-keys % [:client-data :document-data]))
 (apply merge-with vector))
;; => {:client-data [{:tag "client data here"}],
;;     :document-data
;;     [[[{:tag "docs all around"}] [{:tag "docs here"}]] [{:tag "docs there"}]]}

Andrei Stan14:01:40

@qmstuart hi, can u please write again your solutio, i would like to try it. Thanks

Stuart14:01:52

I realised after it was putting rogue nils in

Stuart14:01:07

(->> (apply concat data)
       (group-by :id)
       (mapv (fn [[_ x]]
               {:client-data   (:client-data (first x))
                :document-data (->> (map (fn [i] (:document-data i)) x) (remove nil?))})))

Daniel Stephens15:01:17

something similar:

(let [data '([{:id 1 :client-data [{:tag "client data here"}]}
              {:id 1 :document-data [{:tag "docs all around"}]}
              {:id 1 :document-data [{:tag "docs here"}]}
              {:id 1 :document-data [{:tag "docs there"}]}]

             [{:id 2 :client-data [{:tag "client2 data here"}]}
              {:id 2 :document-data [{:tag "docs2 all around"}]}
              {:id 2 :document-data [{:tag "docs2 here"}]}
              {:id 2 :document-data [{:tag "docs2 there"}]}])]
  (map
    #(do {:client-data (some :client-data %)
          :document-data (keep :document-data %)})
    data))

Andrei Stan16:01:43

@qmstuart @ULNRSUK8C thank you guys for your time, Daniel's solution works better

jkrasnay14:01:21

You can make that last bit a little cleaner: (->> x (map :document-data) (remove nil?))

Stuart14:01:31

I'm sure there must be a way to do this with merge?

Mno14:01:41

merge-with is probably much more suited for this, but I’m a bit too lazy atm to draw up some code

Max Deineko14:01:01

see above -- iianm something like (map (partial apply merge-with vector)) on grouped map values here should be close to what OP wanted

Mno14:01:27

:thumbsup:

Andrei Stan14:01:51

Thanks guys, i’ll check it when i’ll get back home

roelof15:01:20

anyone who can give me a hint how I can make palingdrome checker that works for this case (true? (__ [:foo :bar :foo]))

clyfe15:01:13

is the thing equal to it in reverse?

roelof15:01:25

I thought so but with this code #(= (clojure.string/join (reverse %) %)))  it fails

Eamonn Sullivan15:01:35

user> (defn palindrome? [thing] (= thing (reverse thing)))
#'user/palindrome?
user> (true? (palindrome? [:foo :bar :foo]))
true
Is that what you are looking for?

clyfe15:01:28

string join operates on strings not lists; why do you need joining anyway? compare collections! https://clojure.org/guides/equality

roelof15:01:31

nope, that one fails on a string

roelof15:01:11

lets say I have "racecar"

roelof15:01:30

when I reverse it , I get a collection of characters

roelof15:01:38

and those two are not the same

roelof15:01:00

so I thought joining them will solve it , but not

clyfe15:01:23

tip: make the string a list not the list a string

roelof15:01:05

oke, time to experiment more

Eamonn Sullivan15:01:24

(defn palindrome? [thing] (cond 
                            (string? thing) (= (clojure.string/reverse thing) thing)
                            (seq? thing) (= (reverse thing) thing)
                            :else false))
One way. Probably not the best way.

roelof15:01:45

can be a lot shorter

#(= (reverse %) (seq  %))
@UCCHXTXV4 @UR71VR71S

🎉 6
yonatan braude15:01:58

hi everyone 🙂 what does ^ mean in ^{key: "value"} ?

andy.fingerhut15:01:47

The ^ indicates that the map that follows is metadata that is associated with the thing that follows, where the thing that follows is usually a collection or symbol.

andy.fingerhut15:01:47

You can see read some more about it here if you wish: https://clojure.org/guides/weird_characters#_and_metadata

👍 3
🙏 3
andy.fingerhut15:01:05

the rest of that page has lots of good description of other special syntax characters in Clojure

frankitox16:01:04

Hi! I'd like to know what's the best practice when using a library that is not an explicit dependency but transitive of an already declared dep. Should I make it explicit adding it to the list of dependencies?

frankitox16:01:11

For example, taoensso.timbre depends on taoensso.encore. I'd like to use taoensso.encore/filter-kvs on my project. I did clj -Stree to list the dependency tree. Now to make the dependency explicit I added taoensso.encore to the list of :exclusions of timbre and then added taoensso.encore in a new line. I used to do this on leiningen, but I'm not completely sure this translates 1 to 1 to clj deps. Hope I'm not missing something here 😅

delaguardo16:01:53

It is better to explicitly declare all dependencies. For example taoensso.timbre might remove taoensso.encore from provided dependencies without changing its API. And your application suddenly will stop working. and this will nether be a case if your explicitly depend on it.

delaguardo16:01:19

in clj cli top-level dependencies takes precedence over transitive one (if the version is higher). so you probably don’t need exclusions either. Only in rare cases when you need top-level dependency to have lower version than the one comming with another lib

Alex Miller (Clojure team)16:01:41

Declare all deps that you explicitly use. Don’t declare deps that you don’t explicitly use (let them be selected as transitive deps).

Alex Miller (Clojure team)16:01:04

That should be the general practice

Alex Miller (Clojure team)17:01:35

The most common reason to diverge from that is if you are resolving a conflict between versions when the same transitive dep is included multiple times (either by declaring a top level dep/version or by using exclusions)

frankitox17:01:47

Thank you so much both 😀, that's what I suspected but wanted to be sure. In this other case you mention Alex, is there any tool to check if there's such a conflict? Or will I get a warning from the cli? Or I need to keep an eye on the output of clj -Stree each time I add a new library?

Alex Miller (Clojure team)17:01:58

You won’t get any error or warning from the cli - it has no way to tell whether there is an issue and will generally just pick the newest version of the library. You might encounter issues at runtime if a newer version of a lib breaks backwards compatibility

Alex Miller (Clojure team)17:01:11

In the Clojure community there is a culture of minimizing such changes so this is relatively uncommon

frankitox17:01:44

Ahh, alright. Yess, I think I only had trouble once with dependencies. Just realized https://github.com/SevereOverfl0w/vizns can help me here, it shows non-declared transitive deps as red nodes. I'll take a peep to that now. Thanks!!

dgb2317:01:39

Is my assumption correct that the textual representation of keywords gets compiled away, which could have positive implications on performance over using strings in some cases?

bronsa17:01:22

no, a keyword is always backed by a string

andy.fingerhut17:01:42

The Clojure/Java implementation does store the string representing a keyword at most once for each keyword, whereas many strings with the same sequence of characters have no such guarantee (i.e. they might all be different JVM objects in memory for strings)

dpsutton17:01:24

when keywords are compared, are the underlying strings compared for equality (and therefore each character in turn compared i guess) or is there a faster compare?

andy.fingerhut17:01:02

Clojure/Java uses reference (i.e. pointer) equality between keywords, identical? . I think ClojureScript is different, but don't know the details.

andy.fingerhut17:01:16

When Clojure/Java reads a keyword, there is a hash lookup on the text of the keyword to see if there is already a keyword object with the same string, part of the process called 'interning', which guarantees that there is at most one JVM object for each keyword.

dpsutton17:01:23

ok. so it would be the case that keywords could have positive implications on performance over using strings in some cases?

dpsutton17:01:12

ah, your "no" was about the textual representation of keywords compiled away, not about the possibility of some positive performance gains

👍 6
Amir J17:01:12

Hi Everyone, We have just released two free courses. If they might be helpful to you, I'd invite you to join either of these.  Android App Development in 2 Hours https://www.udemy.com/course/android-app-development-in-2-hours/ iPhone App Development in 2 Hours https://www.udemy.com/course/iphone-app-development-in-2-hours/

dpsutton17:01:35

this seems kotlin related. does this have any tie to Clojure?

popeye18:01:18

I have similar condition in my script

(and (nil? cond1) (nil? cond2) (nil? cond3) )

popeye18:01:27

can this be shortened ?

popeye18:01:53

lets consider we have n number of nil? condition

noisesmith18:01:54

(every? nil? [cond1 cond2 cond3])

🙌 3
dpsutton18:01:31

this might change the semantics if you rely on short circuiting and side effects

dpsutton18:01:23

oh nevermind. every should be fine

noisesmith18:01:23

are you saying every doesn't short circuit?

dpsutton18:01:47

was thinking there might be some laziness underneath so it might run into chunking issues but just read the source

popeye18:01:34

how laziness causes here?

dpsutton18:01:47

it doesn't. i was completely mistaken

ghadi18:01:54

use truthiness to your advantage (and cond1 cond2 cond3)

🙌 3
hiredman18:01:08

it is more like (and (not cond1) (not cond2) (not cond3)) assuming you are fine treating nil and false the same, which can then be (not (or cond1 cond2 cond3))

Mno19:01:07

Lesson learned there’s quite a few ways to do things. 😅

roelof19:01:01

is there somewhere a example how to send data to a template with ring and compojure ?

dgb2320:01:41

an html template?

noisesmith20:01:25

neither ring nor compojure offer templates, so you need to pick a templating library implementation, the answer will depend on which one you use

noisesmith20:01:02

hiccup has the advantage that the whole thing is vanilla clojure data until you compile to html, selmer has the advantage of being a normal templating language

roelof20:01:15

yep, and im thinking of using hiccup first

roelof20:01:25

later maybe use one of the react one

dgb2320:01:34

Hiccup is simple and popular, other, more advanced libraries are using its syntax or depending on it

noisesmith20:01:49

https://github.com/weavejester/hiccup the README should be enough to get you started

dgb2320:01:51

a similar dichotomy exists with honeysql and hugsql

dgb2320:01:15

one defines sql as clojure data-structures and the other is a nice abstraction over templating sql

noisesmith20:01:33

regarding react: you'll likely find it much easier to start with server side rendering, cljs and react wrappers add a lot of new weirdness beyond what you are working with now

dgb2320:01:55

I very much agree with this!

roelof20:01:24

I know that is why I said that maybe later I will do that

roelof20:01:26

I will again google to find a answer

roelof20:01:47

What im trying to do is to display data I fetched from a external api

noisesmith20:01:16

I linked to the hiccup readme, it uses the same syntax as the popular react frontends, and it includes multiple examples

roelof20:01:50

I will and had take a look at it but could not find what im looking foor

noisesmith20:01:41

could you describe what you are looking for? when I look at it that's the first thing I see, it's a function that takes a data structure and returns html

noisesmith20:01:08

your "templating" is done by filling in the data in the data structure (a normal function does that, you don't need anything special)

roelof20:01:51

The last few days I make a method called get-data that fetches some data from a external api. Now I try to find a way to send the output of that method to hiccup so I can display it

roelof20:01:15

or that hiccup can ask for that data

noisesmith20:01:22

hiccup takes one kind of data: a clojure dta structure

dgb2320:01:25

In simple terms: hiccup is not an object, its just a data structure. If you put in symbols they get evaluated just normally

noisesmith20:01:33

in order to use it, you fill in your data into that structure first

noisesmith20:01:43

there are a few examples in this repo: https://github.com/yokolet/hiccup-samples/blob/master/src/hiccup_templating/views/contents.clj#L20 - in the linked function you pass in a "label", and it returns html data that uses that label

noisesmith20:01:05

that's the general pattern - it's very simple IMHO

roelof20:01:29

oke, I will sleep about it, I now have a collection of collections that holds the data

roelof20:01:17

maybe im overthinking things

noisesmith20:01:40

to expand an example from the README:

user=> (html [:span {:class "foo"} "bar"])
"<span class=\"foo\">bar</span>"
(defn foo-span
   [txt]
   [:span {:class "foo} text])

(html (foo-span "bar"))

noisesmith20:01:04

hiccup is simpler than most people expect, since it just expects you to use standard clojure features to do anything interesting

roelof20:01:11

so I can write a function that calls the function that outputs this :

clj꞉paintings.core꞉>  
({:object-number "SK-A-4830"
  :width 175
  :height 136
  :url
  ""}
 {:object-number "SK-A-1451"
  :width 186
  :height 127
  :url
  ""}
 {:object-number "SK-A-1505"
  :width 127
  :height 198
  :url
  ""}
 {:object-number "SK-A-3841"
  :width 191
  :height 143
  :url
  ""}
 {:object-number "SK-A-2963"
  :width 145
  :height 184
  :url
  ""}
 {:object-number "SK-A-135"
  :width 144
  :height 169
  :url
  ""}
 {:object-number "SK-A-2099"
  :width 145
  :height 179
  :url
  ""}
 {:object-number "SK-A-1796"
  :width 202
  :height 113
  :url
  ""}
 {:object-number "SK-C-2"
  :width 136
  :height 56
  :url
  ""}
 {:object-number "SK-A-180"
  :width 146
  :height 179
  :url
  ""})
clj꞉paintings.core꞉>

roelof20:01:34

what I want to do is to display all images with a <img> tag

roelof20:01:46

but for now GN

andy.fingerhut20:01:24

I would recommend starting by writing the hiccup data manually (not created via a program) that produces what you want for one or two images.

💯 3
andy.fingerhut20:01:55

Once you get that working, then you will know what "shape" of data structure you want to create via a function that works for N images on a page.

noisesmith20:01:11

the result will probably look kind of like [:div (for [img ...] [:img ...]])

andy.fingerhut20:01:13

walk before you run

🎉 3
👍 3
dgb2320:01:43

@andy.fingerhut I’m going to include this in my “programming mantras” sheet that we use to teach introductory programming!

andy.fingerhut21:01:09

I don't know if it adds much, but REPLs can make it much quicker to try out many tentative walking steps very quickly.

👍 3
roelof09:01:10

so as I understand you all well. I do not have to make a seperate template but it is also a method ?

noisesmith15:01:09

the "template" is a clojure data structure, you can use standard clojure functions to construct that data structure and include the items you need to display

noisesmith15:01:45

this example might help:

$ clj -Sdeps '{:deps {hiccup/hiccup {:mvn/version "1.0.5"}}}'
Clojure 1.10.1
(ins)user=> (require '[hiccup.core :as hic])
nil
(ins)user=> (hic/html [:img {:src "foo.png"}])
"<img src=\"foo.png\" />"
(ins)user=> (def images ["foo.png" "bar.png" "baz.png"])
#'user/images
(ins)user=> (hic/html [:div (for [img images] [:img {:source img}])])
"<div><img source=\"foo.png\" /><img source=\"bar.png\" /><img source=\"baz.png\" /></div>"
(ins)user=> (hic/html [:html [:body [:div (for [img images] [:img {:source img}])]]])
"<html><body><div><img source=\"foo.png\" /><img source=\"bar.png\" /><img source=\"baz.png\" /></div></body></html>"

roelof15:01:38

oke im used to that template are seperate files in a seperate directory

roelof15:01:58

but that seems to be not needed here

noisesmith15:01:34

right "template" is a more abstract concept here haha - using a file is one convention for templating, but eg. format is documented to take a "template string" as an argument

noisesmith15:01:32

if you prefer a separate template file, you can actually do this with hiccup, with an edn file (plus a function to fill in your input...) but it's often simpler and clearer to do inline definitions of data structures

roelof15:01:38

oke, time to experiment

roelof15:01:58

and later to see how I can add css to the mix

roelof15:01:23

Right now I have a big css file that does things and I use there before and after filters

noisesmith15:01:15

that works just fine with hiccup (you can add the style names in the {} attribute maps of the elements, and use a [:style "..."] or [:link {:rel "stylesheet" :href "/foo.css"} ...] or whatever to include the css in the page

noisesmith15:01:34

and of course you can put string html snippets inside a hiccup if that's easier

roelof15:01:38

oke, one small step at the time

roelof15:01:53

first experiment till I have the html I want

noisesmith15:01:17

yeah - the hiccup rules are very simple once you get used to them, and it's easy to test any helpers with string comparison

roelof15:01:05

as everything in clojure 😛

roelof15:01:36

was joking

roelof16:01:17

but clojure is al totally other beast then the languages I worked with

noisesmith16:01:18

have you seen the "simple vs. easy" talk? in many ways clojure is simpler than mainstream options, but less easy

roelof16:01:55

nope,I did not see that talk

noisesmith16:01:31

it's an enjoyable talk, and might help you understand clojure's design

roelof16:01:40

oke will do that

sova-soars-the-sora20:01:53

Hi everybody. Hope you are happy and having a smooth day. I was wondering just now... Is there a way I can "screen scrape" content that is loaded by javascript? If I retrieve the HTML of the page it does not have the info I want on it [yet]. Is there a way to programmatically scrape a page after the javascript loads?

noisesmith20:01:19

this library uses a normal web browser as a tool for that task https://github.com/igrishaev/etaoin

noisesmith20:01:56

I don't know of something that is just java/clojure that would reliably fill in the html in a compatible way

sova-soars-the-sora20:01:59

interesting! it seems like I just have to invoke "render" on the html somehow...

AC20:01:09

one option I have used in the past (with python+beautifulsoup) is geckodriver to fetch and render the page and then grab/interact with the DOM. https://github.com/mozilla/geckodriver

sova-soars-the-sora20:01:19

Right on. Well I might be able to use some python thing to scrape and then clojure to render later...

noisesmith20:01:25

right - the problem is that frontend devs are not targetting the html spec (which some clojure lib might implement), they are targetting specific browsers

noisesmith20:01:54

@U0124C56J5R yeah etaoin uses geckodriver, but it also works with chrome

👍 3
sova-soars-the-sora20:01:21

There is also something called PhantomJS ...

noisesmith20:01:39

really the ideal thing is to be able to drive the exact renderer / browser that the web app dev is targetting

noisesmith20:01:22

@U3ES97LAC it exists but is no longer maintained, and I assume no web app dev is ensuring their code works with phantomjs

sova-soars-the-sora20:01:36

Well, cool! Looks like etaoin is the ticket.

Andrew08:01:31

Is there a reason that you can’t use Puppeteer or Selenium ? https://pptr.dev/ https://www.selenium.dev/

Stuart20:01:21

I deserialize a json response from a webserver, but I end up with way more data than I need. After deserialzing I end up with a collection of maps, is their a way to get rid of most fields I don't care about?

(map (fn [{:keys [EndPoint, InstanceName, DatabaseName, DaysSinceLastBackup, LastDataBackupStartDate, DataSizeGb, RecoveryModelDescription, LogSizeGb, StateDescription]}]
         {:EndPoint EndPoint :InstanceName InstanceName ,,,,,} response)
? Or shoudl I just ignore the fields I don't care rather than creating a map with the fields I do care about? There is about 61 fields or so and I only care about 10 or so.

noisesmith20:01:41

that's a lot of bindings to stick into one command line destructure

Stuart20:01:57

yeah, i know 😞

noisesmith20:01:06

the same syntax works in let

noisesmith20:01:22

it looks like you are just doing select-keys

noisesmith20:01:54

(select-keys response [:EndPoint InstanceName ...]) - that vector can be a separate def

Stuart20:01:00

my concern is i have over a thousand maps being created, ancd each with 61 fields. WHen I want the thousand maps, but i only care about 10 of the fields.

seancorfield20:01:11

@qmstuart It's probably more idiomatic to just ignore the keys you don't care about -- unless you have a very specific context in which extra fields are not allowed (such as storing the hash map in a database).

Stuart20:01:25

no, im not storing this stuff. Just processing it. I was thinking having large maps would be a performance issue maybe.

Stuart20:01:28

but it might be fine

noisesmith20:01:48

well, if large maps turned into an issue, I'd use select-keys (maybe with clojure.set/rename-keys too...) upon acquiring the data, but @seancorfield is right that most of the time you can just ignore the keys you don't want

Stuart20:01:55

thanks, @noisesmith I wasn't aware of select keys

andy.fingerhut20:01:59

Map lookup for large maps with N key/value pairs is O(log N) in Clojure, where the base of the log is 32, not 2, and pretty decent on constant factors, too. I wouldn't worry about their performance, until and unless you actually do some benchmarking to determine where most of the time is spent in your code. Clojure maps aren't going to outperform custom-made maps tailored to particular situations, but they are usually close enough that they aren't the performance issue in your application.

💯 3
noisesmith20:01:22

maybe there's some fancy json parser that allows you to whitelist parts of the structure you want, but the complexity that introduces is worse than the cost of unused keys for 99.9999% of cases

Hagenek21:01:26

Ok, so this is a terrible function. How would I write it to accept a map with keys instead (if that is a better idea).

(defn save-create-order
  [external-id service-company-id elevator-id
   contract-id description status due-date
   actual-start-time actual-end-time checklist-id]
  (swap! orders conj (create-order
                      external-id service-company-id elevator-id
                      contract-id description status due-date
                      actual-start-time actual-end-time checklist-id)))

didibus00:01:45

(defn save-create-order
  [{:keys [external-id service-company-id elevator-id
   contract-id description status due-date
   actual-start-time actual-end-time checklist-id}]
  (swap! orders conj (create-order
                      external-id service-company-id elevator-id
                      contract-id description status due-date
                      actual-start-time actual-end-time checklist-id)))

didibus00:01:44

You can also used named parameters if you prefer over passing a map, then it would be:

(defn save-create-order
  [& {:keys [external-id service-company-id elevator-id
   contract-id description status due-date
   actual-start-time actual-end-time checklist-id}]
   ...)

noisesmith21:01:16

often when you have that many args to a function, it should take a map in the first place, by which I mean create-order might need a smaller number of maps as args, and save-create-order would not have to care about all the keys in those maps

noisesmith21:01:39

or perhaps a small number of maps (eg. one for the customer data and one for product related data?)

noisesmith21:01:53

but to literally answer the question: replace (defn save-create-order [external-id ... ] ...) with (defn create-save-order [{:keys [external-id ...]}]) and pass a map that has the matching keys