Fork me on GitHub
#beginners
<
2021-12-24
>
Eugen00:12:56

what is the idiomatic way to translate this java pattern to clojure inside a let block? I see it quite often

File[] files = nullable-array-of-files ; 
    if (files == null) {
      System.out.println("directory " + directoryFile + " not found");
      files = new File[0];
    }
aa
(let [files (build-my-files)
      files (check-files-with-error-printlin-and-replace)] 
;; do more processing with final files value
)

seancorfield00:12:23

@eugen.stan A follow-up on this with a real example:

user=> (file-seq (java.io.File. "src"))
(#object[java.io.File 0x73d6d0c "src"] #object[java.io.File 0xe36bb2a "src/rcf.clj"] #object[java.io.File 0x3961a41a "src/simple.clj"])
user=> (filter #(re-find #"\.clj" (str %)) (file-seq (java.io.File. "src")))
(#object[java.io.File 0x654c1a54 "src/rcf.clj"] #object[java.io.File 0x5bdaf2ce "src/simple.clj"])
user=> (filter #(re-find #"\.cljs" (str %)) (file-seq (java.io.File. "src")))
()

seancorfield00:12:12

As an example of finding all files (recursively) that match a certain pattern similar to that Java code.

Eugen00:12:18

thanks, I got that part covered. main issue is that I try to convert code at-literam and that is not always a good thing. It is a goal to be as close to the Java implementatio as possible because I am translating an example. People should be able to follow the Clojure and Java examples side by side - sort of.

seancorfield00:12:55

@eugen.stan I think it would be more idiomatic in Clojure to do something like:

(if-let [files (seq (find-files directory-file))]
  (process files)
  (println "directory" directory-file "not found"))

Eugen00:12:24

yes, I am trying to translate Java code and I keep forgeting that I need to translate AND adapt it to Clojure's way. if-let is the java code's intention - but Java can't do functional so it uses that idiom

1
seancorfield00:12:54

It probably makes more sense to return an empty sequence of files in the non-matching case, than to return nil.

seancorfield00:12:41

After all:

user=> (filter odd? [2 4 6 8])
()

Dmitrii Pisarenko20:12:57

Hello! I have the following code:

(defn render-idea-table-body
  []
  (let [ideas (:ideas data)
        criteria (:ideality-criteria data)
        ideas-seq (filter #(not (nil? (second %))) (seq ideas))
        criteria-values (compose-criteria-values-map ideas-seq
                                                     data)
        ]
       (apply concat-csv-field (map render-idea-table-row
                                ideas-seq
                                ))))
 
I want to pass to render-idea-table-row 1. key-value pairs from ideas-seq (`ideas` is a map), 2. criteria and criteria-values. For every call of render-idea-table-row in that map call 1. the values from ideas-seq will be different, but 2. criteria and criteria-values will always be the same. I have a feeling that there is a name for a construct like this. What are such constructs called? Update 1: Lexical closures?

seancorfield20:12:30

Sounds like you want this: (map #(render-idea-table-row % criteria criteria-values) ideas-seq) -- assuming render-idea-table-row takes three arguments (the k/v pair, the criteria, and the criteria values)?

Dmitrii Pisarenko20:12:01

@seancorfield Thanks! I found a solution with partial(see below) but yours looks more succinct.

(defn render-idea-table-body
  []
  (let [ideas (:ideas data)
        criteria (:ideality-criteria data)
        ideas-seq (filter #(not (nil? (second %))) (seq ideas))
        criteria-values (compose-criteria-values-map ideas-seq
                                                     data)
        render-row-fn (partial render-idea-table-row criteria
                               criteria-values)
        ]
       (apply concat-csv-field (map render-row-fn
                                ideas-seq
                                ) )
    ; TODO: Get a list of all ideas
    ; TODO: Add a loop here
    )
  )

seancorfield21:12:08

It's not idiomatic in Clojure to put trailing ) on separate lines -- that makes your code kind of hard to read for a lot of Clojurians.

seancorfield21:12:57

I'd also suggest changing render-idea-table-body to accept data as an argument, rather than having it refer to some global data Var. That will make it easier to test and work with in the REPL.

seancorfield21:12:59

(not (nil? (second %))) could be (some? (second %)) and you don't need to call seq there -- filter will call seq for you automatically.

seancorfield21:12:37

(filter (comp some? second) ideas)

Muhammad Hamza Chippa20:12:35

I have a list of url [url1 url2 url3 .. ] and I am trying to fetch data from the vector of urls using the code given below

{:response (doall (map (fn [url]
                  (let [response (client/get url
                                             {:query-params {:auth "KesmU1sqxt8wS"}})
                        response-data (:rows (:data (json/read-str (:body response) :key-fn keyword)))
                        ]
                    response-data
                  
                  )))

                urls)}
but I am getting the error something like this
2021-12-25 01:47:03,321 [XNIO-1 task-1] DEBUG io.undertow.request.error-response - Setting error code 500 for exchange HttpServerExchange{ GET /private/intra-day-analysis} 
java.lang.RuntimeException: null
	at io.undertow.server.HttpServerExchange.setStatusCode(HttpServerExchange.java:1473) ~[undertow-core-2.2.4.Final.jar:2.2.4.Final]
	at io.undertow.server.HttpServerExchange.setResponseCode(HttpServerExchange.java:1444) ~[undertow-core-2.2.4.Final.jar:2.2.4.Final]
	at immutant.web.internal.undertow$eval40290$fn__40321.invoke(undertow.clj:149) ~[na:na]
	at immutant.web.internal.ring$eval10093$fn__10133$G__10080__10140.invoke(ring.clj:106) ~[na:na]
	at immutant.web.internal.undertow$eval40290$fn__40328$action__40329.invoke(undertow.clj:157) [na:na]
	at immutant.web.internal.undertow$eval40290$fn__40328.invoke(undertow.clj:172) [na:na]
	at immutant.web.internal.ring$eval10093$fn__10116$G__10082__10127.invoke(ring.clj:106) [na:na]
	at immutant.web.internal.ring$write_response.invokeStatic(ring.clj:164) [na:na]
	at immutant.web.internal.ring$write_response.invoke(ring.clj:157) [na:na]
	at immutant.web.internal.undertow$create_http_handler$reify__40368$fn__40369.invoke(undertow.clj:241) [na:na]
	at immutant.web.internal.ring$handle_write_error.invokeStatic(ring.clj:155) [na:na]
	at immutant.web.internal.ring$handle_write_error.invoke(ring.clj:142) [na:na]
	at immutant.web.internal.undertow$create_http_handler$reify__40368.handleRequest(undertow.clj:240) [na:na]
	at org.projectodd.wunderboss.web.undertow.async.websocket.UndertowWebsocket$2.handleRequest(UndertowWebsocket.java:109) [wunderboss-web-undertow-0.13.1.jar:na]
	at io.undertow.server.session.SessionAttachmentHandler.handleRequest(SessionAttachmentHandler.java:68) [undertow-core-2.2.4.Final.jar:2.2.4.Final]
	at io.undertow.server.Connectors.executeRootHandler(Connectors.java:387) [undertow-core-2.2.4.Final.jar:2.2.4.Final]
	at io.undertow.server.HttpServerExchange$1.run(HttpServerExchange.java:841) [undertow-core-2.2.4.Final.jar:2.2.4.Final]
	at org.jboss.threads.ContextClassLoaderSavingRunnable.run(ContextClassLoaderSavingRunnable.java:35) [jboss-threads-3.1.0.Final.jar:3.1.0.Final]
	at org.jboss.threads.EnhancedQueueExecutor.safeRun(EnhancedQueueExecutor.java:2019) [jboss-threads-3.1.0.Final.jar:3.1.0.Final]
	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.doRunTask(EnhancedQueueExecutor.java:1558) [jboss-threads-3.1.0.Final.jar:3.1.0.Final]
	at org.jboss.threads.EnhancedQueueExecutor$ThreadBody.run(EnhancedQueueExecutor.java:1423) [jboss-threads-3.1.0.Final.jar:3.1.0.Final]
	at java.lang.Thread.run(Thread.java:748) [na:1.8.0_312]
where as when i am using client/get for single url it is working fine, but when I am trying to fetch the data using map I am getting this error (sorry for the long error)

seancorfield20:12:08

I don't see where your code appears in that stacktrace but my first thought is that one of your URLs is returning a body that cannot be read as JSON and so json/read-str is throwing an exception which is handled by default by Immutant's Ring adapter as "internal server error", hence the 500 status code.

seancorfield20:12:46

I would wrap the (let ...) inside your function in try/`catch` and print out the exception that it catches.

seancorfield20:12:37

(try (let ...) (catch Throwable t (println t url))) -- so you can see which URL is causing the error.

Stuart20:12:12

Is their an idiomatic way in clojure to go from a ratio to a long? I'm doing (/ x y) and getting back x/y, normally i would explicitly make one have .0 after it, but they are both variables. I need to see the actual long value with a few decimal places. Should I just use (format) ?

seancorfield21:12:40

Long values do not have decimal places.

seancorfield21:12:56

Do you mean a double value instead?

Stuart21:12:02

yes, sorry

seancorfield21:12:27

user=> (let [x 42 y 13] (double (/ x y)))
3.230769230769231

👍 1
Stuart21:12:42

yeah, that's it! thanks

Stuart21:12:02

Is their a way to do division without it giving me a ratio at all ? I assume its slower if its dealing with ratios

seancorfield21:12:35

user=> (let [x 42 y 13] (/ (double x) (double y)))
3.230769230769231

seancorfield21:12:05

@U013YN3T4DA You only really need one call to double there but this is nice and explicit

Muhammad Hamza Chippa21:12:40

@seancorfield I am getting this error now

2021-12-25 02:03:52,569 [XNIO-1 task-1] ERROR centipair.middleware - #error {
 :cause clojure.core$map$fn__5880 cannot be cast to java.lang.Number
 :via
 [{:type java.lang.ClassCastException
   :message clojure.core$map$fn__5880 cannot be cast to java.lang.Number
   :at [clojure.lang.Numbers isPos Numbers.java 121]}]
 :trace

seancorfield21:12:24

You are using a function where a number is expected.

seancorfield21:12:52

Hard to tell, based on your code formatting, but I think you have your parentheses incorrect somewhere.

seancorfield21:12:28

The fragment you posted seems to have (doall (map ..) urls) -- and urls should be inside the map call.

seancorfield21:12:01

Instead of (doall (map (fn ..) urls)) you could say (mapv (fn ..) urls) and that would produce a vector (without laziness).

seancorfield21:12:26

See if this helps:

user=> {:response (mapv (fn [url]
  #_=>                    (try
  #_=>                      (-> url
  #_=>                          (client/get {:query-params {:auth "..."}})
  #_=>                          :body
  #_=>                          (json/read-str :key-fn keyword)
  #_=>                          :data
  #_=>                          :rows)
  #_=>                      (catch Throwable t
  #_=>                        (println t url))))
  #_=>                  urls)}

Stuart21:12:10

😑 of course, I'm obviously in an overthinking mode

Stuart21:12:49

thank you sean

Eugen23:12:01

hi, is there a way to make a class from clojure available without gen-class or AOT compilation ? I'm implementing calcite CSV example and I need a class (schema factory) to be loaded by next.jdbc

Eugen23:12:33

source code is here: https://github.com/ieugen/clojure-training/blob/main/csv-clojure/src/ro/ieugen/calcite_csv.clj . Running this:

(comment 
  (let [db {:jdbcUrl "jdbc:calcite:model=resources/model.json"
            :user "admin"
            :password "admin"}
        ds (jdbc/get-datasource db)]
    (jdbc/execute! ds ["select * from sales"]))
)
will try to load
"factory": "org.apache.calcite.adapter.csv.CsvSchemaFactory",
which results in:
Execution error (ClassNotFoundException) at jdk.internal.loader.BuiltinClassLoader/loadClass (BuiltinClassLoader.java:581).
; org.apache.calcite.adapter.csv.CsvSchemaFactory
Because the class should be provided by my implementation.

Eugen23:12:11

Looking at this, the classloader is from jdk so probably not

Eugen23:12:07

looking at the stacktrace, I think I might implement a calcite class that can load clojure namespace / functions instead

org.apache.calcite.model.ModelHandler/visit (ModelHandler.java:277)
org.apache.calcite.model.JsonCustomSchema/accept (JsonCustomSchema.java:66)
org.apache.calcite.model.ModelHandler/visit (ModelHandler.java:200)
org.apache.calcite.model.ModelHandler/<init> (ModelHandler.java:106)
org.apache.calcite.jdbc.Driver$1/onConnectionInit (Driver.java:101)
org.apache.calcite.avatica.UnregisteredDriver/connect (UnregisteredDriver.java:139)

seancorfield23:12:57

@eugen.stan My first thought is that you are missing one of the Calcite libraries in your dependencies.

Eugen23:12:32

thanks, that is the part I am implementing in my code - but need to supply as a class

Eugen23:12:53

or hack calcite model to implement ns loading 😄

seancorfield23:12:37

You are missing org.apache.calcite/calcite-csv {:mvn/version "1.28.0"} in your deps.edn file I think?

seancorfield23:12:32

Yeah, if you add that dependency, you'll have that factory class:

(! 539)-> clojure -Sdeps '{:deps {org.apache.calcite/calcite-csv {:mvn/version "1.28.0"}}}' -M:rebel
[Rebel readline] Type :repl/help for online help info
user=> (import org.apache.calcite.adapter.csv.CsvSchemaFactory)
org.apache.calcite.adapter.csv.CsvSchemaFactory
user=> 

Eugen23:12:34

I don't think so. I am trying to implement that example - available here: https://github.com/apache/calcite/blob/master/example/csv/src/main/java/org/apache/calcite/adapter/csv/CsvSchemaFactory.java . If I add that jar - my code won't be used

Eugen23:12:51

yes, it works but it will laod the java class

Eugen23:12:18

I want to implement calcite CSV engine in Clojure

Eugen23:12:53

I have done that, but I need to supply a class for the Calcite JDBC driver to make it work

Eugen23:12:29

hence my question: can I make a class from:

(defn csv-schema-factory
  ^SchemaFactory []
  (reify SchemaFactory

Eugen23:12:53

I know about :gen-class and AOT - was curios if I can do it without

seancorfield23:12:57

You're trying to write your own version of org.apache.calcite.adapter.csv.CsvSchemaFactory with exactly that name?

Eugen23:12:10

not that name, it can be any name

Eugen23:12:38

it can be genrated by clojure for all I care - but then I will have to generate the model.json with that name

Eugen23:12:06

the name stuck because I got stuck

seancorfield23:12:53

Trying to replace a class in a package you don't "own" and expecting it to work with other Java classes from that package tree is... unorthodox... and I wouldn't expect it to work, to be honest.

Eugen23:12:10

I'm not trying to do that

seancorfield23:12:42

Then I don't understand what you're asking, sorry.

Eugen23:12:54

let's use "factory": "ro.ieugen.calcite_csv.SchemaFactory",

Eugen23:12:08

can I generate the class at runtime ?

Eugen23:12:15

or do I have to compile my code ?

seancorfield23:12:32

You can call gen-class as a function.

seancorfield23:12:24

But. It has to be called in "compiling" mode. So you're really out on the edges of what is possible.

Eugen23:12:25

and if I get the class name back I can write a model.json file that I can then read back via jdbc calls

Eugen23:12:53

thanks, that is a lead

seancorfield23:12:35

That whole area is a giant mess and pretty complicated... good luck.

Eugen23:12:36

the next thing I can do is add clojure support for calcite to work with clojure namespace + fn, not only java class

Eugen23:12:46

thanks for the heads up

seancorfield00:12:05

The recommended approach for stuff like this is usually to write an actual Java class as the entry point and then call the Clojure Java API so you can load Clojure code dynamically.

seancorfield00:12:48

I think your problem here is going to be that Schema extends AbstractSchema and Clojure doesn't really support extending abstract Java classes.

Eugen00:12:40

I'm implementing the interfaces direclty

seancorfield00:12:50

You'll probably end up having to implement the whole of the Schema interface directly -- reimplementing pretty much everything in AbstractSchema...

seancorfield00:12:24

Okay. Well, I guess at least you know what mountain you have to climb 🙂

Eugen00:12:25

yes, I did that - was not as intimidating as I thought

Eugen00:12:56

yes, some Christmas coding 🙂

🎄 2
🎁 1