clojure

New to Clojure? Try the #beginners channel. Official docs: https://clojure.org/ Searchable message archives: https://clojurians-log.clojureverse.org/
dpsutton 2020-10-16T03:13:01.062800Z

is there a way to add protocol implementations to a class instance? ie, extend a particular java.util.Date. with some protocol rather than any java.util.Date?

dpsutton 2020-10-16T03:16:14.063300Z

similar to extend-via-metadata but for arbitrary java class instances

svt 2020-10-16T06:23:45.065500Z

{:cluster-id nil, :app-id nil, :message-id nil, :expiration nil, :type nil, :user-id nil, 
:headers #object[java.util.HashMap 0x1af91489 {__TypeId__=fooservice.bar.events.EventMessage, 
x-jwt-payload={"exp":1573686594705,
                "iat":1573621794705,
                "session-id":"c06fbf0c-04bb-4da8-91f9-4c44d439ca7b"}}]}
I’m getting this response in a queue message header, and I want to get the x-jwt-payload from it, I’m using cheshire.core/parse-string to parse it but not happening getting error
#error {\n :cause class java.util.HashMap cannot be cast to class java.lang.String (java.util.HashMap and java.lang.String are in module java.base of loader 'bootstrap')\n :via\n [{:type java.lang.ClassCastException\n   :message class java.util.HashMap cannot be cast to class java.lang.String (java.util.HashMap and java.lang.String are in module java.base of loader 'bootstrap')\n   :at [cheshire.core$parse_string invokeStatic core.clj 209]}]\n :trace\n [[cheshire.core$parse_string invokeStatic core.clj 209]

2020-10-16T06:29:35.066800Z

parse-string takes a string containing json, that is a map

p-himik 2020-10-16T07:11:56.068900Z

(get-in m [:headers "x-jwt-payload"])

svt 2020-10-16T07:27:29.069100Z

Thanks @p-himik but that’s not working, I have tried (.get value key) and it worked for me

p-himik 2020-10-16T07:31:33.069300Z

Strange, Java maps support get.

svt 2020-10-16T07:32:42.069500Z

Does JavaHashmap supports get?

p-himik 2020-10-16T07:32:59.069800Z

It does.

p-himik 2020-10-16T07:33:38.070Z

user=> (def m (java.util.HashMap.))
#'user/m
user=> (.put m "a" "b")
nil
user=> (get m "a")
"b"
user=> (def x {:a m})
#'user/x
user=> (get-in x [:a "a"])
"b"

svt 2020-10-16T07:34:52.070200Z

let me try this

svt 2020-10-16T06:31:38.066900Z

How can I get x-jwt-payload than?

svt 2020-10-16T06:33:41.067100Z

The problem is it’s a java hashmap?

Endre Bakken Stovner 2020-10-16T06:34:26.067300Z

Thanks for all the replies so far. Perhaps I should explain my use-case. I want to write a highlighter (like https://github.com/benma/visual-regexp.el) for https://github.com/redplanetlabs/specter so that I can see what result my specter-expression returns in real-time. Then it would be neat if I could annotate every value in the data structure I want to query with line/column info. Then a specter highlighter could merely: 1. annotate every value 2. run specter as usual 3. pick out the line/column from the returned results afterward But if one cannot annotate keywords/numbers this approach won't work. I guess I can use the edamame wrapper-approach, but then I would need to convert all specter-queries for numbers/keywords into a specter-query for wrapper(number/keywords). If you can think of simpler strategies please advise.

2020-10-16T06:40:47.067700Z

No

2020-10-16T06:41:16.067900Z

Parse-string takes a string, any kind of map http://will.be an error

svt 2020-10-16T06:41:36.068100Z

So what’s the alternative?

Endre Bakken Stovner 2020-10-16T06:47:48.068400Z

I was unable to see how rewrite-cljs can help me with the above, but many of the linked to pages in their docs were 404s.

p-himik 2020-10-16T07:11:56.068900Z

(get-in m [:headers "x-jwt-payload"])

svt 2020-10-16T07:27:29.069100Z

Thanks @p-himik but that’s not working, I have tried (.get value key) and it worked for me

p-himik 2020-10-16T07:31:33.069300Z

Strange, Java maps support get.

svt 2020-10-16T07:32:42.069500Z

Does JavaHashmap supports get?

p-himik 2020-10-16T07:32:59.069800Z

It does.

p-himik 2020-10-16T07:33:38.070Z

user=> (def m (java.util.HashMap.))
#'user/m
user=> (.put m "a" "b")
nil
user=> (get m "a")
"b"
user=> (def x {:a m})
#'user/x
user=> (get-in x [:a "a"])
"b"

svt 2020-10-16T07:34:52.070200Z

let me try this

Endre Bakken Stovner 2020-10-16T08:12:32.071400Z

Is it possible to extend keywords and numbers in Clojure so they walk and quack like keywords/numbers but can have metadata?

vlaaad 2020-10-16T08:13:14.071700Z

no

vlaaad 2020-10-16T08:13:31.072Z

actually, yes 😄

vlaaad 2020-10-16T08:13:44.072200Z

but mostly no

vlaaad 2020-10-16T08:15:13.073800Z

err… There is no way to put metadata on top of individual numbers/keywords, but you can have a registry that allows registering metadata on keywords/numbers. Sort of like specs — they are identified mostly by keywords, and s/def is a way to assoc a spec to a keyword. This is not quite metadata, but maybe good enough for you? what’s your use case?

Endre Bakken Stovner 2020-10-16T08:16:35.074Z

https://clojurians.slack.com/archives/C03S1KBA2/p1602786333020000

2020-10-16T08:20:28.074600Z

So you could try an experiment like going into the clojure.lang.Keyword class defined in Java in Clojure's implementation, and adding a field to hold metadata for that, and methods to get and add metadata to a keyword, implementing the appropriate interfaces.

vlaaad 2020-10-16T08:21:16.074800Z

hmmm, so this is a sort of a visual exploration tool?

1✔️
vlaaad 2020-10-16T08:21:27.075Z

is it clj only, or clj/cljs?

2020-10-16T08:21:30.075200Z

But that might violate an assumption of keywords that is pervasive in Clojure -- that two occurrences of the same keyword are the same identical object in memory, to enable fast equality comparisons between keywords using identical?, i.e. JVM reference / pointer equality.

2020-10-16T08:22:19.075400Z

If you have the same keyword appearing two places in a text input, and you want different line/column metadata on each, they cannot be the same JVM object. The keyword without the metadata can be identical, but the keyword with the line/column metadata must be different objects.

Endre Bakken Stovner 2020-10-16T08:22:54.075600Z

It would be fun to play around with and see. I just want it to work for the limited use-case described earlier (when specter is navigating the data-structure)

2020-10-16T08:23:21.075800Z

Changing numbers to have metadata is also possible, but it might mean changing the Java implementation of most/all arithmetic operations, which are quite a few.

2020-10-16T08:23:43.076Z

You would end up with your own custom modified version of Clojure that no one else in the world was using.

1😂
Endre Bakken Stovner 2020-10-16T08:24:05.076200Z

Does not matter I think, because they should be identical. I can write it in whichever language is easier. Specter is a tool for navigating data structures, not code.

2020-10-16T08:24:35.076500Z

and likely, a custom version of Clojure that maybe one or two other people might ever want to use.

vlaaad 2020-10-16T08:24:44.076700Z

yeah I know

vlaaad 2020-10-16T08:25:41.076900Z

but how do you show highlights? not in the repl I assume, because there is only text?

2020-10-16T08:26:15.077200Z

For numbers, I suspect it would break the interaction with some third-party numeric libraries and your hacked version of Clojure.

Endre Bakken Stovner 2020-10-16T08:26:22.077400Z

Yes, I can show them in emacs or a SPA. I just want to write the backend first.

2020-10-16T08:28:38.077600Z

because some of those probably assume that Clojure's numbers are the classes that it use now, e.g. JVM's java.lang.Long, java.lang.Double, java.lang.Number, and your modified version could not add metadata fields to those classes, or at least I don't know a way without modifying the implementation of the JVM itself, which sounds like something I doubt anyone but the JVM implementers would be well-prepared to do in less than a year.

2020-10-16T08:29:34.077800Z

(The year there is a wild guess -- maybe you know a JVM implementer who likes you and can pull it off in a weekend for beers.)

1😂
vlaaad 2020-10-16T08:30:51.078Z

maybe your specter tool could, given a specter selection pattern, transform the values at that pattern with #(tagged-literal ’selected %), and your output panel can just show that?

1👍1👏1
vlaaad 2020-10-16T08:31:30.078300Z

or it can process the resulting data structure to highlight those?

2020-10-16T08:31:33.078500Z

The registry idea won't work for multiple occurrences of the same keyword in the data, without changing Clojure/JVM's Java code, because all Keyword JVM objects for :foo are the same JVM object in memory.

1🤯1✔️
2020-10-16T08:33:19.078900Z

That is also true for some integers with small absolute value, in many JVMs, as a memory optimization for commonly occurring small numbers.

2020-10-16T08:33:57.079100Z

And changing the implementation of the JVM itself is a step of difficulty beyond changing the Java code that implements Clojure.

2020-10-16T08:35:24.079300Z

"Any place is walking distance if you have the time." is a phrase I am reminded of in situations like this. The approach of trying to read standard Clojure data structures, but add metadata with line/col info for keywords and numbers, is a long walk away.

2020-10-16T08:38:00.079500Z

Creating a custom type that "wraps" an object that doesn't have metadata, so that the wrapper object has the metadata, and contains the original value, seems like an approach worth thinking about. You would need your own modified reader to read text and create those objects instead of what clojure.core/read or clojure.edn/read does. You would probably want your own custom print/display functions for that, which might be straightforward with Clojure's print-method -- not sure.

1👍
2020-10-16T08:39:10.079700Z

Or, I should back up and say, if you want to read a string representation of Clojure data that looks like EDN, and the result of reading it had all the line/col metadata, then you need a modified version of clojure.core/read.

Endre Bakken Stovner 2020-10-16T08:45:18.080Z

But specter can use user-defined functions to find not only numbers, but, for example, numbers greater than 5. These functions cannot understand my wrapped numbers easily, right?

Endre Bakken Stovner 2020-10-16T08:45:37.080200Z

That is the downside of the wrapping approach as I see it.

vlaaad 2020-10-16T08:47:33.080400Z

I think my suggestion with wrapping using tagged literal still holds..

borkdude 2020-10-16T08:50:39.081Z

@dominicm About LSP: Take a look at https://github.com/borkdude/clj-kondo.lsp which is an LSP implementation for clj-kondo, written in Clojure based on lsp4j

borkdude 2020-10-16T08:50:54.081400Z

It's used in VSCode. The gnarly interop code is heavily borrowed from clojure-lsp (I know see that's been mentioned already)

Endre Bakken Stovner 2020-10-16T08:51:20.081500Z

@vlaaad I will play around with your idea. To begin with I can create a limited version of the highlighter 🙂 But if there are multiple keywords named :a they can only have one place in the register, right? I cannot think of an easy way to tell these apart 🙂

2020-10-16T09:03:40.082200Z

This might be a very bad and unworkable idea, but off the top of my head it might be worth thinking of the registry idea, but instead of the registry pointing at a single line/column location for a keyword/number/whatever, it could point at an ordered list of locations.

1👍
2020-10-16T09:04:35.082400Z

I don't know your use case well enough to determine if it would help you do what you want.

Endre Bakken Stovner 2020-10-16T09:07:56.082600Z

That might help me get closer! Like if the result from specter is ["a", :a, "b", :a] And I have the location data of "a" and "b" I should be able to deduce the locations of the two :a s.

Endre Bakken Stovner 2020-10-16T09:12:32.082900Z

But if you have a dict [{:a "bla"} {:a "blo"} {:a "bli"}] and want to get every :a in every map with an odd index, you cannot tell them apart, really. But come to think of it, you cannot tell them apart in the regular specter result either really.

Endre Bakken Stovner 2020-10-16T09:24:17.083300Z

Now I understand more what you mean by transforming and tagging @vlaaad! Brilliant. That is what I should try.

1👍
erwinrooijakkers 2020-10-16T09:26:29.084500Z

hi what is an idiomatic name for an arbitrary data structure? e.g. in the context of:

(defn as-set [data] 
  (if (coll? data) 
    (set data) 
    data))

p-himik 2020-10-16T09:28:07.085300Z

vec calls its argument coll.

erwinrooijakkers 2020-10-16T09:28:10.085500Z

i think prefixing with a question mark like ?set instead of data is not so idiomatic, since that is mostly used in logic programming (datalog, core match or core.logic) for unbinded vars

p-himik 2020-10-16T09:28:41.085600Z

Same for set.

erwinrooijakkers 2020-10-16T09:30:13.085800Z

thanks i see - wondering now why this function as-set is necessary in the first place in this context actually

erwinrooijakkers 2020-10-16T09:30:23.086Z

why is set not good enough? 🙂

erwinrooijakkers 2020-10-16T09:30:41.086200Z

and then there’s probably a better name depending on the context

p-himik 2020-10-16T09:35:11.086700Z

Ah, wait - I somehow failed to notice the check for coll?. In this case, set is not good enough if data is e.g. 1.

Eloy Pazos Lema 2020-10-16T09:35:24.087200Z

hello guys, anyone here with experience with cider clojure?

p-himik 2020-10-16T09:36:13.087300Z

FWIW I would name such an argument maybe-coll. But I would try to get rid of the need for as-set altogether in the first place.

erwinrooijakkers 2020-10-16T09:38:08.087600Z

thank you

erwinrooijakkers 2020-10-16T09:38:12.087800Z

i agree

flowthing 2020-10-16T09:41:41.088Z

Probably plenty of folks — you might want to try the #cider channel, though.

Stefan 2020-10-16T09:49:41.091400Z

Hi! I have this function and I wonder if there’s a nicer way to write it. It looks something like this:

(defn determine-command [some-map]
  (or (when-let [arg ((comp :key2 :key1) some-map)]
        [#'get-using-method-1! arg])
      (when-let [arg ((comp :key4 :key3) some-map)]
        [#'get-using-method-2! arg])))
In other words: it returns a vector that says which command to use based on the structure of the input, but it also passes the thing that was extracted from the map to the output of the function. This function is easy to unit-test: I verify both the expected function to be used, as well as the expected arg. I don’t think this way of writing it is too bad, but I’m curious if there’s a better way. Thanks!

p-himik 2020-10-16T10:05:25.091700Z

LGTM Some minor things: - A nested if-some might look a bit better - There's a difference between when-let and when-some that sometimes might be important

p-himik 2020-10-16T10:05:57.091900Z

Ah, I would replace ((comp :key2 :key1) some-map) with just (-> some-map :key1 :key2). Or get-in.

1👍
Stefan 2020-10-16T11:02:11.092600Z

Thanks @p-himik, I wan’t aware of if-some and the difference between when-let and when-some, so that’s a very helpful reply!

hanDerPeder 2020-10-16T12:57:44.096200Z

core.async is great, but sometimes everything just stops because of some bug. in cases like these it would be really useful to be able to inspect the channels. how many callbacks (preferably named) are queued, etc. Any support for this?

borkdude 2020-10-16T13:43:33.097Z

why does clojure.java.shell/sh use a future for writing input? https://github.com/clojure/clojure/blob/fe0cfc71e6ec7b546066188c555b01dae0e368e8/src/clj/clojure/java/shell.clj#L119

alexmiller 2020-10-16T13:46:02.098100Z

because io can block?

alexmiller 2020-10-16T13:46:20.098300Z

that would be my guess anyways

dpsutton 2020-10-16T14:40:11.100Z

Got a PR that marks a constant as ^:const. I remember some issues cropping up over use of this. Is this something I should be wary of or is it pretty innocuous?

alexmiller 2020-10-16T14:53:01.100700Z

depends whether it's being used correctly :)

dpsutton 2020-10-16T14:53:17.101100Z

ha. that makes sense. i just don't know where to go to determine that

alexmiller 2020-10-16T14:53:35.101400Z

is it a constant compile-time value?

dpsutton 2020-10-16T14:54:14.101600Z

(def ^:const date-format
  "Standard date format for :type/Date objects"
  "m/d/yy")

(def ^:const datetime-format
  "Standard date/time format for any of the :type/Date variants with a Time"
  "m/d/yy HH:MM:ss")

dpsutton 2020-10-16T14:54:27.101900Z

yes it is. some date formats

alexmiller 2020-10-16T14:54:45.102300Z

seems fine then. doubt it really matters though

dpsutton 2020-10-16T14:55:45.103200Z

thanks. yeah i figured the benefits are absolutely marginal and possibly evaporate with jit magic eventually but was worried about foot guns.

alexmiller 2020-10-16T14:59:00.103500Z

I mean, I assume this being used in some kind of formatter object

alexmiller 2020-10-16T14:59:34.104200Z

depending on whether that's an old school Java formatter or a new school java.time formatter different advice...

dpsutton 2020-10-16T15:00:05.104900Z

its for excel cells using docjure

alexmiller 2020-10-16T15:01:27.106Z

in the former case, they are not thread safe so you either need to create the formatter every time (in which case, you're using an interned string regardless so it really doesn't matter). the trick is to use a threadlocal and create it once. in the latter case, they are thread safe so you want to ensure you just make it once (which is not a const but can be done in a defonce or something)

alexmiller 2020-10-16T15:02:20.107Z

if you're just literally passing in the string, then sure, but I seriously doubt it matters much

1👍
paul931224 2020-10-16T16:20:10.108100Z

Hello guys! Any library which I could use for putting a watermark on my images? It is for a webshop. I want to manage it on backend. Any help appreciated!

alexmiller 2020-10-16T16:38:21.109200Z

image import, manipulation, and export are all part of java2d in the jdk so you probably don't even need any additional library. can't say I know the magic code to do so but if you google for java2d watermark yada yada you can probably find something

1❤️
borkdude 2020-10-16T17:02:17.110300Z

Does anyone have a good idea how to solve the problem in general of an infinite stream of data copied to another thing, using clojure.java/io or similar? Repro:

(require '[<http://clojure.java.io|clojure.java.io> :as io])

(let [os (io/output-stream "/tmp/log.txt")
      out (io/writer os)]
  (binding [*out* out]
    (future
      (loop []
        (println "Hello")
        (Thread/sleep 1000)
        (recur)))))

(io/copy (io/input-stream "/tmp/log.txt") *out*)
This only copies the first line. I guess it makes sense since at the time the stream reaches the end of the file, it doesn't know more is coming. I'm running into this with https://github.com/babashka/process#piping-infinite-input

isak 2020-10-16T17:46:31.111Z

(with-open [fs (java.io.FileInputStream. "/tmp/log.txt")]
    (let [buf (byte-array 24)]
      (loop [bytes-read (.read fs buf)]
        (cond
          (= bytes-read -1)
          (do
            (println "Reached end of file, waiting...")
            (Thread/sleep 500)
            (recur (.read fs buf)))

          :else
          (do
            (println "Got some bytes")
            (.print *out* (String. buf 0 bytes-read))
            (recur (.read fs buf)))))))

borkdude 2020-10-16T17:48:45.111300Z

yep that works. I guess there is no general solution since you don't know if you're reading from some infinite thing or not, and at one point you'd like the thread to stop reading, if you're not dealing with something infinite

borkdude 2020-10-16T17:49:25.111600Z

In unix they have the pipe signal for this.

isak 2020-10-16T17:49:53.111800Z

Hmm yea I think I remember better abstractions for this on .NET, but not sure

cpmcdaniel 2020-10-16T17:52:55.113Z

With deps.edn and the Clojure CLI tools, is it possible to compile .java source files? (if necessary, I can explain why gen-class is not ideal in my scenario)

borkdude 2020-10-16T17:55:28.113200Z

No.

alexmiller 2020-10-16T17:55:50.113600Z

well no, with just those things. yes, with other additional tools

cpmcdaniel 2020-10-16T17:56:15.113900Z

hmm

borkdude 2020-10-16T17:56:42.114800Z

(clojure.java.shell/sh "javac" "Foo.java")

alexmiller 2020-10-16T17:57:08.115400Z

well it's javac, and there is more to it than that

alexmiller 2020-10-16T17:57:24.116Z

(like classpath)

alexmiller 2020-10-16T17:57:45.116300Z

support for this is coming in the upcoming tools.build

cpmcdaniel 2020-10-16T17:58:17.117Z

I will try to explain the situation then. Actually, this is kind of a fun side-project. I am trying to write a minecraft server plugin in Clojure. The problem is with how the classloader works in the server with it's plugins. Basically, the plugins use one type of classloader and the Clojure runtime uses another.

cpmcdaniel 2020-10-16T17:58:31.117400Z

so it's a chicken-egg problem if I use gen-class

cpmcdaniel 2020-10-16T17:59:20.118400Z

in the plugin initialization, I have to do this: https://github.com/cpmcdaniel/spittoon/blob/clj-rewrite/src/java/org/kowboy/SpittoonPlugin.java#L10-L13

cpmcdaniel 2020-10-16T17:59:41.119Z

or it just doesn't work. The Clojure code can't access the APIs from the server libraries

cpmcdaniel 2020-10-16T18:00:25.119900Z

afaik, when you use gen-class, your class actually loads the Clojure runtime, if it isn't already loaded

borkdude 2020-10-16T18:00:29.120Z

@cpmcdaniel You can get a classpath using clojure -Spath. Maybe combining that into a little bash script which calls javac works. There might be tools here which can automate that for you: https://github.com/clojure/tools.deps.alpha/wiki/Tools Else, use leiningen maybe?

alexmiller 2020-10-16T18:00:48.120300Z

some people have had success with badigeon with clj

cpmcdaniel 2020-10-16T18:01:26.120600Z

really wish I could get gen-class to work

cpmcdaniel 2020-10-16T18:02:23.121600Z

the chicken and egg happens because the resulting class needs to extend org.bukkit.plugin.java.JavaPlugin, which the Clojure runtime classloader can't find

cpmcdaniel 2020-10-16T18:03:12.122500Z

The server loads the plugin class, which would thus init the Clojure runtime, then it tries to resolve it's parent class, I guess

cpmcdaniel 2020-10-16T18:03:45.122900Z

anywho, lein is probably the least friction way to do this then

alexmiller 2020-10-16T18:04:34.123500Z

you might end up finding it easier to write a plugin stub in Java that invokes the Clojure runtime

borkdude 2020-10-16T18:05:11.124Z

@cpmcdaniel FWIW, this was some gen-class stuff I had to figure out because I never used it like this before: https://github.com/borkdude/sci/blob/ecb4cba114793c07566768ccc3abcdf9db5813e4/libsci/src/sci/impl/libsci.clj#L4

cpmcdaniel 2020-10-16T18:13:37.125500Z

@alexmiller make the Java plugin class it's own library... it could then use plugin config to know which namespace to load.

2020-10-16T18:14:32.125800Z

is core.async applicable here?

2020-10-16T18:15:06.126Z

can put lines of input on a chan

2020-10-16T18:15:43.126200Z

a half-baked observation, but that’s my goto for managing e.g. file resources that you want to stream

borkdude 2020-10-16T18:19:02.126400Z

@potetm My use case is making something like https://github.com/babashka/process#piping-infinite-input work, but I guess there can't be a general solution without knowing the source

borkdude 2020-10-16T18:20:33.126800Z

(I mean to link to the piping infinite input section)

borkdude 2020-10-16T18:33:43.127800Z

I have this record:

(defrecord Process [proc exit in out err args]
  clojure.lang.IDeref
  (deref [this]
    (wait this)))
When I want to print one, I get the error:
Error printing return value (IllegalArgumentException) at clojure.lang.MultiFn/findAndCacheBestMethod (MultiFn.java:179).
Multiple methods in multimethod 'print-method' match dispatch value: class babashka.process.Process -&gt; interface clojure.lang.IDeref and interface clojure.lang.IRecord, and neither is preferred
When I add:
(prefer-method print-method Process clojure.lang.IRecord)
I get:
Syntax error (IllegalStateException) compiling at (babashka/process.clj:50:1).
Preference conflict in multimethod 'print-method': interface clojure.lang.IRecord is already preferred to class babashka.process.Process
:thinking_face:

borkdude 2020-10-16T18:37:11.128Z

Oh I see:

(prefer-method print-method clojure.lang.IRecord clojure.lang.IDeref)

borkdude 2020-10-16T18:37:54.128500Z

hmm, that's not great to have in a library, since this will override people's preferences for printing things?

zhuxun2 2020-10-16T18:38:47.129200Z

Is there a solution like core.async, but can go across JVMs?

zhuxun2 2020-10-16T18:39:19.130Z

Currently I'm thinking using Redis lists, but is there good alternatives?

Ben Sless 2020-10-16T19:07:01.130500Z

There's this abandoned project, you could revitalize it https://github.com/halgari/com.tbaldridge.hermod Also https://github.com/democracyworks/bifrost and https://github.com/suprematic/otplike

vlaaad 2020-10-16T19:11:22.130700Z

perhaps if you define custom print-method specifically for Process there won't be any conflicts

2020-10-16T19:20:05.131800Z

You could always use a message broker like rabbitmq or artemis

borkdude 2020-10-16T19:25:03.132600Z

Good idea 💡

2020-10-16T19:41:01.135600Z

Here is an intriguing one! What is meant exactly in (doc sequence) by “..Won’t force a lazy sequence..“? The following example reveals a lazy seq input to sequence which is realized, against my expectations:

(defn lazify [[x &amp; xs :as coll]]
  (lazy-seq
   (when (seq coll)
     (cons x (lazify xs)))))
(def my-lazy-input (lazify [1 2 1 3 1]))
(type my-lazy-input)  ;; =&gt; clojure.lang.LazySeq
(type (rest my-lazy-input)) ;; =&gt; clojure.lang.LazySeq
(def not-lazy (sequence (comp (map inc) (distinct)) my-lazy-input)) 
(type not-lazy);; =&gt; clojure.lang.LazySeq  so far so good!
(type (rest not-lazy)) ;; =&gt; clojure.lang.ChunkedCons Ho HUh???
(type (rest (rest not-lazy)));; =&gt; clojure.lang.ChunkedCons WHERE'S my lazy stuff??
What am I missing? How do I prevent chunking? Thanks!

alexmiller 2020-10-16T19:45:18.138200Z

there are several overlapping topics here, not really sure which part you're already familiar with or care about

dpsutton 2020-10-16T19:46:03.139600Z

final public class ChunkedCons extends ASeq implements IChunkedSeq{

final IChunk chunk;
final ISeq _more;
the fields of a ChunkedCons can give some insight here why it still qualifies as lazy

2020-10-16T19:46:07.139800Z

@alexmiller - I simply want to prevent evaluation other than the front element

alexmiller 2020-10-16T19:46:19.140Z

Clojure generally does not guarantee when lazy element will be realized

alexmiller 2020-10-16T19:46:32.140300Z

if you want that level of control over realization, don't use lazy seqs

alexmiller 2020-10-16T19:46:49.141Z

(use loop/recur or reduce, etc)

2020-10-16T19:46:51.141200Z

OK Thank you Alex. So I simply need to roll my own..?

alexmiller 2020-10-16T19:47:21.142200Z

what is your actual problem? why are you trying to avoid realization? are there side effects like io?

2020-10-16T19:47:50.142900Z

No side effects - suppose each element take 1/2 hour to eval. I want to do as little as possble.

2020-10-16T19:48:18.143400Z

No, I am just learning 🙂 No io yet, but expensive computation

alexmiller 2020-10-16T19:48:47.143800Z

then use loop/recur to process each element and decide whether to process the next one

2020-10-16T19:49:22.144600Z

OK…so I just return a lazy-seq when pausing?

alexmiller 2020-10-16T19:49:38.144900Z

why a lazy seq at all?

alexmiller 2020-10-16T19:49:48.145300Z

what's pausing?

2020-10-16T19:49:57.145500Z

To prevent an unrequested computation.

2020-10-16T19:50:21.146500Z

The same reason I would be using a lazy seq…or (map inc range) etc

alexmiller 2020-10-16T19:50:26.146700Z

how do you when it's time to do the next one? there's not enough problem here to answer this well

2020-10-16T19:51:03.147500Z

The client code dictates…sorry if I am not clear.

2020-10-16T19:53:02.151Z

Concretely, I am generating stuff that may be, or is time consuming for each element. Just the same as having an infinite stream of elements that take e.g. 1/2 hour to process. If I pass that stream to (sequence ….) I would expect it not to eval more than one single element at a time, on request, right?

2020-10-16T19:53:51.152Z

…At least if my input is truly a lazy seq I would think…

alexmiller 2020-10-16T19:54:06.152300Z

it sounds like something like this would be useful: https://clojure.atlassian.net/browse/CLJ-2555 which is coming to Clojure, probably in 1.11

2020-10-16T19:54:53.153200Z

Will check it out. In the mean time, hand rolling my task works for me. Thank you!

alexmiller 2020-10-16T19:56:04.153500Z

or maybe it's simple enough to handle with something like iterate

isak 2020-10-16T19:58:48.155100Z

Isn't the problem that if he transforms the seq with sequence, it also changes how lazy the resulting sequence becomes? (E.g., if he did (sequence (map inc) (iteration ...)) he would still have the same problem

alexmiller 2020-10-16T19:59:54.155400Z

all of these things depend what you use them with in combination

2020-10-16T20:00:54.156500Z

if you want to limit speed of consumption for performance reasons, I think a queue fits better than laziness, thanks to things like chunking

2020-10-16T20:03:38.157700Z

Sounds like good advice - thank you all!

borkdude 2020-10-16T20:28:25.158100Z

Hmm, it works with a slight patch on io/copy:

(defn copy [in out opts]
  (let [buffer (make-array Byte/TYPE (buffer-size opts))]
    (loop []
      (let [size (.read in buffer)]
        (when (pos? size)
          (.write out buffer 0 size)
          (.flush out)
          (recur))))))

1
borkdude 2020-10-16T20:28:36.158300Z

so when I flush, the output will be visible

2020-10-16T20:46:20.160100Z

@alexmiller https://gist.github.com/KingCode/8970fe4e2308127ba467ac7f57d3f78b - very convoluted, but it works. Thank you for your advice and comments.

dpsutton 2020-10-16T20:49:43.160400Z

can you show a sample consumer of this?

2020-10-16T20:50:21.160900Z

You mean other than what’s in the gist?

2020-10-16T20:50:41.161400Z

I would have to make something up, since my true usage is more complex.

dpsutton 2020-10-16T20:51:52.163300Z

> Clojure generally does not guarantee when lazy element will be realized with this advice, i still think you're going to trip up at some point

2020-10-16T20:52:02.163700Z

as a general rule, preventing chunking is not the right solution for controlling timing of side effects / long running tasks

dpsutton 2020-10-16T20:52:11.164100Z

^

2020-10-16T20:52:22.164500Z

But the entire motivation rests on the need / desire to avoid consuming more than the front element. Suppose that in my example the thread is sleeping before outputting a result..

2020-10-16T20:52:37.165100Z

laziness is the wrong solution for this

2020-10-16T20:52:40.165200Z

I am not trying to control any timing…just the trigger

dpsutton 2020-10-16T20:52:52.165600Z

i think you're stuck in a box with the phrasing "consuming the front element"

2020-10-16T20:53:00.166100Z

I have no control over the timing, but I do over when to ask for it..

2020-10-16T20:53:22.167100Z

Isn’t this what laziness is about? The front element, and only when requested?

dpsutton 2020-10-16T20:53:22.167200Z

right. and a loop or a reduce with reduced might be far better

2020-10-16T20:53:28.167500Z

@kingcode if realizing 10 items instead of 1 when calling first is an error, then what you are doing is controlling timing

dpsutton 2020-10-16T20:53:30.167600Z

not in clojure.

2020-10-16T20:53:56.168200Z

But I am <not> trying to realize 10 items! Only one 🙂

dpsutton 2020-10-16T20:54:14.169100Z

yes. and the constant refrain has been tucking that behind a lazy sequence is not a good way to go

2020-10-16T20:54:17.169400Z

right - but chunking means you can't control that

2020-10-16T20:54:23.169600Z

loop/recur and reduce are eager, precisely the opposite of lazy !?

2020-10-16T20:54:35.170300Z

And why I don’t want chunking ! 🙂

dpsutton 2020-10-16T20:54:41.170600Z

you control the iterations in both of those. consume as many or as few as you like

2020-10-16T20:54:50.170900Z

if chunking causes errors, don't use laziness to control execution

2020-10-16T20:55:05.171600Z

ok ok.. so a qeue then?

2020-10-16T20:55:17.171900Z

use: doseq, loop, reduce, or run!, these will all act on a single item at a time, guaranteed

2020-10-16T20:55:29.172200Z

or use a queue (which you then consume from eg. a loop)

2020-10-16T20:56:25.172900Z

Hmm… I am trying to understand how reduce/loop would prevent eager execution though. Sorry if we’re going circles with my question

2020-10-16T20:56:49.173500Z

a reduce or loop can wait on some external condition before realizing the next element

2020-10-16T20:58:22.175400Z

Ah, I didn’t know this about waiting within reduce/loop. Other than within a transducer, how would you wait on an external condition in reduce? Other than returning the accumulator untouched? Or in loop?

2020-10-16T20:58:52.175900Z

you can use a queue with an atom, or use a core.async channel

2020-10-16T20:59:30.176900Z

the manifold library has some constructs for these things too

2020-10-16T21:00:00.177500Z

@noisesmith sure, but I would prefer an immutable situation, and going to something heavy as core.async/manifold seems overdone?

2020-10-16T21:00:21.177900Z

What is wrong with what is in the gist, other than being hand-rolled?

dpsutton 2020-10-16T21:01:18.179900Z

because you are attempting to prevent over-realization of a lazy seq with disastrous performance implications and > Clojure generally does not guarantee when lazy element will be realized

2020-10-16T21:01:19.180100Z

Plus, my external condition is the client calling my stuff

2020-10-16T21:01:21.180200Z

you still have to manage consumption of the result - either you have a closure that is cut off from the rest of your code, or a top level memory leak

2020-10-16T21:01:52.180900Z

with a closure you've punted the same problem you already have: how do you control when the next item is consumed, and how it's delivered to the thing that needs it

2020-10-16T21:02:01.181300Z

that's not a problem that lazy-seqs can solve

2020-10-16T21:02:31.182Z

(without a memory leak)

2020-10-16T21:03:38.184500Z

Hmmm….I agree entirely about the memory leak issue, but my lazy-seqs are all small, and will be garbage collected after use. My issue is generally purely performance.

2020-10-16T21:04:07.185700Z

In other words, I am lazy-seq’ing to control evaluation

2020-10-16T21:04:16.186300Z

"they will be garbage collected" how and when? if they are not a top level binding, they are closed over, if they are closed over there are better ways to use the values than wrapping in a lazy seq

2020-10-16T21:04:23.186700Z

while providing sequences to my client

Daniel Stephens 2020-10-16T21:04:42.187200Z

sounds like a sequence of delay or no arg functions that the client can then realise/call when they need the answer would be one option

2020-10-16T21:05:08.187800Z

I may be out of my depth on subtleties involved here, but queues are one of the simplest of all the mutable thing that exist, perhaps?

2020-10-16T21:05:28.188400Z

right, and they are made for precisely this kind of situation

2020-10-16T21:05:43.188700Z

OK…

2020-10-16T21:07:21.190600Z

Now, suppose you are generating nodes within a DFS search tree, and each of these nodes have children generated as lazy seqs, and consuming on demand during traversal. You want the nodes’ and their children to be consumed on demand, one at a time, but still be seqs, not queues?

2020-10-16T21:08:24.192300Z

the queue isn't the data, it's a governor that lets you control the timing of consumption (something that clojure laziness emphatically doesn't provide)

dpsutton 2020-10-16T21:08:27.192500Z

the return value of a DFS and the internal stacks or queues used for traversal don't have to coincide

2020-10-16T21:10:15.194400Z

Concretely, I am providing the sequences to a clojure zipper.

2020-10-16T21:10:41.195500Z

@kingcode the typical shape: a queue that gets all the inputs (from your search tree or whatever), a queue that gets the results, and N processing loops in the middle, all reading from and writing to the same pair of queues, that N controls the parallelism / rate of consumption

2020-10-16T21:11:02.196800Z

So I was hoping that the nodes not traversed are left un’evaled

2020-10-16T21:11:12.197300Z

you can even have recursion, where one of those workers puts more input back into the incoming queue

2020-10-16T21:11:45.198800Z

This may be the wrong time to toss this in, but there is an unchunk function that has been used in a few contexts like this that I believe correctly unchunks a lazy sequence? I believe that even then, you will not get 100% ironclad promises that it will never realize even 1 more element than you ask for, in all situations.

2020-10-16T21:11:57.199200Z

I wouldn't make any assumptions about the eagerness of a zipper consuming nested lazy structures

2020-10-16T21:12:55.200800Z

@andy.fingerhut and unchunk isn't in core because these sorts of constructs still end up being buggy, even if you unchunk

2020-10-16T21:13:28.201800Z

Hmmm. queues processing loops and unchunking sound like more complications than a simple lazy seq of nodes…. but thanks for your advice, I will try to digest all of this 🙂

2020-10-16T21:14:07.203400Z

Here is one way of writing an unchunk function, that helped avoid at least some level of evaluating too far ahead in a math.combinatorics library function. Again, if you want 100% guarantees in all cases that it will prevent evaluating even one more element, I don't think it provides such guarantees: https://github.com/clojure/math.combinatorics/blob/master/src/main/clojure/clojure/math/combinatorics.cljc#L214-L224

2020-10-16T21:14:08.203500Z

@noisesmith I agree about zipper eagerness, will have to investigate

Daniel Stephens 2020-10-16T21:14:31.204400Z

I think in the DFS example each child is quick to find, so the fact that a few in a chunk get realised eagerly is probably not going to cause an issue, it only sounds like an issue where each next item is a big performance hit.

alexmiller 2020-10-16T21:15:10.205300Z

imo, unchunk is a smell that you're using lazy seqs when you cannot accept their constraints (and thus should be doing something else)

1💯
2020-10-16T21:15:11.205600Z

if you need to control the timing of realizing elements, lazy seqs are not actually simple - they are not made for task management, and if you need to control things that strictly (because of the expense of some computation) they are the wrong abstraction

2020-10-16T21:15:37.206200Z

It all depends on what is being done to generate your DFS child…in my case it could be expensive 🙂

2020-10-16T21:15:39.206300Z

that's what I've been trying to say, but better articulated

2020-10-16T21:16:44.207400Z

@noisesmith ok, I will try to find a way to do it right and still consume computations as a seq.

Daniel Stephens 2020-10-16T21:16:53.207700Z

ahh, sorry, had misunderstood the example 😊

2020-10-16T21:17:08.208200Z

another thing to consider: if you have a cheap input but expensive processing step, you can use run! on the input lazy seq, and write to a blocking queue for each result

2020-10-16T21:17:52.209200Z

then the reader controls the workload

2020-10-16T21:17:54.209300Z

Ok sure…but blocking queues and run! make me think “architecture”, when I just have a very small library

2020-10-16T21:18:55.210100Z

I will start with hand-rolling for now as in the gist, and look for a better way to do exactly what I need.

2020-10-16T21:19:11.210500Z

Thanks 🙂

2020-10-16T21:19:33.211Z

either it's safe to pretend the calculation is side effect free (so chunking doesn't matter), or you need to control execution, and side effects are in fact your primary concern

2020-10-16T21:19:47.211200Z

I don't see a third option?

2020-10-16T21:20:15.211700Z

I won't deny it is a code smell. I don't think anyone had the strong interest of reworking the implemetation of the affected math.combinatorics function in a more significant way to avoid the out of memory issues that were occurring before unchunk was added to it.

2020-10-16T21:20:29.212200Z

No side effects ! Just expensive values, and potentially a lot of bactracking.

2020-10-16T21:20:43.212500Z

an expense is a side effect

2020-10-16T21:20:49.212700Z

The only third option I see is the gist :;

2020-10-16T21:21:06.213Z

Good point..

2020-10-16T21:21:25.213400Z

that only displaces the problem, because the consumers of your lazy value have to deal with the same issues you do

2020-10-16T21:22:30.214400Z

yeah - in that case it's kind of a corner case where you want the combinatoric function to be side effect free, but the memory consumption became a side effect

2020-10-16T21:23:14.215300Z

So indeed I need to control execution of expensive (and potentially a lot of) computations producing immutable values.

2020-10-16T21:24:07.216200Z

OK…indeed if it comes to that, then definitely architecture becomes a concern.

2020-10-16T21:24:42.216600Z

Thank you @noisesmith and all

2020-10-16T21:59:43.217Z

Well, I wouldn't go and call an expansive operation a side effect

2020-10-19T15:31:02.341700Z

Thx @didibus, well noted and interesting stuff. Some of my initial confusion had to do with lazy-seq doc’s mention that “..[it] will not force a lazy seq..”

2020-10-19T19:26:02.342100Z

@kingcode Which doc exactly says that?

2020-10-19T20:29:45.342500Z

I see that the doc string for the function sequence contains that phrase, but not the doc string for lazy-seq

2020-10-20T03:27:05.353Z

Ooops sorry, indeed I meant sequence. I was feeding it a true lazy seq, but in spite of “..will not force ..“, got the ChunkedCons version at the other end.

2020-10-20T16:45:39.365900Z

returning a chunked collection and not forcing the input are entirely compatible

2020-10-20T16:45:54.366100Z

it doesn't force the first chunk until you realize an element

2020-10-20T16:46:37.366300Z

as discussed previously, if realizing 1 element at a time vs. realizing 32 at a time actually matters, laziness is the wrong abstraction to use in clojure

2020-10-20T21:29:33.370300Z

got it 👍:skin-tone-5:

2020-10-20T21:36:59.370500Z

Ya, or at least, not an easy one to use as such, cause you'd need to be super careful that you only use things that keep the seq as a lazy-seq, and operations over it that don't ever realize one more than you want. Which I think Clojure would make no guarantee that those assumptions would hold in the next minor version bump of Clojure itself. So ya,all in all, better to avoid for those use case. Delay is way way better, or use core.async channels, agents, something custom, etc.

2020-10-20T21:38:42.370700Z

That said, I've had use cases before that were IO, and variable laziness was fine. For example, I have a lazy-seq backed AWS SQS queue abstraction. SQS itself returns variable length batches of messages, if you use the bath API, and so for that it works really well for me.

2020-10-16T22:00:43.217400Z

But that doesn't mean it isn't a relevant consideration

borkdude 2020-10-16T22:15:27.217700Z

So, it just turns out to be a buffering issue

1
borkdude 2020-10-16T22:15:48.217900Z

Explanation here: https://github.com/babashka/process#piping

2020-10-16T22:33:13.218400Z

the consumption of resources (and the way that effects program correctness) is a side effect

2020-10-16T22:33:30.218600Z

and CPU power is one resource

2020-10-16T22:35:06.218800Z

I mean, that's not the normally accepted definition of side effect. If you want it to mean that sure

2020-10-16T22:35:28.219Z

Its just quite confusing to use it like that to most people I feel.

isak 2020-10-16T22:52:38.221100Z

@kingcode Have you looked into core.async? That should let you write very similar code to what you wanted. And I don't think Rich is going to wake up tomorrow and decide he is going to start chunking puts/takes on channels

2020-10-16T23:17:19.221200Z

Agreed that it is a use of the term you typically won't find in a functional programming course or talk.

2020-10-16T23:18:25.221400Z

It certainly can be "an event you are trying explicitly to avoid happening, even in code that is otherwise dealing only with pure functions"

1👍
2020-10-16T23:33:45.225900Z

Thanks @isak, I have thought some more about my problem and from advice given earlier, using regular sequences and wrapping my tasks in delays should do the trick...indeed chunking is a good and necessary thing. Channels are cool but not what I need in this case - thanks!

1
2020-10-16T23:39:26.229300Z

I wasn’t thinking of it that way but it certainly can be, at least indirectly, e.g. by virtue of the domino effect if you lose a user/customer because of an unmitigated concern.