Fork me on GitHub
#clojure
<
2018-11-15
>
lwhorton01:11:48

how does one chain compose together filter fns? i’m having a brain fart and can’t figure out what’s wrong here:

filters (comp
                  (filter #(contains? % :person/foo))
                  (filter #(contains? % :person/bar))
                  )
        {keep true
         drop false} (group-by filters d)

lwhorton01:11:45

what i really want is a composed filter chain and to use group-by to keep the values filtered as well as the values not filtered

noisesmith01:11:57

so more like (group-by #(or (contains? % :person/foo) (contains? % :person/bar)) d)

👍 4
lwhorton01:11:03

though i think my intent is and not or, this is what i’m looking for

lwhorton01:11:40

yea that’s probably a more straightforward way. i thought i could get fancier, but i see that what i’m trying to do doesn’t really make sense and might need to be wrapped in a transducer

enforser01:11:38

I don't think group-by accepts a transducer like you're calling it. If you must use a transducer you use it in a way such that what is being passed to group-by is a function. For example, the following code uses a transducer to filter elements and place the remaining ones into a set. The set is then used as the function to determine if a value from the collection exists in the output of the transformation of the transducer.

(def coll
  [{:a 1 :b 2 :c 3}
   {:a 1 :b 2}
   {:c 1}
   {:b 3}
   {:a 1}])

(def xf (comp (filter #(contains? % :a))
              (filter #(contains? % :b))))

(group-by (partial contains? (into #{} xf coll)) coll)

noisesmith01:11:47

or #(boolean (seq (select-keys % [:person/foo :person/bar])))

lwhorton01:11:19

this is slick

noisesmith01:11:46

it does or rather than and though

dominicm07:11:59

Which ones have you seen? Why aren't they usable?

didibus08:11:21

Mutation testing, what's that?

dominicm08:11:55

Breaks your program and checks if the tests still pass. If they do, you need more tests.

didibus18:11:59

Hum, never heard of it before, cool.

paulspencerwilliams08:11:29

I'd like to filter a list returning all values until a particular value is found, and stop at that point. e.g. 1, 2, 3, 4, 5, 2 should return 1, 2, 3 if 3 is the target. Thus filter is no good. Do I need recursion or is there a more idiomatic way of achieving this?

paulspencerwilliams09:11:38

oh yeah @lukas.rychtecky, that's perfect! Thank you.

schmee09:11:18

take-while won’t do it unfortunately, since it doesn’t return the last item

schmee09:11:42

there’s a ticket for adding take-until, you can copy the implementation from the patch and use that: https://dev.clojure.org/jira/browse/CLJ-1451

paulspencerwilliams09:11:59

Yeah, I've just picked that fact up. I'm currently playing with split-with and joining the first sequence, and head of the second sequence (i.e. the target item).

paulspencerwilliams09:11:28

That patch function is much better however.

paulspencerwilliams09:11:08

That's perfect, thank you both! take-until was exactly what I wanted.

andrea.crotti10:11:40

I'm having a very mysterious (for me) behaviour, the same exact code works perfectly fine in development, but when I try to do a lein run I get something like

Exception in thread "main" clojure.lang.ArityException: Wrong number of args (0) passed to: middleware/wrap-exceptions/fn--42042, compiling:(/private/var/folders/c2/47k4xqq92gld005q_8df532nh8hy8l/T/form-init3592174147248454589.clj:1:125)

andrea.crotti10:11:05

this comes from the actual app-handler which is something like

(def app-handler
  (-> routes
      (make-handler spa-handler)
      (mw/wrap-exceptions ex/notify!)
....

andrea.crotti10:11:56

but it's not a problem with the actual functions at all, they all fail for the same reason one after the other in inverse order, all complaining that I'm passing 0 arguments, while it's always 1 or even 2

andrea.crotti10:11:26

I guess it's some kind of weird aot compilation issue maybe, anyone ever had this problem?

andrea.crotti11:11:19

mm never mind I found out, it was just because I was calling (app-handler) which now is a def, so the error was rather confusing but I guess it makes sense

todo11:11:24

Please don't ask me why (answer: I'm an idiot) I need something that is equiv to "uber grep" -- it needs to look through all files in ~/ , all .zip, all .tgz ... and it needs to find me blocks of code that are clojure code. There's no guarantee that the file ends in .clj or .cljs (basically, I have a 4000 line of clojure code stored as a COMMENT in either a .kt kotlin or .rs rust file ... and I think it's in some .zip or .tar or .tgz or .tar.gz -- and I need to find this block of code).

schmee11:11:11

write a script that counts the number of parenthesis in each file and then sort it by count

todo11:11:49

that's brilliant, 4k lines f clojure ==> at avg of 3 ()/line, that's 12000 () pairs in the file, something most files ar eunlikely to have

todo11:11:17

it should count ([{}]), then give extra poionts for defn, let, ...

schmee11:11:34

{} is common in rust and kotlin as well, so () will give you most bang for the buck

apa51212:11:54

(ns test.core
  (:gen-class))

(defn -main
  "I don't do a whole lot ... yet."
  [& args]
  (.addShutdownHook (java.lang.Runtime/getRuntime)
                    (Thread. (fn []
                               (println "Shutting down")
                               (shutdown-agents)
                               (prn 2))))
  (future (fn []
            (do (println 1)
                (Thread/sleep 1000)
                (println 3))))
  (System/exit 0))
can someone tell me why this code never prints 3? i tried running it every way i can think of. lein trampoline run, lein run, lein uberjar and running with java -jar, removing (System/exit 0) and quitting with Ctrl+C.

todo12:11:40

Can you paste the full output?

apa51212:11:08

erik@nixos ~/c/c/test> lein run
Shutting down
2

todo12:11:13

It should print "Shutting down", "2", and possibly "1" right ?

todo12:11:29

Why do you expect it to print "3" at all?

apa51212:11:03

i thought the point of (shutdown-agents) was to let futures finish

todo12:11:09

In my my understanding of the JVM, when (System/exit 0) is run, it executes all of the hooks, then kills all the threads.

todo12:11:02

https://clojuredocs.org/clojure.core/shutdown-agents hmm -- How is "future" related to agents ?

mpenet12:11:45

they run on the same threadpool

todo12:11:54

@marcus165: In your opinion, should the "3" also be printed, for the reasons @apa512 stated?

schmee12:11:54

@apa512 looking at the source for shutdown-agent, it calls shutdown on the thread pools

schmee12:11:16

in the docs for shutdown, it says: > This method does not wait for previously submitted tasks to complete execution. Use awaitTermination to do that. https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ExecutorService.html#shutdown--

apa51212:11:29

thanks @schmee. i must have misunderstood the purpose of (shutdown-agents).

schmee12:11:13

strange though cause the docstring for shutdown-agents says something else… https://clojuredocs.org/clojure.core/shutdown-agents

apa51212:11:05

i know. there are also a few code examples that strongly indicate it should wait.

schmee12:11:38

try removing the (fn [] ...) in the future call, future wraps up the body in a fn by itself

schmee12:11:44

that might be the problem

schmee12:11:04

you don’t need the do either

schmee12:11:37

your code create a future the creates a function and then exits, it never actually calls the function

apa51212:11:29

yes, i was trying a lot of different things. but still the same behavior if i call the future correctly.

gleisonsilva12:11:06

hello guys! by convention, when a function name ends with * what that means?

nha12:11:04

By convention, it is similar to prime in mathematics. I usually see it used as the implementation/helper. Ex.

(defn my-fn* [a b]
   …
   )

(defn my-fn [a b]
   …
   (my-fn* [a b]))

nha12:11:32

I’ve seen my-fn* or my-fn' used in that way. Naming convention really

nha12:11:02

it can be useful in macros too to avoid name-clashing. See https://clojure.github.io/test.check/clojure.test.check.properties.html for instance

gleisonsilva12:11:55

tks, @nha! I'll read more about it.. for now, i'm pretty sure that I should have give more thought to the math classes.. 'cause I couldn't understand what you mean by "... similar to prime in maths" 😕

nha12:11:02

well it’s not important at all - it’s just a naming convention

apa51212:11:43

(shutdown-agents)
                               (.awaitTermination clojure.lang.Agent/soloExecutor
                                                  10
                                                  java.util.concurrent.TimeUnit/SECONDS)
for anyone who's interested in the shutdown-agents problem, ^ seems to do the job

👍 4
roklenarcic13:11:39

ArityException Wrong number of args (-1) passed to: core/ptype  clojure.lang.Compiler.macroexpand1 (Compiler.java:6917)
Wut? 😮

roklenarcic13:11:18

how can number of args be -1 😅

bronsa13:11:07

it's a bug with arityexceptions in macros

roklenarcic13:11:19

oh right... 🙂

bronsa13:11:15

it's been fixed in 1.10 FWIW

roklenarcic13:11:54

yeah makes sense

roklenarcic14:11:07

I was off by one in a list

roklenarcic14:11:49

when writing project.clj for a library, should I mark clojure dependency as provided?

paulspencerwilliams15:11:50

So after some useful tips from @lukas.rychtecky and @schmee about take-until, I wondered how people typically handle non-merged patches to Clojure in their projects? Fork, merge, and reference from deps.edn? Include the patch in a patches ns?

Alex Miller (Clojure team)16:11:06

it’s a function - just put it in your code?

Alex Miller (Clojure team)16:11:22

you don’t need a patched version of clojure

Alex Miller (Clojure team)16:11:53

the cool thing about functions is anyone can make one

Alex Miller (Clojure team)16:11:20

if (and it is if) it gets added to Clojure later, your code will still work fine. you’ll get a warning that you’re shadowing core, which you can either ignore, or remove your version, or refer-clojure :exclude to keep using yours and not get the warning

paulspencerwilliams16:11:11

Cheers @alexmiller, that’s what I’ve done up until now. The possible future core addition was what I was concerned about.

Alex Miller (Clojure team)16:11:38

we’ve worked hard to make that a non-issue

paulspencerwilliams16:11:26

Thanks to those that have enabled this, and to your fast and authoritative advice 👍

roklenarcic16:11:58

@thheller Very few libraries mark clojure as provided though

mewa16:11:16

how would you share vars from project.clj with code that uses it?

seancorfield16:11:04

@marcin.k.chmiel I wouldn't -- I'd put the shared code in a src file and have project.clj pull that in, I think. I've also seen examples that use an EDN file or a text file for simple values that need to be pulled into project.clj as well as "regular" code.

mewa16:11:43

@seancorfield Thanks. The first approach is what I started doing

dpsutton17:11:09

there's a way to get that stuff into your project. slothcfg gives your project the project.clj map with all of the merging done. we deeply regret going down this route right now

dpsutton17:11:23

and it keeps you on lein 2.7.1 rather than lein 2.8.1

seancorfield17:11:48

Or move to clj/`deps.edn` and have a "pure" dependency config file with the "shared vars" in actual code 🙂

dpsutton17:11:23

^ we are envious of this in our main web app. our libs are migrating over to deps edn and we are loving it

seancorfield17:11:08

We just completed a full migration from Boot and we're very happy. Deleting our 2,000 line build.boot file was a nice milestone.

dpsutton17:11:22

wow. can't believe yall are off boot

deliciousowl17:11:04

migration to ?

mewa17:11:18

Interesting. I don't do that much clojure to be honest, just in between of my regular job. When I started learning clojure leiningen seemed to be (more or less) the standard.

mewa17:11:18

Would you recommend dropping leiningen entirely for new projects? (or boot FWIW)

seancorfield17:11:49

@marcin.k.chmiel Well, Leiningen is going to remain the de facto standard for a while because it's so well-established and all the books and nearly all the online tutorials use it. But, yes, I'd recommend at least trying the new CLI tools that the Clojure team have built.

seancorfield17:11:32

We use (a fork of) depstar for building uberjars for deployment. We use Cognitect's test-runner for running all our tests (including our expectations tests).

dpsutton17:11:15

lein has good stories around deployment and artifact creation. there's a plugin that reads deps from deps.edn files so our stuff has mostly had a thin project.clj file for deploying to private repos and creating jars

dpsutton17:11:27

otherwise we work with deps.edn during dev

dpsutton17:11:01

honestly the ideal to me sounds like boot tasks for artifact/deployment and deps.edn for dev

seancorfield17:11:09

@dpsutton I'm rather liking the depstar approach -- my fork supports both uberjars (for production app deployment) and "thin" JARs for Clojars/Central deployment. For now I'm using mvn deploy:deploy-file for pushing to Clojars, after creating a pom.xml with clj -Spom (and a one-time editing pass to expand the information there).

dpsutton17:11:32

yeah we looked at a bunch and didn't try your fork unfortunately

dpsutton17:11:55

but we also need deployment to private s3 repo and lein works so easily so it kinda won out

dpsutton17:11:42

didn't know about the mvn deploy stuff. i'm still a newcomer to actual java/jvm land. To some people clojure is a java program and to others its a language 🙂

seancorfield17:11:20

I'm not thrilled about having to use mvn for Clojars deployments 🙂 There's a recent deps-based project that does the "push" using the pomegranate library that I need to check out...

hlolli19:11:47

I'm experimenting agents, I have a while loop of an audio loop, which I start with this fn

let [thread (agent csnd)]
 #(send-off thread
  (fn [instance]
    (while (zero? (perform-ksmps instance)))))
which works fine at this point, my problem is signaling "stop", which I currently try to do this way
#(send-off thread (fn [instance] (stop instance)))
but nothing happens. Should I add some checking mechanism into the while loop? Not sure what's the best way. (I could use core-async thread or (Thread. ...) but I'd like to learn some agents).

jwhitlark19:11:07

@noisesmith You've said a couple of times about some libs not playing nice with jdk11. I'm working on fixing stuff like that, can you give me a list?

jwhitlark19:11:45

More generally, I wish we had some place to collect jdk11 type issues.

noisesmith19:11:53

I just know I've seen it in forums and bug tickets, but I don't have something handy, sorry - I'll make a note to document that in the future

noisesmith19:11:27

at one time our friend @tcrawley was collating these issues for 1.9

jwhitlark19:11:36

The xml.bind thing is a common one...

noisesmith19:11:27

I think a lot of the stuff on the above link is still relevant for 11

tcrawley19:11:56

@jwhitlark I was tracking java 9 issues at https://github.com/tobias/clojure-java-9/issues, but I haven't really kept up with it

Alex Miller (Clojure team)19:11:57

the xml.bind issue is different in 9 and 10+

tcrawley19:11:16

and some of those have been fixed in Clojure 1.10

noisesmith19:11:01

yeah - I trust the very competent Clojure team to fix these things, my main concern personally is regressions in miscellaneous libs

jwhitlark19:11:01

Yea. I'll take a look, and maybe do a java-11 version.

🍻 4
Alex Miller (Clojure team)19:11:29

in 9, it’s no longer in the base module and you can run into reflective access issues (those are actually a warning). in 10, they removed it entirely so you need to do something to add either the module or the lib

Alex Miller (Clojure team)19:11:27

the main thing we ran into with 11 is the new arity in Collection.toArray() which made existing calls/impls ambiguous

Alex Miller (Clojure team)19:11:08

those have been fixed in Clojure and in contribs that have the issue, but you might encounter it if you have your own reifications of Collection

Alex Miller (Clojure team)19:11:35

I don’t know of any other Java 11-related issues

Alex Miller (Clojure team)19:11:45

if you find one, please file a CLJ ticket

Alex Miller (Clojure team)19:11:44

I’ve been using Java 11 as my default for several months now, haven’t had any problems

jwhitlark19:11:39

@alexmiller Any chance we can get a hint of direction on https://dev.clojure.org/jira/browse/CLJ-2365 It's currently high on my wishlist. I don't mind working on it or testing it, but if it's not seriously under consideration....

Alex Miller (Clojure team)19:11:11

it’s post 1.10 so I’m not going to look at anything for it right now

Alex Miller (Clojure team)19:11:27

we’d like to do something there

Alex Miller (Clojure team)19:11:14

The attached patch is interesting but has some potential downsides that need to be assessed

jwhitlark19:11:36

I can imagine. If there's any legwork I can do to help out, please let me know.

Alex Miller (Clojure team)19:11:44

one possible issue is whether those added function calls will conflict with other existing impls of IFn (some of which may not be in clojure itself)

Alex Miller (Clojure team)19:11:08

I think they actually mostly do not, which is good

Alex Miller (Clojure team)19:11:44

the bigger question is whether this would actually be enough to allow Clojure functions to act in a Java SAM context and get all of the high-performance treatment they are doing now (since our functions have a lot more “stuff” on them)

jwhitlark19:11:46

It's a pity there's not a way to do this as a library for the moment. But AFAIK, you can't extend IFn without a custom clojure build?

Alex Miller (Clojure team)19:11:40

if the above is not true, then it would work from a Java type perspective, but may not actually give you the performance a Java user expects and that would be sad

Alex Miller (Clojure team)19:11:55

so that’s kind of an open question, which could be investigated

ghadi19:11:27

there are many more SAMs than the ones in java.util.function.*

jwhitlark19:11:50

@marctrem said he'd been using it, I wonder if he's looked at performance.

jwhitlark19:11:45

So, my understanding is that as SAMs are implemented via invokedynamic, it makes it a tricky case? Where if we just implement the interface, we'll basically get the correct behavior, but will probably be slower since it's using the regular class machinery?

Alex Miller (Clojure team)19:11:29

the desire is to make it “like Java” in performance

Alex Miller (Clojure team)19:11:00

do we get that? if so, then cool. if not, then it might end up needing something more.

ghadi19:11:10

SAMs are instantiated with invokedynamic, but do not have to be

mccraigmccraig21:11:10

i'm seeing an NPE in - apparently because the ContextClassLoader referenced here is nil - https://github.com/clojure/clojure/blob/ee1b606ad066ac8df2efd4a6b8d0d365c206f5bf/src/clj/clojure/java/io.clj#L450

mccraigmccraig21:11:40

javadocs seem to indicate that nil/null is a reasonable value for the ContextClassLoader - https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#getContextClassLoader--

mccraigmccraig21:11:27

so that looks like a bug in io.clj ?

schmee21:11:19

okay, this is driving me crazy, can someone please help me figure out what’s going on here?

(defn print-three [a b c]
  (println a b c))

(defn wrapper [f]
  (fn [& args]
    (apply f args)))

(defn foo [f]
  `(let [a# ~f]
     a#))

(do
  (eval (foo print-three)) ;; this works
  (eval (foo (wrapper print-three)) ;; this doesn't
))

schmee21:11:21

when I print the form that’s to be evaled, in both cases they both look like functions, but the first one works and the second one gives

java-http-clj.core=> (eval (foo (wrapper print-three)))
Execution error (IllegalArgumentException) at java_http_clj.core$eval21417/<clinit> (form-init14437179902714
598852.clj:1).
No matching ctor found for class java_http_clj.core$wrapper$fn__20910

seancorfield22:11:12

Maybe a classloader thing? Doesn't eval create a classloader for the evaluation, so an anonymous function passed in is going to have been compiled into a class in a different classloader? :thinking_face:

schmee22:11:16

oof, I’m in the weeds now ain’t I? 😂

seancorfield22:11:04

If you change foo to a macro it works.

seancorfield22:11:17

(or, at least, it doesn't blow up)

schmee22:11:23

FWIW I’m trying to reify some stuff at runtime based on a functions passed in a map

seancorfield22:11:16

user=> (defmacro foo [f] `(let [a# ~f] a#))
#'user/foo
user=> (eval (foo (wrapper print-three)))
#object[user$wrapper$fn__146 0x2f48b3d2 "user$wrapper$fn__146@2f48b3d2"]
user=> (eval (foo (wrapper print-three)))
#object[user$wrapper$fn__146 0x44e3a2b2 "user$wrapper$fn__146@44e3a2b2"]
user=> ((eval (foo (wrapper print-three))) 1 2 3)
1 2 3
nil
user=> ((eval (foo print-three)) 1 2 3)
1 2 3
nil
user=> 
But maybe that won't help for your use case.

schmee22:11:39

it might, lemme give it a try

schmee22:11:12

does it make sense to use eval in a macro?

hiredman22:11:34

you are passing a fn object to eval

hiredman22:11:43

instead of a form that evaluates to fn object

hiredman22:11:11

for sort of arcane reasons this tends to work if the function doesn't close over any values, and explodes if it does

hiredman22:11:35

in general you should also pass to eval forms that evaluate to something

schmee22:11:31

I see! I’m actually surprised that the first one works, but I would’ve expected both to work or both to fail

schmee22:11:56

can you give some more info on these “arcane reasons”? I’d like to do some digging 🙂

lwhorton22:11:06

interesting meta-question… why can i call first on anything that’s associative but not ordered/sequential?

lwhorton22:11:35

(first my-map) semantically doesn’t make much sense; what’s the “first” thing of a non-ordered collection?

noisesmith22:11:45

because the item is seqable?

noisesmith22:11:20

it is seqable? because it's useful to iterate, as a side effect of this calling first works

hiredman22:11:42

I would argue that if you are using eval in a macro you are mixing levels in the stages of read -> macro expand -> compile -> run in ways that you shouldn't

hiredman22:11:26

your code has a specific meaning at each stage, and trying to use the meaning from a later stage in an earlier stage is going to be broken

emccue22:11:44

we dont have a cthulu emoji

emccue22:11:06

but yeah, there be dark magics

schmee22:11:13

yeah the thing I’m doing is totally stupid, spent two hours with insane macro gymnastics just to avoid one function call 😂

schmee22:11:25

BUT, that’s how you learn interesting stuff 🙂

emccue22:11:42

I think rust has the right idea with calling their macro book the "macronomicon"

emccue22:11:43

you can use regular ol' magic (hygenic macros) all the time

emccue22:11:18

but once you start dabbling in the dark arts (macros that mess with time or matter (state))

emccue22:11:45

its not long until you are a full on ev(a)l wizard

schmee22:11:14

it’s definitely true that once you start using macros, it starts to “infect” the surrounding code and suddenly everything has to be macros

schmee22:11:37

or maybe that’s only true for macro beginners like myself 🙂

dpsutton22:11:57

i can't write clojure without defn these days 😞

dpsutton22:11:03

stupid macros

hiredman22:11:20

yeah, if you use values at earlier stages in the pipeline, then they must be available at earlier stages of the pipeline

noisesmith22:11:28

@dpsutton (intern *ns* 'my-fn (fn* [x] ....)) - not that I recommend writing code that way, and arguably special forms are worse than macros

dpsutton22:11:57

haha. i'll slip that into the next PR

noisesmith22:11:25

oh, and then use with-meta to attach a doc-string, of course :D

noisesmith22:11:49

since a reader macro would be cheating

schmee22:11:42

is it possible to call the default static method when reifying an interface?

noisesmith22:11:08

proxy can use proxy-super

hiredman22:11:52

you mean the new default interface method things interfaces can have?

hiredman22:11:29

how are those represented in bytecode?

emccue22:11:34

That would be a good title for a book on lisp macros

emccue22:11:39

"Eval Wizard"

emccue22:11:14

If it is static in the interface then reify cant do anything to it

emccue22:11:32

but you can always refer to it

emccue22:11:25

Here is the bug that has been resolved that added that support

schmee22:11:55

here’s what I can’t figure out:

(reify WebSocket$Listener
     (onOpen [this ws]
       (if on-open
         (on-open ws)
         (.onOpen this ws)))

schmee22:11:33

I want the .onOpen to use the default method, but since I’ve already overrided the onOpen in my reify, it just loops and stack overflows

emccue22:11:47

try (proxy-super onOpen ws)

hiredman22:11:48

from a little reading, you can't invoke the default interface method from a clojure implementation of the interface

hiredman22:11:31

it requires an invokespecial to call it, and clojure only emits those for constructors

emccue22:11:04

macro time everybody

😂 4
schmee22:11:07

hmm… sounds worthy of a ticket at least?

emccue22:11:19

let me whip up a minimal example

hiredman22:11:21

macros can't emit different bytecode

schmee22:11:33

@emccue dude this is how I got stuck in macro hell in the first place! 😂

dpsutton22:11:02

@hiredman where did you go to find out "it requires an invokespecial to call it"

hiredman22:11:25

I googled "jvm interface default method bytecode"

dpsutton22:11:45

wow. don't overthink it huh 🙂

dpsutton22:11:08

and thanks by the way

jaawerth22:11:21

I think the "official" way to handle that is to use a proxy instead of reify so you can use proxy-super

schmee22:11:48

can proxy do interfaces as well? I’ve only used it for abstract classes

hiredman22:11:21

@schmee

(if on-open
  (reify
    WebSocket$Listener
    (onOpen [this ws]
      (on-open ws)))
  (reify
    WebSocket$Listener))

schmee22:11:55

yes, and now multiply that for all 8 methods with default implementations 😄

hiredman22:11:07

so write a macro that generates the conditional

schmee22:11:32

(defmacro websocket-listener-fast
  ([{:keys [on-binary on-close on-error on-open on-ping on-pong on-text]}]
   `(let [~'ob ~on-binary
          ~'oc ~on-close
          ~'oe ~on-error
          ~'oo ~on-open
          ~'opi ~on-ping
          ~'opo ~on-pong
          ~'ot ~on-text]
     (reify WebSocket$Listener
       ~@(when on-binary
           [`(~'onBinary [_# ws# byte-buffer# last?#]
              (~'ob ws# byte-buffer# last?#))])
       ~@(when on-close
          [`(~'onClose [_# ws# status-code# reason#]
             (~'oc ws# status-code# reason#))])
       ~@(when on-error
          [`(~'onError [_# ws# throwable#]
             (~'oe ws# throwable#))])
       ~@(when on-open
          [`(~'onOpen [_# ws#]
             (~'oo ws#))])
       ~@(when on-ping
          [`(~'onPing [_# ws# byte-buffer#]
             (~'opi ws#))])
       ~@(when on-pong
          [`(~'onPong [_# ws# byte-buffer#]
             (~'opo ws#))])
       ~@(when on-text
          [`(~'onText [_# ws# char-seq# last?#]
             (~'ot ws# char-seq# last?#))])))))

hiredman22:11:09

you specifically don't generate the conditional there

hiredman22:11:27

you are trying to do the conditional at macro expand time instead of at runtime, which is your problem

hiredman22:11:02

your macro should return a form like (cond some-set-methods some-call-to-reify ...)

hiredman22:11:33

or case might be even better

schmee22:11:51

but doesn’t that mean I need a cond for each combination of input fns?

hiredman22:11:16

but yeah, a branch for each possible combination

schmee22:11:42

oof, there are a couple of those, let me count

hiredman22:11:54

which is why you generate them with a macro

hiredman22:11:41

2^8 (each method is either overridden or it isn't)

hiredman22:11:01

you only have 7 there

schmee22:11:32

haha yeah I guess I could actually do that then 😄

hiredman22:11:34

much more manageable

schmee22:11:49

let me give it a shot, thanks 🙂

schmee23:11:51

here’s the final product, it actually works 😂

(defn websocket-listener-fast [ks]
  `(reify WebSocket$Listener
     ~@(when (ks :on-binary)
         [`(~'onBinary [_# ws# byte-buffer# last?#]
                       (~'on-binary ws# byte-buffer# last?#))])
     ~@(when (ks :on-close)
         [`(~'onClose [_# ws# status-code# reason#]
                      (~'on-close ws# status-code# reason#))])
     ~@(when (ks :on-error)
         [`(~'onError [_# ws# throwable#]
                      (~'on-error ws# throwable#))])
     ~@(when (ks :on-open)
         [`(~'onOpen [_# ws#]
                     (~'on-open ws#))])
     ~@(when (ks :on-ping)
         [`(~'onPing [_# ws# byte-buffer#]
                     (~'on-ping ws#))])
     ~@(when (ks :on-pong)
         [`(~'onPong [_# ws# byte-buffer#]
                     (~'on-pong ws#))])
     ~@(when (ks :on-text)
         [`(~'onText [_# ws# char-seq# last?#]
                     (~'on-text ws# char-seq# last?#))])))

(def fns [:on-binary :on-close :on-error :on-open :on-ping :on-pong :on-text])

(defmacro dear-god-why [subset]
  `(case ~subset
     ~@(mapcat
         (fn [s] [(set s) (websocket-listener-fast (set s))])
         (combo/subsets fns))))

(defn do-the-thing
  [{:keys [on-binary on-close on-error on-open on-ping on-pong on-text] :as fns}]
  (dear-god-why (-> fns keys set)))

schmee23:11:05

feels crazy to write code like this but maybe I’m just not used to it 🙂

jaawerth22:11:24

ohh yeah if you're not actually working with a class then you likely can't use proxy. my bad, misread the situation

emccue22:11:57

StackOverflowError java.lang.Class.privateGetDeclaredMethods (Class.java:2695)

emccue22:11:09

I will fix it!

emccue22:11:00

IllegalAccessException no private access for invokespecial getting there

schmee22:11:16

compiler hacking? 😄

emccue23:11:54

yep I got nothing

emccue23:11:58

wait, hat trick

schmee23:11:46

oh man, that’s great! 😄

emccue23:11:17

so instead of going through the hoops to call the interface, just selectively dispatch

schmee23:11:17

cheers! 🍻

emccue23:11:51

(maybe better to pass this instead of default-listener to your custom functions)

dpsutton23:11:10

maybe (let [that (reify WebSocket$Listener) and then dispatch on either this or that