clojure

New to Clojure? Try the #beginners channel. Official docs: https://clojure.org/ Searchable message archives: https://clojurians-log.clojureverse.org/
2021-06-16T04:33:26.429400Z

Ya, I got the impression print-dup is very much an internal detail, I don't know for what exactly. But for the user, you would want to extend print-method instead, see here: https://groups.google.com/g/clojure/c/R-9Pwk3HcFk

2021-06-16T04:33:44.429600Z

This is true for prn and pr-str as well

Juλian (he/him) 2021-06-16T12:31:47.430700Z

what's the proper way to parse HTML? there's clj-xpath, but that expects valid XML. should I have a look at hickory? or is there some other library I didn't find yet?

flowthing 2021-06-16T12:35:43.430800Z

https://jsoup.org/ probably

✅ 1
jumar 2021-06-16T12:53:22.431900Z

I used Hickory in the past and it worked well

borkdude 2021-06-16T13:54:57.432400Z

I personally like jsoup a lot

alexmiller 2021-06-16T14:06:13.432800Z

I thought you were supposed to use regex?

😆 2
alexmiller 2021-06-16T14:07:42.433100Z

https://stackoverflow.com/a/1732454/7671

😂 1
borkdude 2021-06-16T14:10:02.433600Z

@alexmiller I think using clojure.spec.alpha is the correct approach

2021-06-16T14:19:04.433900Z

it's been some years, but a huge gotcha for the jsoup bindings I found: it returns a lazy-seq of document tags and data, and it creates a gc root for the underlying jsoup object in a helper thread it only allows the jsoup object to be freed if you consume the entire document the issue I ran into is that I wanted to lazily scan each document for a specific tag containing matching data (it's a lazy-seq, this is what they are for right?) and the book-keeping the library did meant I had to choose between reading an entire document I don't need or leaving the jsoup object hanging as unrecoverable garbage in the vm

2021-06-16T14:19:48.434100Z

yet another example of "never mix laziness and state"

borkdude 2021-06-16T14:20:01.434300Z

jsoup bindings? just use jsoup directly

2021-06-16T14:20:28.434500Z

💯

2021-06-16T14:21:18.434700Z

(it was years ago but my tech lead at the time was highly averse to adding any java interop to our codebase)

km 2021-06-16T19:32:04.437500Z

Hey guys. Trying to use clojure.spec to write a simple string calculator, i.e. "2+2" => 4. It works, but it seems really verbose! I also don't like how I used regex-replace before conforming to spec (I'd rather use 100% spec). Any ideas how I could clean it up?

(ns demo.calculator
  (:require
   [clojure.spec.alpha :as s]
   [clojure.string :as string]))

(s/def ::digit (set "0123456789.n"))
(s/def ::operator (s/cat :kw #{:operator} :val char?))
(s/def ::expression (s/* (s/alt :number number?
                                :operator #{\/ \* \+ \-}
                                :parenthetical ::parenthetical)))
(s/def ::parenthetical 
       (s/cat :open #{\(}
              :body (s/* (s/alt :number number?
                                :operator #{\/ \* \+ \-}
                                :parenthetical ::parenthetical))
              :close #{\)}))

(def order-of-operations
  [[:operator \*]
   [:operator \/]
   [:operator \+]
   [:operator \-]])

(defn some-index [coll ops]
      (if (empty? ops) nil
          (let [i (.indexOf coll (first ops))]
            (if (= i -1)
              (some-index coll (rest ops))
              i))))

(defn evaluate [expression]
  (let [index (some-index expression order-of-operations)
        or-paren #(let [e (Exception. (str "invalid arithmetic at " %))]
                    (case (first %)
                    :parenthetical (-> % second :body evaluate)
                    :number (second %)
                    (throw e)
                    ))]
    (if-not index (or-paren (first expression))
            (let [[left-hand operator right-hand :as operation]
                  (take 3 (drop (dec index) expression))
                  before (take (dec index) expression)
                  after (drop (+ 2 index) expression)
                  result [:number ((resolve (symbol (str (second operator))))
                                   (or-paren left-hand)
                                   (or-paren right-hand))]]
              (evaluate
               (concat before
                       [result]
                       after))))))

(defn parse-math [arithmetic]
  (->>
   (string/replace
    (string/replace arithmetic #" " "")  #"(^|[^0-9])-([0-9]+)" "$1n$2")
   seq       
   (partition-by #(s/valid? ::digit %))
   (map #(if (s/valid? ::digit (last %))
           (Integer/parseInt (string/replace (apply str %) #"n" "-"))
           %))
   flatten
   (s/conform ::expression)
   evaluate))

(= 1/2 (parse-math "1/2"))
(= 42 (parse-math "(((((((((((42)))))))))))"))
(try (parse-math "2+*2")
     (catch Exception e true))
(= -4 (parse-math "-1 * (2 * 6 / 3)"))

zendevil 2021-06-16T19:39:16.438200Z

What is the time and space complexity of the core count function?

ghadi 2021-06-16T19:39:58.438900Z

On a vector or map, O(1)

ghadi 2021-06-16T19:40:17.439500Z

On a lazy seq, you have to realize the seq to count it

zendevil 2021-06-16T19:45:07.440100Z

why the hell does the racket implentation of lisp have a O(n) time for the built-in length function?

isak 2021-06-16T19:45:31.440600Z

don't they use linked lists?

phronmophobic 2021-06-16T19:46:36.441200Z

clojure has counted? to check if a coll implements count in constant time

zendevil 2021-06-16T19:49:32.441800Z

How are vectors and maps implemented differently in Clojure to give O(1) time for count as opposed to O(n) time for length in racket? And is the space complexity for count O(1) too?

Elliot Stern 2021-06-17T14:33:11.491100Z

count is O(1) in Racket on vectors and can be O(1) or O(n) on hash tables. The big difference is that cons lists and assoc lists (cons lists of key value pairs) are much more idiomatic in Racket, and the vector implementation is much less generally useful than in Clojure.

Elliot Stern 2021-06-17T14:39:15.493700Z

Racket doesn’t provide a persistent vector with a constant time vector-set! function; you can only update mutable vectors. Clojure gets around that by using a comparatively recent data structure for maps and vectors, the Hash Array Mapped Trie which allows amortized constant updates and access.

2021-06-18T02:28:23.048700Z

I don't think anything about Hash Array Mapped Trie is needed for the O(1) count though, Clojure just literally counts elements as calls to assoc are made.

zendevil 2021-06-18T06:03:44.057Z

@didibus can you point me to the loc’s where this mutation on count’s value happens?

2021-06-18T06:18:03.061Z

Ya, if you look here: https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/PersistentHashMap.java#L139 For maps, each assoc implementation returns a new map with the count parameter set to count + 1 and here for vector on cons is the same: https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/PersistentVector.java#L228

phronmophobic 2021-06-16T19:59:24.442600Z

There's lots to be written about how to implement data structures with different time/space complexities for various operations. I think why may be more interesting than how. From https://download.clojure.org/papers/clojure-hopl-iv-final.pdf > 3.4 Clojure’s Data Structures > > While linked lists are available and can be implemented in most programming languages, and have a certain elegance and functional heritage, their use is dominated in professional practice by the use of vectors/arrays and hashtables/associative maps. Of course, lists are sequential like arrays and one can access the nth element, and e.g., Common Lisp has a notion of association lists and a function ‘assoc’ for creating mappings and thus treating a list of pairs like a map. But it highlights an important aspect of programming and difference from mathematics in that merely getting the right answer is not good enough. Not all isomorphisms are viable alternatives; the leverage provided by indexing support matters. I felt Clojure would be a non-starter for practitioners without credible substitutes for O(1) arrays and maps.

👍 1
phronmophobic 2021-06-16T20:00:57.442800Z

Answering how clojure implements its data structures is a little complicated since maps and vectors are primarily defined by their interface rather than their implementation. In fact, the implementation can "change" as these collections shrink and grow even though the interface stays the same.

👍 2
quoll 2021-06-16T20:04:20.443300Z

user=> (type (zipmap (range 8) (range 8)))
clojure.lang.PersistentArrayMap
user=> (type (zipmap (range 9) (range 9)))
clojure.lang.PersistentHashMap

👆 2
2021-06-16T20:09:09.444100Z

The simple "how" is just that Clojure map and vector keep track of the count as elements are added/removed, so when you ask for the count, it's already been computed, you don't have to go through and count things.

dpsutton 2021-06-16T22:27:02.446400Z

is there a way to remove a var from the current namespace? I did a (apply require clojure.main/repl-requires) not realizing there is a function in this ns called source. Now re-evaluating the file errors saying that source already refers to: #'clojure.repl/source in namespace

Dane Filipczak 2021-06-16T22:28:18.446500Z

is it not (ns-unmap ns ’do-something)

dpsutton 2021-06-16T22:32:07.446700Z

that's exactly it

dpsutton 2021-06-16T22:32:24.446900Z

i had forgotten it was unmap. i was looking at apropos results for remove and var

dpsutton 2021-06-16T22:32:26.447100Z

thanks dane!

seancorfield 2021-06-16T22:33:15.447300Z

The code my “clean ns” hot key is bound to: https://github.com/seancorfield/vscode-clover-setup/blob/develop/config.cljs#L68-L78

seancorfield 2021-06-16T22:33:41.447600Z

Removes aliases, interned names (public + private), refers except core.

Dane Filipczak 2021-06-16T22:36:16.447800Z

@seancorfield so cool, borrowing this : )

dpsutton 2021-06-16T22:39:23.448100Z

neat. thanks sean

seancorfield 2021-06-16T22:40:07.448300Z

It’s nice because it cleans out a namespace without actually destroying the ns itself, so loading the file “does the right thing” and any other nses that hold references to this ns — via :require — don’t get broken. I don’t have to use it very much, but it’s “just enough cleanup” to avoid any of those reload/refresh things…

dpsutton 2021-06-16T22:49:43.448500Z

oh i'm jealous reading this config in cljs instead of elisp 🙂

coby 2021-06-16T22:58:23.449100Z

I need to serialize HTTP (Ring) requests for a debugging tool I'm writing in ClojureScript. Each request is enriched with extra data, such as plugins (functions) that have been loaded, and random stuff like Reitit Match records. These are things that the ClojureScript environment isn't going to know about so I need a generic, legible way to represent them to the user. What's a good strategy for doing this? I found this article about https://tech.redplanetlabs.com/2020/01/06/serializing-and-deserializing-clojure-fns-with-nippy/, but I think that's overkill for what I need (and Nippy doesn't work in CLJS).

seancorfield 2021-06-16T23:00:26.449300Z

VS Code/Clover or Atom/Chlorine — exact same config (and hot keys) work on both.

phronmophobic 2021-06-16T23:14:15.451100Z

If nippy is overkill, then maybe serializing your data as edn is appropriate?

phronmophobic 2021-06-16T23:16:14.452900Z

If you don't need to store data and are just transmitting data between applications, then transit might be a good fit, https://github.com/cognitect/transit-clj

coby 2021-06-16T23:19:21.455100Z

> maybe serializing your data as edn is appropriate? That's the idea, but that results in fns that the EDN reader doesn't know how to deserialize client-side. Here's the current process, for context: • Enrich each request on the server as it comes in • Call (prn-str req) and send it over a websocket to the debugger • On the debugger client side, do (edn/read-string req) I think the issue here is with prn-str being too simplistic

phronmophobic 2021-06-16T23:21:06.456Z

If you're serializing to transmit between applications and aren't storing the data, then check out transit

phronmophobic 2021-06-16T23:22:07.456500Z

I'm not aware of any serializer that can generically serialize functions. You'll probably have to preprocess the data or ignore that part of the data.

2021-06-16T23:23:15.456800Z

I need to make an appeal to the collective memory here: somewhere on youtube, there is a video of a presentation about (as far as i remember and it's killing me) building a DSL out of ASTs constructed from a sample data structure about planets(?). My best guess for a time frame is three or four years ago. Does that ring any bells?

phronmophobic 2021-06-16T23:24:09.456900Z

There are also some caveats when using pr-str like *print-length*. Let me see if I can remember a good reference that covers them.

coby 2021-06-16T23:25:16.457100Z

Yeah, I guess what I'm asking about is the pre-processing part. I need to transform functions and other "unkown" entities like third-party records into simple symbols or strings, something that won't result in tagged values like #object that EDN/CLJS would need to load extensions for.

phronmophobic 2021-06-16T23:27:44.457300Z

This reference seems pretty good: https://github.com/jafingerhut/jafingerhut.github.com/blob/master/clojure-info/using-edn-safely.md

1
coby 2021-06-16T23:30:43.457700Z

Transit is great, I will probably switch to it eventually to avoid the *print-length* trap etc. But AFAICT it'll still come up against the limitation of not knowing how to deserialize random library object client-side. It's okay to lose some fidelity in those cases and just render strings instead...so maybe what I'm looking at is extending Datafiable for fns etc. via metadata? Just a little hazy on how that would work. 🙂

phronmophobic 2021-06-16T23:32:55.457900Z

another option is to use clojure.walk/prewalk or similar and remove any types you don't recognize

1
coby 2021-06-16T23:39:32.458200Z

Right, makes sense. Talking through this has been helpful, thanks!

dpsutton 2021-06-16T23:40:38.459Z

That’s Tim bald ridge making a logic engine at the Denver clojure meetup

dpsutton 2021-06-16T23:41:08.459900Z

On GitHub he’s halgari. Look in his repos for “logic” and you’ll find it

phronmophobic 2021-06-16T23:41:13.460Z

Unfortunately, I can't find anything recent that does a good job of explaining the "right" way to write edn.

Donald Pittard 2021-06-16T23:46:19.461400Z

Has anyone played around with gitpod and a clojure environment?

2021-06-16T23:46:23.461500Z

Aha. It was searchproof because it's about zippers!

coby 2021-06-16T23:46:53.461600Z

I'm not too worried about it as I'll likely switch to Transit and translate anything unrecognized into stuff it can handle out of the box.

👍 1
2021-06-16T23:54:27.461900Z

Thanks. This has been tormenting me for weeks.