Fork me on GitHub
#beginners
<
2024-01-24
>
Jim Newton09:01:13

I would like to do something like read-from-file-with-trusted-content from https://clojuredocs.org/clojure.core/read but I'd like to do it effectively from a shell script or makefile. I've processed a correct clojure file and i've elided some expressions which my students are supposed to replace with correct code. I want to check to make sure that my eliding script has not mis-matched the parentheses, brackets, braces, etc. Currently I'm using lein check but that doesn't work, as lein check checks too many semantics for my need, I just want to check read syntax, not compile integrity.

Jim Newton09:01:28

An example of elided code might be the following

(defn hyphenated-names
  [base-name]
  (assert (= base-name (lower-case base-name))
          (cl-format false "hyphenated-names must be called with lower case name, not [~A]" base-name))
  (let [s (io/resource "France-baby-names/nat2020.csv")]
    (with-open [r (io/reader s)]
      (set (for
               ;; CHALLENGE: student must complete the implementation. 
               (throw (ex-info "Missing one or more expressions, not yet implemented" {}))
             ) 
       )
      )))

Jim Newton09:01:40

lein check complains that the for syntax is wrong

Ed10:01:45

if you can clojure.edn/read it, then it's got correctly balanced brackets, and well formed symbols, etc ... is that good enough?

Jim Newton10:01:41

if i'm not mistaken, read both the one in clojure.edn and also clojure.core only read the first sexpression in the stream. right?

Jim Newton10:01:59

so I think I need to loop and read until EOF.

Jim Newton10:01:48

but I don't understand the difference between those to variants of read

Ed10:01:14

edn/read is safe to run and will not modify your running process, while read can.

Ed10:01:36

you can repeatedly read from a reader until the end of the file with something like

(import '[java.io PushbackReader])
(require '[clojure.java.io :as io] '[clojure.edn :as edn])
(with-open [rdr (PushbackReader. (io/reader (io/file (str system-file))))]
  (loop [els []]
    (let [el (edn/read {:eof ::eof}
                       rdr)]
      (if (= el ::eof)
        els
        (recur (conj els el))))))

Jim Newton10:01:28

Actually won't that fail if there is a line containing ::eof in the file?

Jim Newton10:01:45

I'm using something like this:

Jim Newton10:01:48

(defn line-read-file
  "Read all the lines from a file and return an vector of lines which have been read"
  [filename]
  (println [:open filename])
  (with-open [r (java.io.PushbackReader.
                 (clojure.java.io/reader filename))]
    (binding [*read-eval* false]
      (let [eof-value (list :end)]
        (loop [lines []
               line (read r false eof-value)]
          (if (identical? line eof-value)
            (printf "read %d lines from %s\n" (count lines) filename)
            (recur (conj lines line)
                   (read r false eof-value))))))))

Ed10:01:25

::eof is namespaced to the code that you're reading the file in ... so it seems unlikely that it'll be in the code your reading?

Jim Newton10:01:28

it is impossible for read to return a list identical? to a list I've allocated at run-time.

Jim Newton10:01:11

the way read works in CL seems to be the same conceptually as in clojure. you give read a special value and it returns exactly that value if it encounters end of file, then you can test using pointer equality whether it returned exactly that value.

Ed10:01:08

read is explicitly different between cl and clojure

Ed10:01:29

I don't think it's easy to return a value that's identical? to something already in ram from read, unless it's a keyword

Jim Newton10:01:34

that is the point. read returns exactly its 4th argument when it reaches end of file, as long as the third argument is false. it seems to me the correct way to detect end of file is to test whether read returned that exact value as documented.

Ed11:01:29

ah yeah ... sorry - I misunderstood what you meant ... yes, when it gets to the end of the file thing you give it

1
Jim Newton11:01:24

anyway, i can replace clojure.core/read with clojure.end/read, no problem to avoid run-time evaluation

Jim Newton11:01:04

but I suspect *read-eval* controls the same thing.

Jim Newton11:01:56

BTW, I don't understand what `

(java.io.PushbackReader.
                 ( filename))
does. I call ls in the shell before calling the clojure script, and I verify that the file is there. however, when I run the script it seems that is unable to find it.
clojure.lang.Var.applyTo (Var.java:705)
    clojure.main.main (main.java:40)
Caused by: java.io.FileNotFoundException: binarysearch.clj (No such file or directory)
 at java.io.FileInputStream.open0 (FileInputStream.java:-2)
    java.io.FileInputStream.open (FileInputStream.java:219)

Jim Newton11:01:31

i'm calling the script using lein exec. is that changing my CWD?

Ed11:01:52

edn/read supports a subset of clojure while *read-eval* disables some features of the clojure reader

Ed11:01:21

I think you need (PushbackReader. (io/reader (io/file filename)))

Ed11:01:52

io/reader assumes that a string is a url and tries to fetch it

Jim Newton11:01:53

yes, lein exec changes the directory. I print the value of (-> (.File. ".") .getAbsolutePath) inside the script, and it is different than the directory lein exec is called from.

Ed11:01:16

oops ... tell a lie ... it does this

(try
                          (make-input-stream (URL. x) opts)
                          (catch MalformedURLException e
                            (make-input-stream (File. x) opts)))

Jim Newton11:01:08

is that wrong?

Ed11:01:01

nope ... it's me that's wrong - if you pass io/reader a string it will try and turn it into a url and if it's invalid it'll assume it's a file and try to read that - you problem is probably that you've got a relative path and you're in the wrong dir

Jim Newton11:01:35

my string is exactly the string "binarysearch.clj" and I've verified it is in the directory I started lein in. But within the program I print the directory and it is no longer the same directory.

Jim Newton11:01:03

perhaps lein tries to walk up the directory to the top of the project???

Jim Newton11:01:24

it is surprising, but i can call my script with full path names to avoid this annoyance.

Ed11:01:38

quite possibly ... I've not used lein in ages and can't remember what it does

Ed11:01:25

by "top of the project" do you mean "where the project.clj file is"?

Ed11:01:44

if so I wouldn't be surprised if lein changed dir to there before doing anything else

Jim Newton11:01:27

seems it does that. changes directory to where it finds the project.clj file

Jim Newton11:01:00

I didn't bother reading the documentation of lein exec. it is probably documented behavior.

Jim Newton10:01:31

what is the clojure equivalent of the common lisp (and all other lisps) eq ?

delaguardo10:01:09

via interop:

(let [x (Object.)]
  (.equals x x))
;; => true

(.equals (Object.) (Object.))
;; => false

Jim Newton11:01:23

why not use identical?

delaguardo11:01:45

ha) I never use it because clojure's equivalent semantic is good enough for all my cases. IMHO, equivalency check makes sense only if you do heavy interop to java. That is why I never bothered to look for identical? function.

Jim Newton11:01:39

as I understand it is the only way to dependably use the 4th argument of read

Jim Newton11:01:24

read returns its 4th argument exactly when trying to read past end of file. to detect this you must use identical? to distinguish the improbable case that it read something = to your 4th argument

Jim Newton11:01:31

at least that how it works in common lisp and it looks like clojure read was modeled off that.

delaguardo11:01:49

(let [t (random-uuid)]
  (loop [acc [] rdr rdr]
    (let [x (read {:eof t} rdr)]
      (if (= x t)
        acc
        (recur (conj acc x) rdr)))))
You don't have to use identical? and btw, you have to have really strong argument to use read . IMHO, it is always a mistake. Use clojure.edn/read instead

Jim Newton11:01:17

my sense was if I'm reading edn data I should use end/read and if i'm reading clojure code I should use normal read but just make sure *read-eval* is bound to false.

Jim Newton11:01:25

what is the reasoning I'm missing?

Jim Newton11:01:41

what is random-uuid ?

Jim Newton11:01:17

it seems like you're just making it very unlikely to read t from the file. it seems better to me to depend on the documented behavior of read and just detect whether it returned the think I said to return on eof.

Jim Newton11:01:36

no need to generate a randomized uuid

Jim Newton11:01:51

BTW, my original question was also intended to ask whether there is already a function which will slurp in the entire file, or whether the programmer is required to explicitly call read (or cousin) repeatedly

delaguardo12:01:10

> what is random-uuid ? function to generate a random UUID. it doesn't matter what function to use here. In most place where I have to use clojure.edn/read I simply use ::eof as a token. And yes, it might not consume input completely when by some reason there is a single top-level token matching :some.namespace/eof .

delaguardo12:01:00

> it seems better to me to depend on the documented behavior of read What makes you think I'm suggesting you something different?

Jim Newton12:01:24

because if you use any token for the eof marker, if read reads that object at the top level, read will return in and your equivalence check will match and the read loop will terminate before the file is completely exhausted. it seems to me the only way to detect eof is to detect whether read returned exactly and identically your given eof marker, and to distinguish that from the case when read returned something different but not identical to that eof marker.

Jim Newton12:01:26

wrt the clojure doc, I did read that. it advises that you should not read untrusted data with read. that's not my case. I'm reading data which I just created. However, if there's some other reason than trust, perhaps I didn't really understand what I read.

delaguardo12:01:13

Then why you read data you just created?

andy.fingerhut14:01:45

You can use a freshly created Java Object instance as the sentinel value that read returns if it gets an error, and then use identical? between that freshly created object and the return value. I don't know any other shorter way to guarantee whether the return value is from the file vs. not.

andy.fingerhut14:01:48

I believe for a freshly created Object instance like that, that Clojure = will be handled by reference equality, too.

Jim Newton18:01:48

> Then why you read data you just created?

Jim Newton18:01:31

because I didn't create it in the same process. I created in this case from a python program.

Stephen11:01:07

I want to point my :expr-deps in my :aliases to a directory where there are many .jar files which are my dependencies. From how I understand this, I can add a directory and must not specify all the jars one by one (see screenshot) I use something like this (screenshot2) but it gives me:

Error building classpath. Local lib lib/libs not found: /obox/wwww/WEB-INF/lib

1
delaguardo11:01:59

:local/root must be either a path to jar or path to clojure project with manifest (e.g. deps.edn).

Stephen11:01:32

Ahh sh*t, so I must specify them one by one?

Stephen11:01:04

Okay, thanks man!! 🙂

delaguardo11:01:49

you can also write a small babashka script to prepopulate your project's deps.edn based on a content of some folder )

Stephen11:01:24

hehe ,good idea... I may give it a try, or just type them out 😉

Alex Miller (Clojure team)13:01:39

This is a total hack but Java accepts limited form of wildcarding so it probably works to add “dir/*” to your :paths or :extra-paths. Haven’t tried it

Stephen16:02:09

Guys I am really struggeling. I have org.babashka/http-client {:mvn/version "0.4.15"} in deps.edn and in my (ns my-ns (:require [babashka.http-client :as http])) but I get :

; Syntax error compiling at (babashka/http_client/interceptors.clj:1:1).
; namespace 'babashka.http-client.internal.multipart' not found

Stephen16:02:10

What am I doing possibly wrong?

Jim Newton11:01:36

I asked some months ago whether anyone was interested in volunteering to be a guinea pig for some/all of my student exercises for a course I'm developing. I've lost the thread.

delaguardo12:01:53

off-topic, please use threads for multiple messages. It is asked in channel's description

1
Jim Newton12:01:09

thanks for the reminder

seancorfield20:01:19

@U010VP3UY9X I've deleted several of your top-level channel messages from this morning. In future, please stay on topic for the channel and use threads -- we have plenty of other channels where some of that would have been on-topic /admin

Jim Newton11:01:41

is anyone interested in looking at some of these exercises and giving me feedback?

Jim Newton12:01:11

i'm doing a last pass through the homework problems fixing all the docstrings I see as being problematic

Jim Newton12:01:42

i'm surprised to see that although map takes an n-ary function an n-many sequences to map across in parallel, every? unfortunately does not.

Jim Newton12:01:20

or perhaps it does but is not documented as such.

Jim Newton12:01:09

something like the following ought to work

(every? == '(1 2 3) '(1.0 2.0 3.0))

Ed12:01:31

it does not, you'll need to (every? mytest (map vector col1 col2 col3))

delaguardo12:01:39

and what if

(every? == '(1 2 3) '(1.0 2.0 3.0 4.0))

Ed12:01:10

float comparisons are dangerous things

Jim Newton12:01:35

are you asking how should it be implemented? I'd say do it like common lisp does it. but i understand that someone else might say not to use CL as the standard.

delaguardo12:01:12

No, I'm asking about what you expect

Jim Newton12:01:27

in CL it is documented that if the 2nd list (or nth) list is longer than the first, the extra values are ignored. but if the first argument is longer than any subsequent, then a run-time error is signaled

Jim Newton12:01:46

isn't that what map does?

Jim Newton12:01:57

I guess I'd like it to be consistent with map

delaguardo13:01:27

no, map takes from args until one of them is run out

1
Jim Newton13:01:00

@U0P0TMEFJ just an example. actually what I wanted was (every? (almost-equal 0.0001) '(1 2 3) '(1.0 2.000000000001 3.0) )

Ed13:01:56

how does (every? (partial apply ==) (map vector [1 2 3 4 5] [1.0 2.0 3.0 4.0 5.0])) seem?

Jim Newton13:01:13

good to know about map.

delaguardo13:01:14

but anyway, every? implies it should eagerly check everything. That is why I'm asking what do you expect in a situation when there are uneven length sequences

Jim Newton13:01:49

no, i'd expect it to work by the same traversal rules as map

Jim Newton13:01:13

@U0P0TMEFJ that's ugly, but not your fault. could also do some sort of take-until-false or take-until-true concoction with (map == list-1 list-2)

delaguardo13:01:32

(defn my-every? [pred & args]
  (if (= 1 (count args))
    (every? pred (first args))
    (every? #(apply pred %) (apply map list args))))

(my-every? == '(1 2 3) '(1.0 2.0 3.0))
;; => true

(my-every? == '(1 2 3) '(1.0 2.0001 3.0))
;; => false

Ed13:01:18

take-until-false - that's what every? does ... right?

🤪 1
Jim Newton13:01:08

there's some function like some which asks whether something in the list is false.

Jim Newton13:01:56

I always mix up the functions like some vs some? etc

Jim Newton13:01:30

(every? identity (map == list-1 list-2))

👍 1
picard-facepalm 1
Valery Kocubinsky15:01:23

Hi, how in clojure make sorted subset of sorted set ? In java I can do next

jshell> new TreeSet<>(Arrays.asList(2,3,4,5,6)).headSet(3, true)
$13 ==> [2, 3]
Result is again SortedSet. So I can continue to work with result as with sorted set
jshell> new TreeSet<>(Arrays.asList(2,3,4,5,6)).headSet(3, true).headSet(2,true)
$14 ==> [2]
In clojure I found next way
user> (apply sorted-set (subseq (sorted-set 2 3 4 5 6) <= 3))
#{2 3}
Does exists more simple way to create subset of sorted set?

delaguardo15:01:04

(into (sorted-set) (subseq #{,,,} <= 3))

delaguardo15:01:39

or clojure.set/select

(type #{1 2 3})
;; => clojure.lang.PersistentHashSet

(type (sorted-set 1 2 3))
;; => clojure.lang.PersistentTreeSet

(type
 (clojure.set/select #(<= % 3) (sorted-set 1 2 3)))
;; => clojure.lang.PersistentTreeSet

andy.fingerhut15:01:09

@U04V4KLKC Did you mean to use subseq instead of subset in your code snippet above?

dpsutton15:01:41

i think those proposals are linear whereas the ask is for a less-than-linear approach

andy.fingerhut15:01:32

It should be linear in the size of the subset, not the full set you are taking the subset of, but if that is what is desired, I am not aware of anything built into Clojure that would achieve faster-than-linear subset creation like that.

delaguardo15:01:46

🤷 the question was about returned type not performance

andy.fingerhut16:01:17

(and to be precise, when I say "linear" here it is really more like O(N log N) than it is O(N))

dpsutton16:01:12

clojure.data.avl has a sorted set with some nice properties

"An implementation of persistent sorted maps and sets based on AVL
  trees which can be used as drop-in replacements for Clojure's
  built-in sorted maps and sets based on red-black trees. Apart from
  the standard sorted collection API, the provided map and set types
  support the transients API and several additional logarithmic time
  operations: rank queries via clojure.core/nth (select element by
  rank) and clojure.data.avl/rank-of (discover rank of element),
  \"nearest key\" lookups via clojure.data.avl/nearest, splits by key
  and index via clojure.data.avl/split-key and
  clojure.data.avl/split-at, respectively, and subsets/submaps using
  clojure.data.avl/subrange."

dpsutton16:01:06

❯ clj -Sdeps '{:deps {org.clojure/data.avl {:mvn/version "RELEASE"}}}'
Downloading: org/clojure/data.avl/maven-metadata.xml from central
Clojure 1.11.1
user=> (require '[clojure.data.avl :as avl])
nil
user=> (avl/subrange (avl/sorted-set 0 1 2 3 4 5) > 1)
#{2 3 4 5}

Valery Kocubinsky16:01:42

Thanks for all answers!

Marcelo Feodrippe18:01:51

Lets say that I have the following

;;core.clj
(ns tree-prun-api.core
  (:require [tree-prun-api.infra.repository :as r :refer [->GisRepository]])
  (:gen-class))
   ;;here a a instance of a type from tree-prun-api.infra.repository, that uses scripts, is called
  ...

;;repository.clj 
(ns tree-prun-api.infra.repository
  (:require [tree-prun-api.domain :as d :refer [AGisRepository 
                                                DataResponse
                                                make-entity
                                                ->GeoCoordinate]]
            [tree-prun-api.infra.scripts :refer [scripts]]
            [clojure.java.jdbc :refer [query execute!]])
  (:gen-class)) 
  ;;here scripts from tree-prun-api.infra.scripts is called
  ...

  
;;scripts.clj
ns tree-prun-api.infra.scripts
  (:require [clojure.string])
  (:gen-class))
  
  (defn getScript
  ([typeName]
   [typeName
    (slurp
     (clojure.string/replace
      *file*
      #"scripts.clj"
      (str "scripts/" (name typeName) ".sql")))]))

(def scriptBinds
  [:getPoles
   :getPolesFilterCoords
   :getFeederCircuits
   :getPowerTranformers
   :getPowerTranformers
   :getSwitches
   :getTowers
   :getWires
   
   :insertPole])

(def scripts  
  (reduce
   #(try
      (conj % (getScript %2))
      (catch Exception e
        (println ";" (.getMessage e))
        %))
   {} scriptBinds))
I'm using lein to run. When I use it inside ns tree-prun-api.infra.scripts, file works ok. And if i run things there all good. But if I need to start the project on ns tree-prun-api.core, that require tree-prun-api.infra.repository, and inside repository require tree-prun-api.infra.scripts, seems not to work. Is there any way to that file (any other methot to replace file here) be relative scripts.js location (tree-prun-api.infra.scripts)? Image here showing data file structure,

Marcelo Feodrippe18:01:24

I'm trying to read scripts folder that are on same folder "infra" where is scripts.clj , so I can read each script. (relative to it)

hiredman18:01:59

*file* is bound at compile time when the compiler loads a file, outside of that context the value it has is undefined

hiredman18:01:25

the way this is kind of thing is usually done is using what are called "resources" which are things that are not class files that can be loaded from classloaders

hiredman18:01:06

http://clojure.java.io/resource is a convenient way to get a handle on a resource

hiredman18:01:04

most build tools(like lein) follow the convention of putting resources in a distinct folder usually "resources/"

👍 1
Marcelo Feodrippe19:01:20

Got it, thanks. I shall move the files to there right now

Marcelo Feodrippe19:01:19

I wanted to leave inside infra, because is part of it haha. But i'll move them to there right now

hiredman19:01:39

you can leave them in there

hiredman19:01:52

it all gets mingled together at the end

hiredman19:01:02

a separate resources/ folder is something build tools do just so people can feel more organized

🙌 1
Aaron Martin21:01:40

Hi All (moved this from the clojure channel to here), Been lurking for a while now, was curious if anyone has used this with Kafka, if it's even possible? I'm looking to change up the way I do CEP and have gotten more into Clojure and thought this would be neat to play around with. https://github.com/PyroclastIO/metamorphic/tree/master

Patrick Delaney03:01:58

I have not used metamorphic, but if I understand correctly, Jackdaw might be something you could use https://github.com/FundingCircle/jackdaw

Aaron Martin21:01:09

Thanks! I'll check this out