Fork me on GitHub
#clojure
<
2022-08-05
>
didibus06:08:16

Any reason why Clojure lists are not double-ended and thus able to insert at the end quickly? Is it something to do with the persistent implementation of them? Or, I don't mean fully double-ended in that all node point both ways, but just why not keep a tail and a head?

hiredman07:08:07

It isn't possible to have an immutable linked list that you can update the tail of without copying the whole list

didibus07:08:50

What's the downside to the finger-tree? i.e., why not replace the core list with them?

didibus07:08:20

Oh, maybe it only does constant time read of the tail, but doing an append to the tail in constant time is not possible for persistent immutable data-structures, like hiredman said

octahedrion07:08:42

I think finger trees are constant time conj to either end

jumar08:08:18

It doesn't say much and I really don't know the matter, but the implementations look quite similar: https://github.com/clojure/data.finger-tree/blob/master/src/main/clojure/clojure/data/finger_tree.clj#L224-L230

(cons  [_ b] (deep (digit meter-obj x)
                       (EmptyTree. (finger-meter meter-obj))
                       (digit meter-obj b)))
  ConjL
    (conjl [_ a] (deep (digit meter-obj a)
                       (EmptyTree. (finger-meter meter-obj))
                       (digit meter-obj x)))
I tried to benchmark them and conj looks was about 3x faster than conjl. https://github.com/jumarko/clojure-experiments/pull/25

octahedrion09:08:40

(def dl (apply double-list (range 1000000)))
=> #'user/dl
(cc/quick-bench (conj dl -1))
Evaluation count : 22189998 in 6 samples of 3698333 calls.
             Execution time mean : 25.125505 ns
    Execution time std-deviation : 0.093653 ns
   Execution time lower quantile : 25.004565 ns ( 2.5%)
   Execution time upper quantile : 25.209793 ns (97.5%)
                   Overhead used : 1.901927 ns
=> nil
(cc/quick-bench (conjl dl -1))
Evaluation count : 32925162 in 6 samples of 5487527 calls.
             Execution time mean : 18.172871 ns
    Execution time std-deviation : 2.377633 ns
   Execution time lower quantile : 16.432850 ns ( 2.5%)
   Execution time upper quantile : 21.952353 ns (97.5%)
                   Overhead used : 1.901927 ns

Found 1 outliers in 6 samples (16.6667 %)
	low-severe	 1 (16.6667 %)
 Variance from outliers : 31.6430 % Variance is moderately inflated by outliers
=> nil
not much in it

andy.fingerhut13:08:15

I believe the big O run times of finger trees are good, but their constant factors hidden by the big O notation is larger in implementations, often by a factor that makes them less attractive for actual use

🙏 1
🖐️ 1
🌳 1
Joshua Suskalo16:08:15

Finger tree just looks like a generalized rope to me.

pinkfrog08:08:47

I have a single close-ch to issue closing signal. I remember that there is some limitation on the number of core.async go routines awaiting on a channel. What’s the number?

didibus09:08:06

Awaiting I don't think has any limits. There's a limit on how many puts

thheller09:08:32

I think there is only a limit on blocking takes/puts

didibus09:08:17

I think 1024 pending puts per channel is the limit

didibus09:08:24

If you get that issue, slow down your puts by having better back pressure handling, or use a bigger channel buffer, or use a dropping or windowed buffer to drop extra puts when consumer is too slow

thheller09:08:21

since the question was about close-ch puts are not the problem. just takes. assuming that closing the channel is the only "signal".

pinkfrog09:08:25

@U0K064KQV > There’s a limit on how many puts It is conceivable on the number of data that are put on to the queue of a channel. But on the other hand, isn’t the code itself (e.g.,alts) gets somehow transformed into some anonymous function and being put into some implicit queue waiting to be executed when conditions are met? If so, there shall be some limit too.

didibus16:08:32

You're right, form some reason I thought that they allowed it to be unbounded, but I checked the code, they apply the same limit of 1024 for takes as well. So you can't have more than 1024 pending puters or 1024 pending takers on the same channel

didibus17:08:55

It's only when the handler is blockable, but I think that's always the case

thheller17:08:17

hmm interesting. I thought it was only blocking ops but I guess not?

didibus17:08:24

Not from what I understand, I always struggle a bit to read the code though. But the blocking ops like >!! and <!! are implemented with the async take! and put! just wrapped in a blocking promise. It's the async put! and take! which >! and <! eventually calls that will throw an error if you try to put or take more than 1024 in the pending takes and puts queues. When the handler is not blockable?, the put! and take! will not add it to the pending takes and puts queues in the case the channel buffer is full and no immediate takers / puters we're available, so it's kind of dropped, but that's used from offer! and poll! where that's the expected behavior. Otherwise it's added to the puts and takes queues but only if they're not at 1024 elements already. If they are, it throws an error.

didibus17:08:37

And it seems it's the same for Clojure and Clojurescript

pinkfrog05:08:04

> which >! and <! eventually calls that Which part of code did you see >! get translated to put! ?

didibus05:08:49

But to be honest, I more relied on what it said here for that: https://clojure.org/guides/core_async_go > go just ends up calling put! eventually anyway, so there really isn’t a downside

wotbrew09:08:23

Is anyone aware of any reference projects (or community projects say on github) that are deploying jars to central / ossrh using a deps / tools.build setup?

wotbrew10:08:03

I am guessing https://github.com/slipset/deps-deploycan be used to deploy to ossrh (I tested with a snapshot and it seemed to work) but to fulfil the release https://central.sonatype.org/publish/requirements/ I get the feeling some custom tools build program would likely be necessary. Any pointers would be super useful 🙏

Joshua Suskalo14:08:13

This uses build.clj for building a jar, and deps-deploy to deploy to clojars

Joshua Suskalo14:08:54

It could be a reasonable starting place.

Joshua Suskalo14:08:29

The other requirements here I believe could be accomplished, you just have to deploy more artifacts than just the .jar, which could most easily be done if you're calling deps deploy manually from your build.clj. The only actual challenge as far as I can tell for the requirements on central is javadocs.

wotbrew17:08:04

Agreed, I played with using deps-deploy / pomegranate as a library in a custom tools.build program (to control for extra stuff in pom, dummy source & javadoc jars), sounds like I was on the right(ish) track with that.

Joshua Suskalo17:08:28

I suspect maven central might not like the dummy jars if they see that's what's happening.

Joshua Suskalo17:08:40

Is there a reason you have to publish to one of those repositories instead of clojars?

wotbrew17:08:13

I think its ok > If, for some reason (for example, license issue or it's a Scala project), you can not provide -sources.jar or -javadoc.jar , please make fake -sources.jar or -javadoc.jar with simple README inside to pass the checking. We do not want to disable the rules because some people tend to skip it if they have an option and we want to keep the quality of the user experience as high as possible. and uh, I am doing analysis explicitly for maven (i.e I have been told it has to be maven in this case), I understand these probs are solved for clojars :)

wotbrew17:08:37

To put a bit of meat on that, central is a better target for libraries to be consumed by other jvm languages (incl java) - due to the requirement otherwise for users to add clojars as a repository to their build tool (maven, gradle etc) even for transitives. That is the root of why this matters for some clojure projects. IUUC anyway.

wotbrew17:08:06

But yea, I wouldn't recommend it if clojars will do!

misha11:08:36

#{[] ()}
Syntax error reading source at (REPL:1:9).
Duplicate key: ()

#{() []}
Syntax error reading source at (REPL:1:9).
Duplicate key: []

misha11:08:31

probably because of:

(= [1] '(1) (seq [1]))
=> true

Ben Sless13:08:43

Those are Clojure's equality semantics. Things are equal by value and behavior, not strongly on type

danboykis14:08:35

I am looking for advice on what to use to write app logs in json. I am currently using logback + clojure/tools.logging for regular non-json logging, rewriting every log statement in my app is not a big deal if there's a nicer solution using something else.

Derek15:08:12

I’ve just integrated https://github.com/logfellow/logstash-logback-encoder into my logback config

❤️ 1
danboykis15:08:37

Cambium looks interesting

rolt15:08:23

is goes beyond logging, but i find https://github.com/BrunoBonacci/mulog 's approach interesting. Otherwise for logback logstash config: https://github.com/pyr/unilog is nice. It's a bit of a pain to add attribute via mdc though

javahippie16:08:43

I am trying to use JobRunr, a Java Library to schedule Jobs, with Clojure. The API expects me to pass a functional interface via Java Lambda Expression for the code that should be executed by the Job. I tried to reify the functional Interface, but internally they check it with isSynthetic() and throw an exception telling me that I have to pass a lambda expression. Is there any way I can create an object that fulfills this and passes the check?

if (!value.getClass().isSynthetic()) {
            throw new IllegalArgumentException("Please provide a lambda expression (e.g. BackgroundJob.enqueue(() -> myService.doWork()) instead of an actual implementation.");
        }

hiredman16:08:45

that is terrible

hiredman16:08:36

sounds like they do that because they only know how ti serialize lambdas? so you might be out of luck

javahippie16:08:00

I think that’s exactly it, they are serializing the lambdas for later use

😲 1
dpsutton16:08:40

can you make a java class that closes over and then invokes your passed in function?

javahippie16:08:12

@U11BV7MTK that would be a way. Found an issue in their GitHub, Kotliners seem to have the same issue. But I wanted to choose Jobrunr over Quartz because it is way easier to use, and with that tradeoff I think I will re-evaluate

hiredman16:08:33

no, because implementing an interface using reify or from java using implements doesn't set the synthetic flag on the class

dpsutton16:08:35

is serializing intrinsic to the operation or a feature you can opt out of?

javahippie16:08:18

I think the RAM scheduler might work, but I need persistent Jobs

dpsutton16:08:29

oh my god, are they essentially putting () -> Users.pruneUsers() into the db?

😂 1
hiredman16:08:04

they get the bytecode for the lambda and decompile it and then do something with that

hiredman16:08:36

there are bugs like https://github.com/jobrunr/jobrunr/issues/289 about the decompiler hitting bytecodes it doesn't understand

javahippie16:08:57

I could always create a Java Class which executes a passed Clojure function in a Lambda, did something like that before. But something tells me, that support of any languages on the JVM besides Java is not a priority here

didibus17:08:29

The extent at which static languages are willing to become dynamic always astounds me. They basically implemented a byte code based eval

ghadi18:08:39

java is highly dynamic, this is just a particular choice a lib/tool made

didibus19:08:55

We're probably in "arguing semantics" territory. But Java has no eval, so you can't just store some code as text in a DB, and dynamically load, parse and run it.

dpsutton19:08:01

That kinda describes clojure itself though right?

didibus21:08:29

> That kinda describes clojure itself though right? > What do you mean?

dpsutton21:08:45

the jvm quite handily dynamically loads, parses and runs code. A helpful library to do this is the Clojure jar

didibus22:08:11

Ya, the JVM is pretty dynamic of a runtime, but Java isn't as dynamic as the JVM.

didibus22:08:29

I mean, Clojure goes through crazy extent to be dynamic as well, all it has to do on top of the JVM, bundling a compiler, including a bytecode generator ASM, hooking up a nightmarish combination of class-loaders and all that. Being dynamic in itself is difficult. But when the effort is centralized in the language constructs, its better, because you end up with a better dynamic implementation. In Java, I see a lot of code-bases doing their own ad-hoc implementation of dynamism that is missing from standard Java. I think there is a famous quote about this... Found it, it's Greenspun's tenth rule: > Any sufficiently complicated C or Fortran program contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of Common Lisp. This Java lib seems to be an example of this to me.

💯 1
didibus22:08:58

> Other programming languages, while claiming to be simpler, require programmers to reinvent in a haphazard way a significant amount of needed functionality that is present in Lisp as a standard, time-proven base.

didibus22:08:50

> It can also be interpreted as a satiric critique of systems that include complex, highly configurable sub-systems.[3] Rather than including a custom interpreter for some domain-specific language, Greenspun's rule suggests using a widely accepted, fully featured language like Lisp.

hiredman22:08:12

you know guy steele worked on the java language spec and famously said something like "we managed to drag c++ programmers halfway to lisp"

didibus22:08:41

I encounter that a lot in practice. Because teams are always hesitant if I bring up Clojure. It's too dynamic, how can you understand anything of what the code is doing, there's no compile error, how do you know what has broken. But then, in 90% of all legitimate production Java enterprise app, you'll start to find Class.forName("..."), and other things that are to bring in more runtime dynamism/configuration to the system, which is similarly just as difficult to understand anything of what the code is doing and similarly bypasses all compiler errors as well. The downside is that, you're under the false impression this is not the case, and you've put in place no mitigating processes or safe-guards, unlike say in Clojure, where you accept the dynamism from the start, recognize its utility, and learned patterns and mechanisms to tame it and make it an asset.

💯 1
didibus22:08:45

The same is true for meta-programming as well, where you'll find pre-processors, code gen, and all that...

coby01:08:12

was there ever a workaround here? The Bugsnag java client does something https://docs.bugsnag.com/platforms/java/other/#sending-diagnostic-data.

javahippie06:08:48

I avoided the library in the end and used quartz instead. If really had to, I probably had tried to wrap the functionality in my own Java class

coby20:08:16

yeah I think I'll probably just end up using the REST API for this. Really unfortunate design decision 😕

Drew Verlee18:08:17

What jvm configuration should i consider for development when running a local server? For instance, i rarely set the heapsize, i'm not even sure what the default behavior is.

Drew Verlee18:08:35

this post has lot of information on how to set things https://ericnormand.me/guide/clojure-jvm#java-options

jumar18:08:01

You typically don't need to worry about it much. The default xmx is 1/4 of total ram so maybe decrease that a bit

👍 1
Joshua Suskalo18:08:41

Setting the max ram is a reasonably good idea on the JVM because the way it works is that it will take up as much memory as you give it in a sufficiently long-lived process, even if your program doesn't use much ram.

Joshua Suskalo18:08:31

what you can do is set a ram value that's higher than what you'll need, wait for it to do a long garbage collection cycle, see how much memory it uses after that, and then set your max heap size to that + a little wiggle room.

ghadi18:08:31

-XX:-OmitStackTraceInFastThrow <-- set this only in dev

☝️ 1
ghadi18:08:04

sometimes for NullPointer and ClassCast Exceptions the jvm throws away the stack trace. that option preserves the stacktrace

Drew Verlee18:08:51

for clojure deps would that look like this? :jvm-opts ["-XX:-OmitStackTraceInFastThrow"]

Joshua Suskalo18:08:13

yes, in a :dev alias or similar

👍 1
Joshua Suskalo18:08:31

Although for myself I usually just put it in the cider global options for my dir-locals in emacs

Drew Verlee19:08:40

And then what you using to "see how much memory" the jvm is using and that a garbage collection has run?

ghadi19:08:20

learn how to use JFR

👀 1
👍 1
ghadi19:08:42

Java Flight Recorder. It is an efficient ring buffer deeply hooked into the JVM

ghadi19:08:02

Data captured by JFR can be processed by tools, including "Java Mission Control" a nice free GUI

ghadi19:08:23

it will show you memory, gc, locks, IO, all sorts of stuff

ghadi19:08:42

you can also trigger a JVM to record a JFR session from outside a running JVM

Drew Verlee19:08:54

awesome, i'll look into it.

ghadi19:08:08

jcmd is the swiss army knife

ghadi19:08:29

type jcmd $pid_of_some_jvm and it will show you a bunch of diagnostic things you can do to the jvm

✔️ 1
👍 1
ghadi19:08:57

show a class histogram, start / stop / configure JFR, get the VM settings, dump thread stacks, etc.

👀 1
Kelvin21:08:38

TIL that Clojure does not like Unicode chars above \uFFFF:

(str "我们去西安吃" \u30EDD \u30EDD "面")
; Syntax error reading source at (REPL:480:26).
; Invalid unicode character: \u30EDD
(Bonus points to whoever gets what the sentence is saying)

didibus23:08:49

The error might be misleading, but \uFFFF syntax literal stops there, it doesn't support to have 5 entries, because it is actualy the literal for Java Char types, which are unsigned 16 bit integers. In fact, its better to think of Char as uint16 really.

didibus23:08:57

So unicode codepoints above that don't fit a Char, so they'll be represented in UTF16 style as two chars in a char array. Unfortunately, there's no good literal support for those. The easier way is this:

(Character/toChars 0x30EDD)

didibus23:08:40

But, the str function does not convert a char-array into its unicode representation as a string. It prints the array memory location instead. So you'll need to first convert the char-array into a string representation.

didibus23:08:06

(str "我们去西安吃" (String. (Character/toChars 0x30EDD)) (String. (Character/toChars 0x30EDD)) "面")
"我们去西安吃𰻝𰻝面"

didibus23:08:01

But you can make yourself a super simple util function, and it'll be super nice again like so:

(defn u
  [codepoint]
  (String. (Character/toChars codepoint)))

(str "我们去西安吃" (u 0x30EDD) (u 0x30EDD) "面")

❤️ 1
hiredman21:08:02

it is a jvm thing

hiredman21:08:36

strings are utf16 or whatever, so characters outside the bmp (if I recall correctly) are two code points not one

hiredman21:08:44

so you can put that character in a string literal

user=> "𰻝"
"𰻝"
user=> (count "𰻝")
2
user=>
and when you count it, it says 2, so you cannot represent it with a single character escape

Joshua Suskalo21:08:08

until jdk18 which moves to utf8

hiredman21:08:08

there have been jdk changes to change how strings are stored internally to try and reduce the resource usage, but the exposed public behavior (like the way count is up there) has to stay the same for backwards compatibility

didibus23:08:31

Are you thinking of: https://openjdk.org/jeps/400 ? It's a change to the default charset, like when reading/writing a file, it will default to UTF-8 encoding

❤️ 1
didibus23:08:56

It doesn't change char or Character or CharSequence or String, all of which are still using UTF-16.

didibus23:08:34

> A charset governs the conversion between raw bytes and the 16-bit char values of the Java programming language.

didibus23:08:12

The issue was that, the default charset is environment dependent. So if you didn't explicitly set it, running the same program in different places could misbehave, give you different results. So in jdk18 they are making it a constant, so it will UTF-8 as default no matter the environment it is running in. So you'll get consistent behavior.