Fork me on GitHub
#clojure
<
2023-11-29
>
mikerod04:11:03

I found an interesting case of a “closure over field names” in deftype not being interpreted correctly when placed inside of locking and using set!. I’ll put the full example in a thread. The high-level part is just this though:

;;; Compiler error:
;;;   Cannot assign to non-mutable: x
(deftype ChangerWithLock [^:volatile-mutable x]
  ChangingThing
  (getVal [this]
    (locking change-lock
      (set! x (inc x)))
    x))
x isn’t recognized as a mutable field when the locking is there - it is fine otherwise. Need to use something like (set! (.-x this) ...) instead.

mikerod04:11:13

Here it is:

(definterface ChangingThing
  (getVal []))

(deftype ChangerNoLock [^:volatile-mutable x]
  ChangingThing
  (getVal [this]
    (set! x (inc x))
    x))

(let [^ChangingThing changer (->ChangerNoLock 1)]
  [(.getVal changer)
   (.getVal changer)
   (.getVal changer)])
;;= [2 3 4]

(def change-lock (Object.))

;;; Compiler error:
;;;   Cannot assign to non-mutable: x
(deftype ChangerWithLock [^:volatile-mutable x]
  ChangingThing
  (getVal [this]
    (locking change-lock
      (set! x (inc x)))
    x))

;;; Do this instead
(deftype ChangerWithLock [^:volatile-mutable x]
  ChangingThing
  (getVal [this]
    (locking change-lock
      (set! (.-x this) (inc (.-x this))))
    (.-x this)))

(let [^ChangingThing changer (->ChangerWithLock 1)]
  [(.getVal changer)
   (.getVal changer)
   (.getVal changer)])
;;= [2 3 4]

mikerod04:11:11

I looked a bit at the bytecode, but didn’t dig enough to really understand it fully. I’m wondering if it tends to just not be a good idea to access mutable fields as a “closure” within deftype and instead do the field access interop everywhere.

oyakushev08:11:36

Looks like the compiler can't figure out that x is a deftype field when the setter is within a try-catch block. But, weirdly enough, sometimes it can.

;; This works
(deftype ChangerWithLock [^:volatile-mutable x]
  Object
  (toString [this]
    (try (set! x (inc x))
         (str x)
         (catch Exception _))))

;; This doesn't
(deftype ChangerWithLock [^:volatile-mutable x]
  Object
  (toString [this]
    (try (set! x (inc x))
         (catch Exception _))
    (str x)))

😮 1
Alex Miller (Clojure team)13:11:06

there are some tickets in the 1.12 queue for this, it’s tricky

👍 1
Vlad07:11:36

Is it the expected behavior?

zimablue15:11:44

would clojure be better if macros could splice themselves? Like reader conditional unquote splicing but happening at eval time like if @ could be called in the middle of a normal form, like suggested here: https://stackoverflow.com/questions/44052107/clojure-unquote-slicing-outside-of-syntax-quote happening at macro-expansion time or evaluation-time, not sure is there some invariant that this violates which is useful? can implement it for a random scope something like (reduce [acc el] (concat acc ((if (splice? el) identity list) el)) form)

valerauko15:11:56

when writing macros and feel the need to do something like that i usually use a loop one () outer and build the list normally

zimablue15:11:36

But if it was in the language you could do it in arbitrary scopes without needing to be in macro scope

zimablue15:11:10

I think I asked the question wrong as thinking about it I don't really mean macros, they're just an example of something that would usefully splice itself

Ed16:11:48

what do we mean by "better"?

1
Noah Bogart16:11:17

there are languages that can do this (`unpack` in lua, for example) but i don't think it's necessary in clojure? if you have a list and you want to splice something, you can use syntax-quote to make it happen: '(1 2 3 @(4 5 6)) is equivalent to (1 2 3 ~@(4 5 6))`

zimablue16:11:11

But syntax quote pushes you into reader conditional time, the most restrictive possibility

zimablue16:11:31

Doesn't let you eg write a macro that can be called inline in a let binding vector to add bindings

Noah Bogart16:11:43

like creating dynamic bindings? that seems hard to parse and follow

zimablue16:11:44

How does it feel in lua? Useful or a problem somehow?

zimablue16:11:27

Just one example, there's a few places I could use it I think

Noah Bogart16:11:43

i think python has this with * (`*args`) and javascript has this with ... (`[...obj]`)

Noah Bogart16:11:59

give that we have macros, i don't see the need, but i know i've wanted them in the past

Noah Bogart16:11:36

oh, this is just a version of "multiple return values", which we accomplish with destructuring

zimablue16:11:57

Basically I'd say the main need/use is to be able to compose your macro (from the inside of?) Another macro, eg. The first example is let

zimablue16:11:33

I think it's always doable (kind of) by rewriting the outer macro, or moving your macro /outside/ the outer one, but then you need to rewrite some existing macro, or refer to specific positions inside the (not-your-macro) from outside. For let you can just wrap another let /outside/ not inside for the example I gave

Noah Bogart16:11:19

yeah, i get it, and i can seem some arguments for it

Ed16:11:40

So what's the change you're suggesting? Does it make it more complicated to write a macro that doesn't need this feature? How much more complex does it make the implementation of macroexpand? Regardless of bugs does it add to the cognitive weight of writing a macro? Clojure has a strong emphisis on composing simple elements together, and making something as fundamental macroexpansion more complex might just not be worth the trade-off? I guess it depends on "better" at what?

zimablue16:11:37

I think it does nothing semantically, unless there's something that yields an unsplice element, and that's handled by the compiler not you

Noah Bogart16:11:41

i think the suggestion is that at read time (not even macro time), if a bare ~@ is encountered, it would be evaluated and inserted into the surrounding form

zimablue16:11:58

If someone returns one where it's not expected I guess you could get an interesting explosion

Noah Bogart16:11:04

so (let [a 1 b 2 ~@[c 3]] (prn c)) would work

Ed16:11:42

~@ means nothing to the reader

zimablue16:11:02

Not read time, because read time wouldn't let you write macros that yield something that wants to be unsplice I think

Ed16:11:06

it's a syntax quote thing only .. right?

Noah Bogart16:11:17

lol that's why this is a suggestion

Noah Bogart16:11:23

you're right that it currently doesn't work like that

zimablue16:11:43

Syntax quote is read time, so it does mean something to the reader

zimablue16:11:51

But only inside stx quote

Ed17:11:00

yeah ... so the reader has to be able to return more than one result? ... or we need a new datatype that means "splice these things in"?

zimablue17:11:44

The second one I'd guess

zimablue15:11:34

not even macros I guess, els

mmer16:11:12

Is there anything more upto date than Swagger1st that creates a server from an OpenAPI specification.

immo05:11:21

I haven't tried it myself but there's https://github.com/juxt/apex. Seems to be pre-alpha and haven't been touched in 3 years so I'm not sure if it's any more up to date.