Fork me on GitHub
#clojure
<
2023-12-18
>
slipset07:12:04

Looking at some multi threaded code which might show some race condition, I’m wondering (to eliminate some possibilities) is binding thread safe, ie if I have two threads which do (binding [name (.getName thread)] …) do I risk a race condition or will this work as expected, ie each thread only having name contain the value of thread it’s running in?

p-himik07:12:40

binding is thread-safe, yes.

1
p-himik07:12:46

(Assuming the things on the right hand side of the bindings are thread-safe).

joshcho11:12:33

what are some recommendations for doing browser automation in clojure?

vemv13:12:47

Depends on the use case, if it happens to be more on the scraping side of things, playwright is most suitable (available on pure java via https://github.com/pfeodrippe/wally, or use a nbb/squint type of thing)

jjttjj15:12:38

Lately I've preferred just using https://chromedevtools.github.io/devtools-protocol directly. you start chrome with a debug flag and then you get an http/ws api to do whatever you want in the browser. I think I always hit some limitation or another when trying to use webdriver for non-testing automation purposes. Though if it meets your needs etaoin is a great wrapper for it

💡 2
vemv16:12:13

Playwright is essentially that, it uses CDP

seancorfield16:12:32

We use Playwright at work.

phill16:12:13

Consider also whether you might like to subscribe to a browser test automation service. You can then choose a tool that supports both your local browser and the service you have in mind.

1
joshcho10:12:02

I see. So advantage of playwright is that it’s more in-browser?

vemv10:12:28

I'd say it's webdriver vs CDP, different protocols with different capabilities (CDP is more featureful but Chrome-specific)

Ludger Solbach14:12:52

do we have a comment (;) reading/preserving clj/edn reader? I have some big edn files with comments all over the place which I'd like to rewrite automatically? Does clj-rewrite support this?

mpenet14:12:00

also https://github.com/borkdude/rewrite-edn sugars a few things on top

❤️ 3
borkdude14:12:02

edamame doesn't preserve comments though

Ludger Solbach14:12:57

ok, then I will look into rewrite-edn first. 😀

borkdude14:12:32

I think that is about comment , not ;;

borkdude14:12:06

some comment forms had a ridiculous amount of children forms which caused a problem, apparently

seancorfield16:12:09

(also, please use 🧵 for replies in future)

luke21:12:38

Question that’s been bothering me today. Is there any way to get a tagged literal to recursively eval the same as regular Clojure collection does? For example, I’ve got a custom record for RDF triples, with an associated tagged literal.

(def foo "")

;; when evaluating a vector, symbol `foo` is evaluated & resolved
(eval (read-string "[foo]")) ; => [""]

;; when evaluating a triple record, symbol `foo` is captured as a literal symbol.
(eval (read-string "#rdf/triple [1 foo 2]")) ; => #rdf/triple [1 foo 2]
I’ve read the source code and it appears that the answer is no, since the conditional for a IType/IRecord is baked into the analyzer and returns a ConstantExpr, but just wanted to check…

Alex Miller (Clojure team)21:12:24

tagged literal reader functions are a read-time construct to create a value - they take read (but unevaluated) data and return an object (in your case an instance of record or type)

Alex Miller (Clojure team)21:12:05

records or types are like any other Java object and evaluate to themselves

luke21:12:45

Right.. but in this case they’re also (sorta) like a map or a vector, which eval recursively. Curious if any way to make them behave that way.

Alex Miller (Clojure team)21:12:47

so, in short - no this is not how it works (and you probably shouldn't actually want read-time eval)

luke21:12:13

well, it’s eval-time eval, not read time 🙂

Alex Miller (Clojure team)21:12:23

the tagged literal reader is read time

luke21:12:32

it reads just fine

luke21:12:41

just like a vector reads just fine (and reads the symbol)

hiredman21:12:50

but the way the compiler will compile it is as a constant without evaluating any internal fields

Alex Miller (Clojure team)21:12:09

functions are cool things that do eval :)

hiredman21:12:30

if you eval in the literal you can end up getting double evaled

Alex Miller (Clojure team)21:12:31

there even happens to be a positional record constructor that does exactly this :)

luke21:12:09

Yep, @U064X3EF3 that’s the fallback. I was just hoping for a construct so that writing literals embedded in code would look the same as when writing them in an EDN file. Would be a bit preferable IMO to have a way to do:

(let [foo ...]       
   #rdf/triple [1 foo 2])

luke21:12:14

but the record constructor fn will work

Alex Miller (Clojure team)21:12:01

tagged literals are about data and values. people constantly try to do trickier things with them, and mostly just get into trouble doing so

luke21:12:40

👍 fair enough, thanks for the rationale

Joshua Suskalo21:12:58

I kinda feel like this in particular is a justified attempt though since this is how you'd make e.g. a queue and I think it's perfectly reasonable to want a queue literal which can have elements be references to locals.

Alex Miller (Clojure team)21:12:27

that's all fine, but why do you need to use a read literal for it?

hiredman21:12:49

this is why, for example, if you use a clojure set literal with (+ 1 2) in it, that will evaluate, but if you use an ordered set literal you get the unevaled list

Joshua Suskalo21:12:17

Mostly just ergonomics, it's not really absolutely necessary.

luke21:12:17

It’s a way in which Clojure data structures (lists/maps/vectors) are privileged syntax… which is fine, that part of the language just isn’t extensible in this respect

1
luke21:12:28

I can argue that it should be, but am fully aware that would take a well articulated problem statement and a full stack of spreadsheets, haha

Alex Miller (Clojure team)21:12:58

tagged literals are a part of edn to be exchangeable with other languages which may not have eval

Alex Miller (Clojure team)21:12:59

having eval in your data means you're back to programs, not data

Alex Miller (Clojure team)21:12:14

you shouldn't want that for edn :)

luke21:12:21

I would argue the actual issue is orthogonal to tagged literals… it happens even if you construct forms by hand out of symbols and then call “eval” on them. eval in the reader fn for a tagged literal is 100% evil I agree

hiredman21:12:32

clojure sets exist, and are part of edn

hiredman21:12:46

they also evaluate their members when passed through clojure's eval function

hiredman21:12:29

this is not "these types should automatically evaluate stuff", this is the behavior of these types when passed through clojure's eval

hiredman21:12:12

the thread I linked above ends with https://clojurians.slack.com/archives/C03S1KBA2/p1698768489212979?thread_ts=1698763672.356679&amp;cid=C03S1KBA2 which is a pretty simple protocol that if the clojure compiler used it would allow you to define new set literals that would have their members evaled if passed through eval

Joshua Suskalo21:12:19

Yeah this is the behavior that I would want, and I've talked about desiring this before e.g. when I was writing my ropes library

1
luke22:12:09

I see the distinction Alex is making, though. Clojure code is data, but not all data can be code. Right now, lists/maps/sets are special because they are data that can be eval’d as Clojure code, and that mechanism is not designed to be extensible in Clojure as it exists. Tagged literals are solving a different problem. You COULD design a “polymorphic eval” for arbitrary data structures but that would be a design change with significant ramifications.

hiredman22:12:34

when serializing objects to bytecode, which the compiler has to do to, if the object isn't something the compiler already has hard coded cases for (including IType and IRecord) the compiler calls printString

hiredman22:12:20

it is already the case that the compiler is attempting to be polymorphic eval

Alex Miller (Clojure team)22:12:11

(the pr thing is kind of a hack, which is maybe something we could remove with constant dynamic in Java 11)

Joshua Suskalo22:12:28

This is what I ran into when I was working with ropes, where it has support to be emitted into code, but it can't be evaluated recursively.

Alex Miller (Clojure team)22:12:07

functions are a fine way to make custom data structures, which can act as Clojure data by implementing the right interfaces. I think it's a non-goal (or even an anti-goal) to make new syntax with custom eval semantics.

hiredman22:12:36

no new syntax, it is the same evaluation semantics as core data structures, so I would argue no new semantics

hiredman22:12:54

for my strawman, if the type satisfied the protocol (interface would maybe be better in the compiler) when passed through eval, eval would eliminate it, and eval the result of that

hiredman22:12:30

and it wouldn't break any existing code, because it would introduce a new interface for this, and none of the existing code implements it so they keep the same non-evaling behavior

Alex Miller (Clojure team)22:12:34

the principle I've heard Rich espouse about is being able to read anyone else's program, which is why the read table is not open for extension. in this case, you would need the lib creating the data structure and the semantics its applying at read time to understand how it is to be evaluated. that seems like a step too far, but don't know.

hiredman22:12:49

you don't have to evaluate it though

hiredman22:12:17

it is already the case with, again, the built in clojure collection types where those get used in contexts where evaluation is expected and where it isn't

Alex Miller (Clojure team)22:12:43

I don't understand what you're proposing then

hiredman22:12:47

so with a clojure set #{(+ 1 2)} is read in as a set with a list in it, then when eval'ed, it generates a set #{3}

hiredman22:12:13

the goal is the same behavior for #ordered [(+ 1 2)]

Alex Miller (Clojure team)22:12:28

so your objective is to ... [say words]

hiredman22:12:29

when #ordered [(+ 1 2)] is read in, it would be some ordered set type containing the list (+ 1 2) and then after calling clojure.core/eval on it, if the ordered set type implements the right interface, you get an instance of the ordered set type containing 3

Joshua Suskalo22:12:23

honestly this could be implemented without a protocol as long as you can call seq and empty on it

Alex Miller (Clojure team)22:12:46

forget the mechanism for a moment, I'm still unclear precisely what the goal is

Joshua Suskalo22:12:52

(into (empty coll) (map eval) (seq coll))

Joshua Suskalo22:12:16

the goal is to allow source code to have literals which have the same semantics as core literals

hiredman22:12:34

same interior evaluation semantics

Alex Miller (Clojure team)22:12:33

to have custom literals that have the interior evaluation semantics of core collections

hiredman22:12:41

if (-> #{(+ 1 2)} eval first) is 3, then (-> #my/custom-set-type [(+ 1 2)] eval first) resulting in the list (+ 1 2) is kind of a drag

Alex Miller (Clojure team)22:12:53

there are lots of ways custom colls that implement the necessary interfaces/protocols can participate in Clojure with identical semantics, this is one place where they don't have the same level of access

👍 1
Noah Bogart01:12:21

This is the best description of the problem statement and issue I’ve seen yet. Might be worthwhile to capture in an ask/jira ticket