clojure

New to Clojure? Try the #beginners channel. Official docs: https://clojure.org/ Searchable message archives: https://clojurians-log.clojureverse.org/
zendevil 2021-03-11T07:02:23.456600Z

I’m querying a mongodb database which is returning items of the following kind:

({:_id #object[org.bson.types.ObjectId 0x79b7fb5 "6011a0b428dacb0004d7475b"], :name nil, :uri "dzhqroksvtvveqqnxmrclebakmighdlmkccpkidefupibqifziezasljxqvjkrmxfzuydyoucxozsbqxmodpjvvuzzkxjgckrkmz.mov"} etc...)
However, the problem is that in order to send the data to the client, I have to convert the _id which I think is called a tagged literal to a string. I do this using
(map id->str data)
where id->str is
(defn id->str [item]
  (assoc item :_id (str (:_id item))))
However, the problem arises when there are multiple fields that contain tagged literals, in which case I have to do this manually. Is there a way to send this data without having to manually find and convert each tagged literal?

2021-03-11T07:11:24.458Z

It isn't a tagged literal, that is just how prn prints objects is doesn't know a more specific way to print

2021-03-11T07:13:41.460300Z

Depending on how you serialize the data to send it to clients etc, that may be the best place to do any transformation, since serialization has to walk the whole structure anyway

zendevil 2021-03-11T07:17:15.461300Z

@hiredman does that mean that I have to know beforehand what fields contain ObjectId’s before doing the transformation, or is there a way to do that automatically?

zendevil 2021-03-11T07:18:13.462100Z

I can’t convert every field to a string either because some are numbers

2021-03-11T07:23:47.466700Z

For objects that print like #object[org.bson.types.ObjectId 0x79b7fb5 "6011a0b428dacb0004d7475b"] the format is like #object[JVM_CLASS SYSTEM_IDENTITY_HASH TO_STRING]

2021-03-11T07:24:43.468200Z

So you would walk the data structure, perhaps using something like clojure.walk, looking for instances of ObjectId

2021-03-11T07:26:27.470300Z

Or use a something in your serialization code that allows you to customize the serialization based on types

zendevil 2021-03-11T07:28:19.470900Z

@hiredman what about the link that you sent? I required monger.json but that didn’t work

dpsutton 2021-03-11T07:33:39.471100Z

did you add cheshire?

dpsutton 2021-03-11T07:33:56.471300Z

> To use it, you need to add Chshire dependency to your project

zendevil 2021-03-11T07:34:03.471600Z

yes that’s a dependency

dpsutton 2021-03-11T07:44:47.472100Z

and did you require monger.json?

dpsutton 2021-03-11T07:45:20.472400Z

(json/generate-string (mc/insert-and-return (mg/get-db conn "monger-test") "documents" {:name "John" :age 30}))
"{\"_id\":\"6049ca80360cf43286348620\",\"name\":\"John\",\"age\":30}"

zendevil 2021-03-11T08:11:08.472900Z

@dpsutton however, this involves making changes in the client, because earlier I was sending an edn and now it’s a string.

zendevil 2021-03-11T08:11:45.473100Z

albeit the edn gets converted into json, which is not the case here

zendevil 2021-03-11T08:14:50.473300Z

so I resorted to doing: (-> data json/generate-string json/parse-string)

2021-03-11T08:30:26.473900Z

If you are sending edn I would likely use clojure.walk to transform the structure before printing. You could add a print-method for ObjectId, but that is a global thing (I try to avoid that kind of thing)

dpsutton 2021-03-11T07:51:29.472500Z

user> (require '[monger.core :as mg])
nil
user> (def conn (mg/connect))
#'user/conn
user> (require '[monger.collection :as mc])
nil
user> (mc/insert-and-return (mg/get-db conn "monger-test") "documents" {:name "John" :age 30})
Mar 11, 2021 1:42:34 AM com.mongodb.diagnostics.logging.JULLogger log
INFO: Opened connection [connectionId{localValue:2, serverValue:3432}] to 127.0.0.1:27017
{:_id
 #object[org.bson.types.ObjectId 0x50f01cbb "6049c9ea360cf4328634861e"],
 :name "John",
 :age 30}
user> (require 'monger.json)
nil
user> (mc/insert-and-return (mg/get-db conn "monger-test") "documents" {:name "John" :age 30})
{:_id
 #object[org.bson.types.ObjectId 0x462cd22d "6049c9f0360cf4328634861f"],
 :name "John",
 :age 30}
user> (require '[cheshire.core :as json])
nil
user> (json/generate-string *2)
"{\"_id\":\"6049c9f0360cf4328634861f\",\"name\":\"John\",\"age\":30}"
user> 

dpsutton 2021-03-11T07:51:45.472700Z

some logging and other stuff omitted

zendevil 2021-03-11T08:11:08.472900Z

@dpsutton however, this involves making changes in the client, because earlier I was sending an edn and now it’s a string.

zendevil 2021-03-11T08:11:45.473100Z

albeit the edn gets converted into json, which is not the case here

zendevil 2021-03-11T08:14:50.473300Z

so I resorted to doing: (-> data json/generate-string json/parse-string)

2021-03-11T08:30:26.473900Z

If you are sending edn I would likely use clojure.walk to transform the structure before printing. You could add a print-method for ObjectId, but that is a global thing (I try to avoid that kind of thing)

Elso 2021-03-11T12:22:09.474100Z

the idea was something like have the "queue" be a producer that yields when someone takes from it

ABeltramo 2021-03-11T13:08:32.475100Z

Hello everyone! Don't know where to post this, can someone point me out to a clj-commons/manifold channel? Or can I ask help over here?

p-himik 2021-03-11T13:11:14.475200Z

There doesn't seem to be such a channel, so here should be fine.

ABeltramo 2021-03-11T13:11:48.475400Z

Thanks, I'll write my question then

ABeltramo 2021-03-11T13:21:55.481300Z

I'm using manifold and I can't wrap my head around splitting a stream: here's an example code:

(let [pos-stream (ms/stream)
      neg-stream (ms/stream)]
  (->> value-generator 
       (ms/->source)
       (ms/map (fn [value]
                 (if (pos? value)
                   (ms/put! pos-stream value)
                   (ms/put! neg-stream value))))
       ; I need to fill streams, how should I do that? 
       (ms/consume identity)
       )
  
  (->> (ms/zip pos-stream neg-stream)
       (ms/map (fn [[pos-value neg-value]]
                 (process pos-value neg-value)))))
A simple example that should capture my problem: I have a fn that generates values and put it into a stream. I need to process that stream in pairs based on a condition, how can I accomplish that? My idea is explained in the code example: map over the stream, fill two separate stream and then zip them in order to process in pairs. The issue is that the pos and neg streams are not joined with the drain stream, I think that the code above will keep generating values forever (when would consume stop?). Is there a better way to implement this?

ABeltramo 2021-03-11T13:25:31.482Z

Or is this fine since you can't put more than one element in the stream until it's taken away?

flowthing 2021-03-11T13:27:54.482100Z

You could try #aleph if you don't get a response here.

ABeltramo 2021-03-11T13:29:34.482300Z

Thanks! I'll try there if no reply comes here

borkdude 2021-03-11T13:50:33.483800Z

I'm assuming the answer is no, but worth a try: Is there a trick to "disable" an interface on an object instance after the fact? E.g. (def obj (reify Interface1)) (instance? Interface1 obj) ;; true (disable! obj Interface1) (instance? Interface1 obj) ;; false

alexmiller 2021-03-11T13:52:39.484600Z

No, I don’t even think that’s possible via java agent trickery

borkdude 2021-03-11T13:55:45.487800Z

The problem I'm struggling with: I'm trying to support reify in a clojure interpreter that runs in an environment that doesn't allow you to define new classes. The trick I've used is to support reify with a fixed list of interfaces. A compile time function creates instances that implement all of the interfaces, but the instance checks at runtime for methods it has available provided by the caller of reify. This works well. Until... you pass those objects to core functions that have a series of instanceof checks. E.g. the objects always implement IFn so (fn? obj) always returns true, even if you didn't provide an implementation for IFn, since this is a compile time decision.

borkdude 2021-03-11T13:56:01.488200Z

I'm inclined to roll back this idea, unless there is a way out.

borkdude 2021-03-11T14:01:08.488900Z

The other option I've considered is to support groups of interfaces that are commonly used together and then you'll have to implement them all

borkdude 2021-03-11T14:01:51.489300Z

The problem there is that if we add an interface to one of the groups, it will break existing callers.

borkdude 2021-03-11T14:08:59.490300Z

I have also tried the "every subset" approach, but this blows up very quickly. For 10 classes this requires pre-generation of 1024 classes. For 100 classes it's already not feasible anymore.

nilern 2021-03-11T14:52:25.493900Z

Have you looked at java.lang.reflect.Proxy?

nilern 2021-03-11T14:56:06.494700Z

Probably it counts as defining a new class tho :thinking_face:

borkdude 2021-03-11T15:05:57.495800Z

> Therefore all dynamic proxy classes need to be generated at native image build time. Same kind of problem, but there you compile the image yourself, so the classes are adapted to your specific use case. In the above case it's a general use case.

nilern 2021-03-11T15:07:44.496500Z

Seems so

borkdude 2021-03-11T15:10:21.498800Z

So the main problem is false positives of instanceof towards the true side and runtime exceptions that result from it with "not implemented". E.g. when a data structure doesn't explicitly implement Counted you will get an exception for (count obj), because count thinks it's a Counted instance (it is, but it is not)

nilern 2021-03-11T15:13:13.001100Z

Clojure kind of relies on Java being Smalltalk in C++ clothing. When it becomes C++ with GC, you are going to have a bad time with reify etc.

2
borkdude 2021-03-11T15:14:44.001400Z

for sure

nilern 2021-03-11T15:18:10.002800Z

I guess you could patch clojure to replace instanceofs with something that has enough indirection but that does not feel very promising either

borkdude 2021-03-11T15:19:24.003500Z

oooh haha, I could patch the Clojure that goes into babashka's compiled artifact. Hmm, that would be a major hack.

borkdude 2021-03-11T15:19:59.004Z

but even if I would do that, I would have to patch any other library that is bundled with babashka as well, if they do manual instance checks. not going there

borkdude 2021-03-11T15:21:22.004800Z

it may all not be worth supporting reify with interfaces in bb. Protocols work relatively well since they have a smaller surface area

nilern 2021-03-11T15:23:31.006300Z

I wonder if these issues would just go away with Truffle. Even if they did, it would be a lot of work as well

borkdude 2021-03-11T15:23:55.006400Z

There is a Java on Truffle interpreter now that may allow for these things

borkdude 2021-03-11T15:24:10.006600Z

joinr did some experiments on getting the clojure compiler running. It's terribly slow though

borkdude 2021-03-11T15:24:20.006800Z

But this could change in the future maybe

nilern 2021-03-11T15:25:57.007200Z

> TruffleClojure: A self-optimizingAST-Interpreter for Clojure > Master’s Thesis

borkdude 2021-03-11T15:26:12.007400Z

yeah, I've seen that. the source code isn't available and it's based on a very old truffle version

nilern 2021-03-11T15:26:26.007600Z

Indeed

borkdude 2021-03-11T15:26:46.007800Z

I've been thinking of writing my own, but this will be another major project and it's not certain that this will solve most of these problems

borkdude 2021-03-11T15:29:17.008400Z

It has the same kind of limitation: > Java interoperability works in the native configuration but requires more setup. First, only for classes loaded in the image can be accessed.

lilactown 2021-03-11T15:30:32.008600Z

I'm not an expert in manifold, but what I would think to try first is putting all values from the generator on one stream, and then filter the one stream

nilern 2021-03-11T15:30:50.008800Z

I assumed that making a Ruby class that extends a Java class (that is in the image) would work

borkdude 2021-03-11T15:31:43.009Z

You're assuming that they implemented Ruby classes using Java class hierarchies, which doesn't have to be the case

borkdude 2021-03-11T15:32:59.009200Z

But maybe they do

nilern 2021-03-11T15:33:46.009400Z

The Ruby object model has more stuff but I it would not be very useful if you could not inherit from Java classes

ABeltramo 2021-03-11T15:35:15.009600Z

Thanks for your help! I don't know if I'm doing it wrong but filtering doesn't work for me. The first filter who discards messages will not propagate to the other stream.

(defn split-stream [condition stream]
  [(ms/filter condition stream)
   (ms/filter (comp not condition) stream)])
I tried something like this but it doesn't work. I guess I could have two streams but that will mean discarding valid inputs.

nilern 2021-03-11T15:36:00.009800Z

I sometimes think of making a new TruffleClojure when I get ahead of myself

borkdude 2021-03-11T15:36:03.010Z

Generating Java Classes at Runtime JRuby supports converting a Ruby class to a concrete Java class using become_java!. TruffleRuby does not support this. We recommend using a proper Java interface as your interface between Java and Ruby. That's also interesting.

borkdude 2021-03-11T15:36:44.010400Z

If they allow creating "reified" objects that implement interfaces at runtime though, then I want to know if that is possible at runtime in a native image.

ABeltramo 2021-03-11T15:37:11.010800Z

You can see a full working example on #aleph a value generator can be as simple as rand

1👀
borkdude 2021-03-11T15:37:39.011Z

I posted a question in their channel: > Does TR allow creating objects that implement Java interfaces at runtime, in a native-image? How is this achieved?

lilactown 2021-03-11T15:38:41.011300Z

when you say it doesn't work, what does that mean?

nilern 2021-03-11T15:38:48.011500Z

You could also just try it

ABeltramo 2021-03-11T15:39:21.011700Z

if I use two filter with a single stream and opposite conditions it loops forever.

borkdude 2021-03-11T15:39:49.011900Z

trying now

ABeltramo 2021-03-11T15:40:36.012100Z

Using two streams and consuming them I get the following warning:

WARN manifold.stream.default - excessive pending puts (> 16384), closing stream

ABeltramo 2021-03-11T15:40:47.012300Z

Which makes sense since the generator is infinite

borkdude 2021-03-11T15:41:41.012500Z

Hmm, where in the source do you indicate that you're implementing that interface though?

class EthylAlcoholFluidForce
  def getFluidForce(x, y, depth)
    area = Math::PI * x * y
    49.4 * area * depth
  end
end

nilern 2021-03-11T15:46:58.013200Z

It's weird, why can you compile TruffleRuby into a native image but not ye olde classloaders and bytecode JIT? I think Jikes RVM was more flexible than this 20 years ago...

borkdude 2021-03-11T15:48:55.013400Z

Ah what they do there: on the Java side they create something which implements the interface which then delegates to the Ruby methods

borkdude 2021-03-11T15:49:14.013600Z

this is what I also do with the reify trick, only the list of interfaces each object implements is too broad

nilern 2021-03-11T15:52:52.013800Z

Sort of like gen-class

nilern 2021-03-11T16:06:19.017800Z

Hmm. Pure Clojure libraries might not be too bad. Any interfaces that correspond to protocols can be skipped in favor of the extend-protocol mechanism (I assume you already do this) and instance? could be easily replaced. But with Java code some craziness like patching bytecode or Java on Truffle would be required.

borkdude 2021-03-11T16:07:45.018700Z

@nilern Yes, instance? can be replaced, but not for built-in libraries (without changing them) that are part of the babashka std lib

nilern 2021-03-11T16:11:21.019700Z

I meant replacing the Clojure instance? in addition to the sci one

borkdude 2021-03-11T16:13:25.020400Z

This still won't help for functions that call into clojure.lang.RT

borkdude 2021-03-11T16:13:55.020900Z

The surface area of some of these interfaces is just too big to start doing this, I think

nilern 2021-03-11T16:20:31.023400Z

I think there is no 100% solution. Like IFn does not have overloads for primitives beyond arity 4. In such situations I try to focus on making the tool useful and the limits clear.

borkdude 2021-03-11T16:27:35.024600Z

I realize there is no perfect solution, but I have to choose between making things work + unexpected bugs, or not support it at all E.g. (fn? (reify ILookup (valAt [this] :foo))) ;;=> true

borkdude 2021-03-11T16:29:23.025700Z

as long as the reified object has limited usage, this might be fine. I'm not entirely sure about the consequences if you actually call the above object as a function, you will get "not implemented" as a runtime error

nilern 2021-03-11T16:41:54.030300Z

I think it is better to have very little support or no support at all than unexpected bugs. But I am probably biased by the static typing literature where undecidability and especially unsoundness are to be avoided like the plague.

borkdude 2021-03-11T16:51:50.032100Z

Yes, I tend to agree with this

naomarik 2021-03-11T16:52:04.032500Z

hey everyone, been using windows forever and just got an m1 mac so I'm not tied at home. is everyone using the x64 adoptjdk8 for datomic/shadow-cljs?

nilern 2021-03-11T17:09:07.034400Z

I use OpenJDK from my package manager. I used to use https://brew.sh/ on Mac.

walterl 2021-03-11T18:29:36.036300Z

Is there anything that allows you to write Clojure[Script] code directly in <script type="text/clojure"> HTML tags? Like what Brython does for Python. https://brython.info/

borkdude 2021-03-11T18:35:26.036700Z

@clojurians-slack100 Possibly with self-hosted #clojurescript, Klipse (= self-hosted CLJS) or #sci

1👍
nilern 2021-03-11T18:36:32.037900Z

I may have seen a demo years ago. It would require self-hosted, which you usually don't want to ship

walterl 2021-03-11T18:37:14.038400Z

Why not? Size?

borkdude 2021-03-11T18:37:15.038600Z

This site uses #sci https://nextjournal.github.io/clojure-mode/ which is probably the smallest bundle size

borkdude 2021-03-11T18:38:09.039200Z

However, you can get better perf and bundle size if you just compile a .js file custom for your site

walterl 2021-03-11T18:38:38.039400Z

Of course 🙂

nilern 2021-03-11T18:39:18.039500Z

Yes and running the compiler on every page load would also be slow (and a bit weird)

1👍
2021-03-11T19:33:03.040600Z

Are there any Clojure webassembly projects or plans?

blak3mill3r 2021-03-11T19:41:31.041300Z

related but this is not compiling clj to wasm

blak3mill3r 2021-03-11T19:41:57.041900Z

I suppose GraalVM is getting us closer to this...

richiardiandrea 2021-03-11T21:58:47.043Z

Hi all, I seem to never remember if there is way to get a spec, say

(s/def ::event
  (s/keys :req-un [::id ::payload]))
and override only the ::payload when defining a specialization of it

alexmiller 2021-03-11T21:59:24.043300Z

(s/and ::event ...)

alexmiller 2021-03-11T21:59:49.043700Z

where ... might need to be a predicate that looked inside the map

richiardiandrea 2021-03-12T18:46:30.071100Z

FWIW the above would not work cause it does not have a :payload key

richiardiandrea 2021-03-11T22:01:05.043800Z

would that be the preferred route over a multi-spec (I have just recalled I could go that route)

richiardiandrea 2021-03-11T22:03:58.044Z

this is your solution implemented

(s/def ::file-id ::model.file/id)
(s/def ::file-deleted-payload
  (s/keys :req-un [::file-id]))

(s/def ::file-deleted-event
  (s/and ::event-bus/event ::file-deleted-event-payload))