Fork me on GitHub
#clojure
<
2023-06-05
>
dekel08:06:49

I’ve run into an unexpected issue when using deftype. I’m using a java library with various functional interfaces (:face_with_rolling_eyes:), and I’m trying to make using clojure function with them as seemless as possible, so I’ve done this:

(deftype CustomFn [f]
  Handler
  (handle [_ event]
    (f event))
  Function
  (apply [_ args]
    (f args)))
Unfortunately, it seems that when I call (.doThingWithHandler x (->CustomFn my-fn)), I still need to type hint the result of ->CustomFn to avoid reflection (using ^Handler, ^CustomFn doesn’t help). Is there something I can do to ensure that CustomFn is treated as both Function and Handler without polluting the code based with type hints everywhere I use it?

igrishaev08:06:35

I'm not sure you need your own custom type. Usually you pass an object made with reify .

igrishaev08:06:50

(.doThingWithHandler x (reify SomeInterface
                         (do-this [_ arg1 arg2]
                           (call-my-func arg1 arg2))))

dekel08:06:33

Yeah, I could

(defn ->custom-fn [f]
  (reify
    Handler
    (handle [_ event]
      (f event))
    Function
    (apply [_ args]
      (f args))))
But then there’s no way to type hint this as both Handler and Function , which would be convenient. Maybe I’m asking for a lot and I should just be reifying adding type hints on the invocation itself, I was just hoping to avoid that.

igrishaev08:06:22

Hm, passing a result of reify is usually enough to mute reflection warnings. Are you sure you implement the right interface? Does this code work?

dekel08:06:22

Yeah, it’s because it’s in a function, if I had reify inline it would work, but at that point I’d rather just type hint.

igrishaev08:06:23

you can use this then:

(defn ->custom-fn ^SomeInterface [f] ...)
so every call of this function is aware of the result type

dekel08:06:15

This would work well if I had just one interface, but I need to type hint the result as both Function and Handler , and I was hoping to avoid treating these differently. Oh well, I guess Java interop code is never all that clean, I guess I’ll either compromise on in-line type hinting or on treating the two interfaces differently

igrishaev08:06:28

Why not having two wrappers? 1) ->handler and 2) ->function?

dekel08:06:13

That’s an option, I was just hoping to avoid that as the two functional interfaces both behave the same (1 arg, non-void return value). I thought having a value of type CustomFn (that implements these interfaces) would allow clojure to read it as both of these types and for me to use them indiscriminately, but given that’s not the case I will either have two different type-hinted reifying functions, or type hint in line.

emccue17:06:32

@U02634NQ5MK Make a protocol/interface that extends all the interfaces

emccue17:06:18

not sure why CustomFn isn't enough of a hint tbh

dekel06:06:50

(gen-interface
  :name mypkg.CustomFn
  :extends [io.vertx.core.Handler java.util.function.Function])

(defn- ^mypkg.CustomFn ->vertx-fn [f]
  (proxy [mypkg.CustomFn] []
    (handle [event]
      (f event))
    (apply [arg]
      (f arg))))
This ended up solving the problem. Using gen-interface here feels a bit over the top but I guess it gets the job done

djtango10:06:34

hi folks 👋 I am trying to remember there was a library which I think was inspired by re-frame but was for the backend to plumb IO, does that sound familiar to anyone? My google-fu is letting me down...

djtango10:06:10

yes this is very similar to what I was thinking of but the project is much younger than I was thinking of, as I definitely came across this at least more than a year ago IIRC

djtango10:06:07

(thanks btw)

kwladyka12:06:12

not as popular (because new) but good

kwladyka12:06:35

“better” than re-frame because base on new solutions while re-frame was created much earlier when things didn’t exist

djtango16:06:21

thanks for this, not really looking for a front end library, more the paradigm of using effects to model IO

👍 2
djtango08:06:20

aha this was the library I was thinking of fwiw

pesterhazy12:06:04

I was surprised by this:

user=> (nth [] 0) ;; makes sense
Execution error (IndexOutOfBoundsException) at user/eval1 (REPL:1).
null
user=> (nth () 0) ;; makes sense
Execution error (IndexOutOfBoundsException) at user/eval3 (REPL:1).
null
user=> (nth nil 0) ;; surprise
nil
Is this expected?

p-himik12:06:37

I'd say yes given that the impl has an explicit check for nil.

pesterhazy12:06:45

It does have counter-intuitive consequences:

;; A
(nth *command-line-args* 0) ;; throws

;; B
(defn main [& args] (nth args 0))
(apply main *command-line-args*) ;; doesn't throw

pesterhazy12:06:08

Normally I'd consider A->B a safe refactoring

pesterhazy12:06:16

(In fact that's what tripped me up just now)

pesterhazy12:06:08

Thanks for finding the Jira @U2FRKM4TW. I agree that changing this behavior is undesirable for backward-compatibility reasons

p-himik12:06:51

But it is safe, no? Assuming you don't want an exception.

pesterhazy12:06:37

I was relying on the exception being thrown (based on nth's docstring)

👍 2
p-himik12:06:08

Ah. I myself have dropped EAFP along with dropping Python in favor of LBYL or functions that just don't throw. :) In this case, I'd use first or destructuring. I use nth pretty much only on vectors and maybe strings.

pesterhazy12:06:40

Right, I'm using (or (first args) (throw (Exception. "...."))) now as a remedy

pesterhazy12:06:25

I'll see if I can propose a docstring patch at some point

👍 2
pesterhazy12:06:55

I didn't know about EAFP vs LBYL - will read up about that

p-himik12:06:51

tl;dr: don't rely on something throwing an exception when you can check or, better yet, rely on nil punning.

jpmonettas12:06:14

what is a safe way of checking if something is a map and contains a key? Doing (and (map? m) (contains? m :k)) will throw for example for m being a sorted-map of symbol keys because the comparator can't compare keywords with symbols

(let [m (sorted-map 'a 10)]
        (and (map? m)
             (contains? m :k)))

java.lang.ClassCastException: class clojure.lang.Symbol cannot be cast to class clojure.lang.Keyword

mkvlr12:06:05

I have this code for that

(defn get-safe
  ([key] #(get-safe % key))
  ([map key]
   (when (map? map)
     (try (get map key) ;; can throw for e.g. sorted-map
          (catch #?(:clj Exception :cljs js/Error) _e nil)))))

jpmonettas12:06:46

yeah I was about to do the same, but seems hacky

jpmonettas12:06:07

it is also one of those bugs that is hard to figure out in dev when you are writing generic code

skylize14:06:26

I think this should be safe for anything map-like? (including seqs-of-pairs) It's still a bit hacky, but concise with no need to catch exceptions.

(defn has-key? [m k]
  (->> m keys (filter #{k}) count pos?))

(let [m (sorted-map 'a 10)]
  [(has-key? m :k) (has-key? m 'a)])
; =>
[false true]

jpmonettas14:06:28

but that is going to be pretty slow for big maps

Yuner Bekir13:06:48

Hello guys! I am trying to write a testing framework which will be used to run integration tests. The issue I am encountering is that I can't control the output of System/CurrentTimeMillis I tried to redef it but it didn't work. Can you suggest a solution?

p-himik13:06:34

Could you refactor lines that use that function to use a wrapper that you can redefine?

Yuner Bekir13:06:39

@U2FRKM4TW I will have to rewrite pretty much of the whole project and in the future whenever someone will use the function he will always have to use the same wrapper function name

lread13:06:15

Another technique is to use JDK8+ https://docs.oracle.com/javase/8/docs/api/java/time/Clock.html. But in any case, you need a mechanism to optionally insert yourself into a function that fetches time.

igrishaev13:06:41

Briefly, there is no a way to just redefine a System/CurrentTimeMillis call. Either wrap it into a function, or implement any kind of inversion where you pass an object responsible for getting time. Then pass a fake object in tests.

Yuner Bekir14:06:41

Thanks for your replies

vemv15:06:21

I must be having a dense day morning

(async/go
  (loop []
    (while (not @closed?)
      (let [v (async/<! x)]
        ( v channel)
        (recur)))))
it complains about tail position. What would the correct rewrite?

p-himik15:06:28

while is sugar for loop+`recur`, so you end up having two recurs in the inner loop.

🙌 2
p-himik15:06:47

You probably don't need the outer loop at all and that recur as well.

vemv15:06:03

Oh, sure, I meant when not while picard-facepalm

2
Joshua Suskalo15:06:05

I suspect you meant to make that while a when , but while is probably more semantically correct and you should drop recur and loop

🙌 2
vemv16:06:55

The original form was go-loop, I unbundled it trying to debug the issue, which is why it looks extra bad

Darrick Wiebe16:06:32

It can be useful to macroexpand inner forms to see what is going on with them when you have problems. If you try to macroexpand something that's not a macro, it just returns unmodified, so it's safe and easy to try anywhere. Typically there's an editor shortcut to do it, but here's the repl way:

(macroexpand
    '(while (not @closed?)
       (let [v (async/<! x)]
         ( v channel)
         (recur))))
becomes
(loop*
  []
  (clojure.core/when (not @closed?)
    (let [v (async/<! x)] ( v channel) (recur))
    (recur)))

clojure 4
Ben Sless17:06:08

BTW, why do you need the closed atom? You can close the channel and taking from it returns nil (you should check it, btw)

vemv17:06:36

Sure thing! Thanks.

(async/go-loop []
  (when-let [v (async/<! x)]
    ( v channel)
    (recur)))

;; and (async/close! x) elsewhere

Joshua Suskalo17:06:12

This is exactly the circumstance that I want while-let for

Ben Sless17:06:05

Should be when-some for completeness, it's legal to send false on a channel

Ben Sless17:06:14

And you want to handle an exception in case send fails because you might want to stop recurring or recover

Joshua Suskalo17:06:33

(defmacro while-some
  [test & body]
  `(loop []
     (when-some ~test
       ~@body
       (recur))))

(a/go
  (while-some [v (a/<! x)]
    ( v channel)))

Joshua Suskalo17:06:36

This makes it pretty nice

Ben Sless17:06:56

Additionally, if you want to cascade shutdown, you can close the websocket channel after you finish the loop

Ben Sless17:06:52

(a/go
  (while-some [v (a/<! x)]
    ( v channel))
  ( channel)) ;; guessing the API looks like that

vemv18:06:42

It looks different (if you're curious) https://kit-clj.github.io/docs/websockets.html - the closing goes on :on-close-message I found it an elegant extension to Ring :) The same handler can respond to vanilla and websocket requests alike (which I had to achieve, for reasons):

(if x
  {:status 200 :body "hello vanilla"}
  {:undertow/websocket {:on-open f
                        :on-message g
                        :on-close-message h}})

stopa18:06:03

Also note, undertow’s send is generally non-blocking. If your intent is for the loop to recur when the send is done, you may need to wrap it.

vemv18:06:45

That would be interesting. Are you talking about https://github.com/luminus-framework/ring-undertow-adapter/blob/3b27314d3eb040c17a0e8dae081adead4feb844f/src/ring/adapter/undertow/websocket.clj#L88-L97 send? If it indeed is async, it would be a great doc addition

stopa18:06:00

Yes! That one. I wrapped it like so: (defn go-send "Non-blocking send. Returns a channel that will close when the message sends. Propagates errors." [msg ws-conn] (let [ch (promise-chan)] (ws/send msg ws-conn (proxy [WebSocketCallback] [] (complete [ws-conn context] (a/close! ch)) (onError [ws-conn context throwable] (a/put! ch throwable) (a/close! ch)))) ch))

vemv18:06:51

Nice. I will create an issue there if you wouldn't want to do it yourself. At the very least it seems wise to document the behavior, else bugs can happen (in my snippet, I could imagine out-of-order delivery)

vemv18:06:38

@U0C5DE6RK: another approach being to use the *blocking methods, opt-in:

- WebSockets/sendBinary
+ WebSockets/sendBinaryBlocking
would make a nice PR, I could do that

stopa20:06:39

One thing to be careful about with the blocking methods, is that it could block core async’s underlying thread pool. (This gentleman has a good explanation https://martintrojer.github.io/clojure/2013/07/07/coreasync-and-blocking-io). I am not sure how big of a problem this would be though. For the library a blocking option could be a good PR! :right-facing_fist: :left-facing_fist:

2
2
vemv06:06:46

Yeah I was aware, I was using (<! (thread (blocking-thing))) (at least for the time being... I guess it performs OK)

❤️ 2
Joshua Suskalo17:06:28

loom will help with this a lot because you can just make everything its own thread and don't have to worry about starvation (with the caveats about running native code and currently on monitors as well)