Fork me on GitHub
#clojure
<
2020-04-27
>
rlander00:04:25

Hi! Is there a way to add metada to a final Java class? I need to have different cheshire formatters for different instances of a Java class (backwards compatibility).

seancorfield00:04:51

You can't add metadata to arbitrary Java objects. Only to things that implement clojure.lang.IObj

rlander00:04:24

@seancorfield Thanks for the explanation. I've tried using proxy but that didn't work.

rlander00:04:34

(defn wrap-money
    ([^Money m] (wrap-money m {}))
    ([^Money m _meta]
     (proxy [Money IObj] [(.getAmount m)]
       (meta [] _meta)
       (withMeta [meta'] (wrap-money m meta')))))  

rlander01:04:26

Even if I create a Java wrapper that implements IObj, it's not gonna work because the Money class is final.

didibus01:04:26

There's another interface you need to implement as well, forgot what it is

didibus01:04:37

IMeta maybe?

rlander01:04:08

@didibus will try that, thanks

rlander01:04:54

nope, same error Cannot inherit from final class

didibus01:04:15

Oh, that's different, you can't extend a final class in Java

didibus01:04:33

I'm guessing Money is final?

didibus01:04:23

All you could do is use composition, make something take a Money inside itself. MoneyWrapper, you can make that a vector, a map, a record, or go for a deftype or a Java class, up to you

didibus01:04:13

But I'm guessing maybe cheshire has another mechanism to pick its formatter, aside from meta?

rlander01:04:55

@didibus I've spent the last hour reading the cheshire docs & source and, to be honest, I think even the meta route wasn't going to work because I don't think Cheshire will pickup the correct superclass.

rlander01:04:10

I really don't want to have to refactor old code, so I've been trying the polymorphism/meta route before a wrapper container.

andy.fingerhut01:04:32

Clojure protocols can add functionality to existing classes, I believe even final ones, only for Clojure protocol functions. Not sure whether Cheshire has any protocols you can extend in its implementation

rlander02:04:59

@andy.fingerhut cheshire does have protocols to extend its implementation, but I don't see how I would differentiate one format from another without metada or somehow extending/subclassing the original class. Maybe my OO is a little rusty?

jjttjj02:04:57

Can you create a wrapper/box class for the special instances and have it contain the money object, then add an encoder for your box class that accesses it?

jjttjj02:04:16

(require '[cheshire.core :as json]
  '[cheshire.generate :as cg])

(defrecord MyMoney [money])

(cg/add-encoder MyMoney
  (fn [c jsonGenerator]
    (.writeString jsonGenerator (str (.-money c)))))

(json/generate-string {:m1 (MyMoney. 123)})
;;=> "{\"m1\":\"123\"}"
(just replace the str call in the encoder to however ou want to access the money object)

rlander03:04:18

Thanks for the suggestion, that's precisely where I arrived at, was just coming back to report on that 😃

rlander03:04:46

the fact that proxy (understandably) doesn't support final classes crushed my simple workaround, but this is a reasonable alternative.

andy.fingerhut04:04:46

@U0B7P8YMB I haven't personally done it, but I believe that one of the reasons for creating Clojure protocols is so that you can define protocol functions differently for every class, even for final classes and/or ones that you do not want to change/extend in any way.

👍 8
andy.fingerhut04:04:40

https://clojure.org/reference/protocols. in particular the part about extend

✔️ 4
Adam Helins08:04:40

Using defn, there is no way to attach metadata on the fn itself rather than the var, is there?

delaguardo08:04:58

the only metadata that is attached to the fn is :tag but you can use

(def foo (with-meta (fn [x] ...) {:k 'v}))
defn is just a macro that expand into ^ that form

Adam Helins11:04:21

Indeed, but it would have been slightly nicer keeping defn . Thanks!

delaguardo12:04:03

write your own macro )

delaguardo12:04:17

(defmacro defn* [meta & body]
  (let [[_ name f] (macroexpand (cons 'defn body))]
    (list 'def name (list 'with-meta f meta))))

dominicm16:04:05

These will break arglists in most tooling... I'd recommend copying the meta to the fn

delaguardo16:04:08

tooling should be ok because arglists attached to var not to the function

dominicm16:04:51

I misread your trick, interesting

plins18:04:46

hey everyone, Im seeking general advice regarding writing tests and fixtures lets say Im testing a REST API, specifically a GET /user/123 where 123 is the id of the test user create right before the test by a fixture

(defn create-delete-user [f]
  (fixture/create-user)
  (f)
  (fixture/delete-user))

(use-fixtures :each create-delete-user)

(deftest user-test
 (testing "get user bla bla .."
   here I need to compare the result of the HTTP call with the fixture user))
lets say fixture/create-user returns the created user, whats the best way to share this data with the test inside the testing block? I could create an empty atom and write and read there, but this just feels wrong

Darin Douglass18:04:20

you could use a binding instead if you didn't want to use an atom:

(def ^:dynamic *user* nil)

(use-fixtures :each
  (fn [test]
    (with-bindings {#'*user* (fixture/create-user)}
      (test)
    (fixture/delete-user)))

plins18:04:31

which one is more idiomatic clojure? Im ok with anything, Im just asking because of shared mutable state etc

noisesmith18:04:09

the idiomatic thing would be to use binding - my understanding was that with-bindings is a helper for macros

Darin Douglass18:04:45

i always forget which func is which, for some reason my eyes gravitated towards with-bindings this time 😛

Darin Douglass18:04:54

imo binding does feel more idiomatic. however, assuming test runs are independent, i typically prefer the "fixtures create/clear tables, while tests setup their data explicitly" route

plins18:04:20

I could also dont use a fixture to create data and do stuff inside a let block on the test itself, but it feels dirty

plins18:04:53

thats exactly what I am talking about, to be honest seems the most sane way of doing it

plins18:04:10

although it feels strange to mix setup code with the actual test, but maybe Im just used to something else and this is fine

Travis Jefferson18:04:45

some pedestal docs I stumbled across yesterday approached this with a purpose-built macro

Travis Jefferson18:04:56

(defmacro with-system
  [[bound-var binding-expr] & body]
  `(let [~bound-var (component/start ~binding-expr)]
     (try
       ~@body
       (finally
         (component/stop ~bound-var)))))

Travis Jefferson18:04:50

you still end up with a let-like syntax in your test, but at least you don’t have to copy the setup/teardown

Darin Douglass18:04:19

(use-fixtures :once
  (fn [test]
    (db/create-schema!)
    (test)
    ;; really only needed if your db instance is persistent
    (db/drop-schema!)))

(use-fixtures :each
  (fn [test]
    (db/clear-tables!)
    (test)))

(deftest test-something
  (let [user (db/crete-test-user!)]
    (sut/do-a-thing user)))

Darin Douglass18:04:41

imo it all really depends on how much your tests share, how complex the db is, etc

plins18:04:57

thanks everyone for the input 🙂

eudis20:04:21

Is it possible to get an ISO String formatted date in Clojure without using Java? ie is there a built in for clojure? (iso string):

"2017-06-09T06:59:40.829-00:00"

hindol20:04:58

I don't think Clojure core has a date time API. Just use the one from Java 8 (not the calendar API, use the Local* things).

eudis20:04:39

well, I figured I'd ask anyway, i was having a hard time finding anything in clojure. Thanks

noisesmith20:04:41

also if clojure had a built in for time, that's what it would do

noisesmith20:04:21

with few exceptions any domain stuff will not be bundled with clojure itself, only data structure and algorithmic functions are well represented

👍 4
Nicky Chorley20:04:24

(of course it uses java.time underneath

souenzzo20:04:15

@U011PQGUDCG (str (.toInstant #inst"2000"))

eudis20:04:11

That did it thank you!

souenzzo20:04:47

java.time is a awesome lib I recommend to use it directly

👍 4
henryw37406:04:38

second that. also see my lib https://github.com/henryw374/time-literals to improve the experience with data literals, e.g. #time/instant "2018-07-25T07:10:05.861Z"

hindol20:04:40

If I start a thing in the REPL that is supposed to read from STDIN, how do I send something to it?

noisesmith20:04:47

by sending something to stdin - or more generally you can bind *in* to an apropriate source of input

noisesmith20:04:13

clojure's read etc. will use whatever *in* is set to (it can be set dynamically using binding)

noisesmith20:04:50

when you use a network repl client, *in* will be bound such that read still works (unless other tooling like your editor gets in the way)

noisesmith20:04:11

of course, be wary of calling read in a background thread while accepting input on the same *in* in the main thread (and visa versa)

hindol20:04:49

Interactivity would be nice, but it's okay if I have to bind *in* to something. Since there is only one Java process now (the REPL's) I was confused who will capture the input. Your answer helps! Thanks.

noisesmith20:04:32

you don't have to bind *in* if you want to use the same input source as the repl itself, it will just work (unless an editor breaks that...)

hindol20:04:35

Since the REPL is already running, is there a way to "attach" to the stdin now?

noisesmith20:04:17

the *in* of your repl client is the one that will be used by read, not the one of the server process

noisesmith20:04:15

how did you start the server? it might be possible to get its stdin, it might not, but it shouldn't matter

hindol20:04:17

That still means I have to dump something in a text file and bind that to *in*. Is there another way?

noisesmith20:04:50

if you want to read from a text file, that's pretty much how you do it - unless you want to use slurp and read-string instead of read

hindol20:04:53

^ The server is just CIDER jack in.

noisesmith20:04:04

that's why I mentioned editors

noisesmith20:04:26

thanks to cider jack in, emacs owns your server's real stdin and I doubt you'll get it back

Ahmed Hassan20:04:47

What difference would it make in the case of cider-connect?

noisesmith20:04:25

because emacs started the process and connected something to it

noisesmith20:04:57

maybe there's a way to ask emacs for that file handle and redirect something else to it, but that's not a clojure question any more, it's an emacs / unix question

noisesmith20:04:27

and I don't expect it to be easy - not compared to binding *in* which already does what one needs here

hindol20:04:58

It seems Emacs can send something to an inferior process and rebind its stdout on the fly to a buffer. But I will experiment with it some other day. (Bottom line: Emacs does everything!)

noisesmith20:04:02

except for example threads

Ahmed Hassan20:04:12

Emacs is a mini operating system

noisesmith20:04:27

without threads

noisesmith20:04:56

I stand corrected

noisesmith20:04:14

I wonder if this is up to date "https://github.com/google/sanitizers/wiki/ThreadSanitizerCppManual quickly shows that Emacs’ threading implementation has many data races, making it https://hboehm.info/boehm-hotpar11.pdf."

noisesmith20:04:42

"only one thread runs at a time"

noisesmith20:04:53

anyway, this is well off topic by now, sorry

hindol20:04:24

NP. Don't know. Never bothered. I like Emacs because you can be totally off the mouse and some of the things just work. Although the UI is terribly slow as compared to something like VSCode.

Ahmed Hassan21:04:27

@U051SS2EU what alternatives do you suggest to Emacs?

noisesmith21:04:25

I don't think that's on topic here, I meant the thread thing to be a joke / tease but my knowledge is out of date

noisesmith21:04:35

use emacs if that's the thing that works for you

didibus21:04:09

Threads in emacs are experimental for now. Packages are advised not to rely on them yet

didibus21:04:27

Most concurrency for now is handled through sub-process

noisesmith20:04:53

but you shouldn't need it - nothing in clojure relies on the identity of stdin itself, it all uses the *in* abstraction instead

Adam Helins20:04:45

Is there a secret way for aliases to include other aliases in deps ?

Alex Miller (Clojure team)23:04:42

what is your goal? running multiple commands or combining multiple pieces of configuration?

Adam Helins20:05:08

Rather combining multiple pieces of configurations (dependencies, really)

Alex Miller (Clojure team)20:05:53

and invoking multiple aliases at the same time is just more than you want to require of project users?

Alex Miller (Clojure team)20:05:07

just trying to understand the needs

Adam Helins20:05:41

It is just that sometimes (but not that often, I concede) it can become a bit hard to track, what alias should be invoked when

Adam Helins20:05:11

Honestly it is not a big deal, I was curious about it but I would certainly not fight for it...

Alex Miller (Clojure team)21:05:03

we have some new stuff coming soon and so I'm really trying to tease apart whether this would be helped or could be helped

👍 4
seancorfield21:04:57

@adam678 No. Aliases cannot bundle together other aliases. It's quite a common request.

andy.fingerhut21:04:33

Might it be fairly straightforward to write a Clojure program that reads a deps.edn file, or something similar to one that introduces some new convention for data that lets one alias reference another one, and merge the contents of the aliases together? Seems like a potentially nifty 3rd-party utility, although definitely understandable if Clojure core team would not want to write/maintain such a thing.

andy.fingerhut21:04:21

That would open the door to N different tools (or perhaps one that combines N different features) for reading something, and generating a deps.edn file.

potetm21:04:31

I usually add a few bash scripts for common tasks which all have the proper aliases.

potetm21:04:56

heck, you can even add a bin/clj script that does pretty much what you’re saying

Adam Helins21:04:13

Just for info, is it by choice or by constraint? Otherwise yes, custom bash scripts is what I am doing, but it does feel too "smart". Maybe it is for the best...

potetm21:04:37

just bash it – it’s for the best

potetm21:04:53

(famous last words, but srsly tho)

didibus21:04:27

I've just been duplicating for now

didibus21:04:58

Also, while you can't have an alias of alias, you can list out multiple alias when running clj to combine them

didibus21:04:41

clj -A:foo:bar

Adam Helins21:04:20

Oh yeah, by custom bash scripts this is what I meant, so that I don't have to type long chains of aliases combinaisons

didibus21:04:17

Ya, you can just setup a bash alias for that command

didibus21:04:25

I think that's fine

didibus22:04:06

I even have some bash alias for single alias. Like I've remapped cljto clj -A:repl

Spaceman22:04:32

I'd like to run clojurescript tests using https://github.com/lambdaisland/kaocha-cljs kaocha-cljs. So I put the [lambdaisland/kaocha-cljs "0.0-71"] dependency in my lein project.clj, and restart the repl and run the command in the root directory:

clojure -m kaocha.runner unit-cljs
But I get the error:
Execution error (FileNotFoundException) at clojure.main/main (main.java:40).
Could not locate kaocha/runner__init.class, kaocha/runner.clj or kaocha/runner.cljc on classpath.
Why would this be, and how can I fix this?

noisesmith22:04:21

clojure doesn't use project.clj

noisesmith22:04:57

you probably want the koacha lein plugin, assuming such a thing exists

Spaceman22:04:23

why does kaocha-cljs exist then if you could just use kaocha for running cljs tests?

noisesmith22:04:21

never mind, you are using koacha-cljs not koacha, and that doesn't use lein

noisesmith22:04:05

it looks like you would need to migrate away from lein to use koacha-cljs, or port it to lein somehow

Spaceman22:04:02

so I'm using the Luminus template, and I haven't yet needed to worry about the connection between cljs and clojure. But any cljs dependency I put in the project.clj dependencies and it just works.

noisesmith22:04:18

sure, that's how the lein integration works

noisesmith22:04:39

but koacha-cljs is built around clj/clojure which is a different tool, not compatible with lein

Spaceman22:04:28

how to port kaocha-cljs to lein

souenzzo22:04:13

lein run kaocha.runner unit-cljs should work

Spaceman22:04:05

@souenzzo that gives me this error:

.BindException: Address already in use
	at .bind0(Native Method)
	at .bind(Net.java:433)
	at .bind(Net.java:425)
	at .ServerSocketChannelImpl.bind(ServerSocketChannelImpl.java:223)
	at io.netty.channel.socket.nio.NioServerSocketChannel.doBind(NioServerSocketChannel.java:128)
	at io.netty.channel.AbstractChannel$AbstractUnsafe.bind(AbstractChannel.java:558)
	at io.netty.channel.DefaultChannelPipeline$HeadContext.bind(DefaultChannelPipeline.java:1358)
	at io.netty.channel.AbstractChannelHandlerContext.invokeBind(AbstractChannelHandlerContext.java:501)
	at io.netty.channel.AbstractChannelHandlerContext.bind(AbstractChannelHandlerContext.java:486)
	at io.netty.channel.DefaultChannelPipeline.bind(DefaultChannelPipeline.java:1019)
	at io.netty.channel.AbstractChannel.bind(AbstractChannel.java:254)
	at io.netty.bootstrap.AbstractBootstrap$2.run(AbstractBootstrap.java:366)
	at io.netty.util.concurrent.AbstractEventExecutor.safeExecute(AbstractEventExecutor.java:163)
	at io.netty.util.concurrent.SingleThreadEventExecutor.runAllTasks(SingleThreadEventExecutor.java:404)
	at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:465)
	at io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:884)
	at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
	at java.lang.Thread.run(Thread.java:748)
Syntax error (BindException) compiling at (/private/var/folders/zz/zyxvpxvq6csfxvn_n0000000000000/T/form-init6496307984697349116.clj:1:125).
Address already in use

Full report at:
/var/folders/zz/zyxvpxvq6csfxvn_n0000000000000/T/clojure-9061200669573262320.edn
and I also tried in the clj repl:
(require '[kaocha.runner])
nil
user> (kaocha.runner unit-cljs)
Syntax error (ClassNotFoundException) compiling at (*cider-repl luminus/vendo:localhost:63551(clj)*:62:7).

noisesmith22:04:56

the lein command line would translate to (koacha.runner/-main "unit-cljs")

Spaceman22:04:27

Still, I get 1. Unhandled java.lang.ClassNotFoundException koacha.runner URLClassLoader.java: 382 http://java.net.URLClassLoader/findClass DynamicClassLoader.java: 69 clojure.lang.DynamicClassLoader/findClass ClassLoader.java: 418 java.lang.ClassLoader/loadClass DynamicClassLoader.java: 77 clojure.lang.DynamicClassLoader/loadClass ClassLoader.java: 351 java.lang.ClassLoader/loadClass Class.java: -2 java.lang.Class/forName0 Class.java: 348 java.lang.Class/forName RT.java: 2211 clojure.lang.RT/classForName RT.java: 2224 clojure.lang.RT/classForNameNonLoading Compiler.java: 1041 clojure.lang.Compiler$HostExpr/maybeClass Compiler.java: 7045 clojure.lang.Compiler/macroexpand1 Compiler.java: 7075 clojure.lang.Compiler/macroexpand Compiler.java: 7161 clojure.lang.Compiler/eval Compiler.java: 7132 clojure.lang.Compiler/eval core.clj: 3214 clojure.core/eval core.clj: 3210 clojure.core/eval interruptible_eval.clj: 91 nrepl.middleware.interruptible-eval/evaluate/fn main.clj: 437 clojure.main/repl/read-eval-print/fn main.clj: 437 clojure.main/repl/read-eval-print main.clj: 458 clojure.main/repl/fn main.clj: 458 clojure.main/repl main.clj: 368 clojure.main/repl RestFn.java: 137 clojure.lang.RestFn/applyTo core.clj: 665 clojure.core/apply core.clj: 660 clojure.core/apply regrow.clj: 18 refactor-nrepl.ns.slam.hound.regrow/wrap-clojure-repl/fn RestFn.java: 1523 clojure.lang.RestFn/invoke interruptible_eval.clj: 84 nrepl.middleware.interruptible-eval/evaluate interruptible_eval.clj: 56 nrepl.middleware.interruptible-eval/evaluate interruptible_eval.clj: 155 nrepl.middleware.interruptible-eval/interruptible-eval/fn/fn AFn.java: 22 clojure.lang.AFn/run session.clj: 190 nrepl.middleware.session/session-exec/main-loop/fn session.clj: 189 nrepl.middleware.session/session-exec/main-loop AFn.java: 22 clojure.lang.AFn/run Thread.java: 748 java.lang.Thread/run

noisesmith22:04:15

you still need to require koacha.runner , and it needs to be available as a dep you can require

noisesmith22:04:22

that error is saying it hasn't been loaded

Spaceman22:04:45

user> (require 'kaocha.runner) nil user> (koacha.runner/-main "unit-cljs") Execution error (ClassNotFoundException) at http://java.net.URLClassLoader/findClass (URLClassLoader.java:382). koacha.runner

Spaceman22:04:05

And my project.clj dependency contains both Kaocha and kaocha-cljs

Spaceman22:04:49

so why classnotfoundexception?