This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-08-31
Channels
- # aleph (37)
- # babashka (23)
- # beginners (46)
- # calva (1)
- # catalyst (12)
- # cider (3)
- # circleci (5)
- # clj-kondo (8)
- # clojure (188)
- # clojure-europe (28)
- # clojure-nl (1)
- # clojure-norway (84)
- # clojure-sweden (2)
- # clojure-uk (1)
- # clojurescript (6)
- # clr (1)
- # cursive (4)
- # datahike (4)
- # datascript (7)
- # datomic (31)
- # deps-new (16)
- # emacs (4)
- # fulcro (4)
- # gratitude (17)
- # hyperfiddle (24)
- # introduce-yourself (4)
- # jobs (5)
- # off-topic (84)
- # pathom (10)
- # polylith (21)
- # portal (6)
- # re-frame (6)
- # reitit (4)
- # releases (1)
- # sci (74)
- # specter (3)
- # tools-build (3)
- # tools-deps (5)
Last night to deal with sleep deprivation I translated https://github.com/weavejester/progrock into Java https://github.com/bowbahdoe/progrock/ and I have some...lingering thoughts
There are a lot of ways that I did things differently that I think give way to probably a larger comparison of "the way clojure works" to "the way java works"
1. "The progress bar is just a map of data:" >
{:progress 0, :total 100, :done? false, :creation-time 1439141590081}
In clojure, the progress bar's data is a data structure. It has a definite shape, but that shape is growable and is directly exposedI could have done something similar
public record ProgressBar(
int progress,
int total,
boolean done,
long creationTime
) {}
public final class ProgressBar {
final int progress;
final int total;
final boolean isDone;
final long creationTime;
because, specifically, I thought I might want to refactor to java.time.Instant
in the future
whereas in Clojure you can just expand the contract of the function to include both, the long creationTime
in a record is a contract that a long
is going to be stored
but also, for reasons I can't articulate too too well, I thought of a "java consumer" in a different way than a "clojure consumer"
> "Render a progress bar as a string. Takes an optional map of profiles, which > connects state keywords to maps of display options. The following display > options are allowed: > > :format - a format string for the progress bar > :length - the length of the bar > :complete - the character to use for a completed chunk > :incomplete - the character to use for an incomplete chunk
> (def default-render-options > "A map of default options for the render function." > {:length 50 > :format ":progress/:total :percent% [:bar] ETA: :remaining" > :complete \= > :incomplete \space})
because in my brain I was thinking "well, complete could be a String if its only one displayable character"
public final class RenderOptions {
public static final RenderOptions DEFAULT = new RenderOptions(
50,
":progress/:total :percent% [:bar] ETA: :remaining",
'=',
' '
);
final int length;
final String format;
final char complete;
final char incomplete;
and did the whole dance with a builder...just so I could change char complete
to String complete
(maybe to put an ascii escape sequence in there...but it should stay a single displayable character - but thats a property of the output format, which is hard to determine)
and all these choices I made the Java version strictly less flexible - I hide the data
so idk @U05291MNLRH if this is relevant to you at all
its not as simple as "oh dynamic typing good" - progrock can definitely be a core.typed library or even have been a typescript library without changing much at all about its construction
but this exact scenario might be indicative of why we tend to end up with one library for a task that's "complete"
As Rich has said, when you move from Java to Clojure, it’s like someone has been standing on your foot for years and they just stepped off it

I find using Datomic vs sql to be similar in terms of freeing you up in making commitments about the data model
Cool and thoughtful experiment! Thanks for sharing it! (And hope you finally got some sleep!).
I guess in Java instead of hiding you could add interfaces when you don't want to commit to any specific type? Or just Object?
you could use generic collections (like HashMap) or Object but everything in the lang drives you towards making types, which are closed and fixed at the point of conception (unless you change all your code)
I think using HashMap and generic collections yes, java will drive you off that and into closed types with class and records for entities, but for the problem of comiting to specific types for fields (what I think this was about) interfaces are natural in java
the problem with interfaces is - they DO add abstraction, but they also increase code size. the abstraction affordances in Clojure instead tend to decrease code size. this makes all the difference over time
interesting, can you expand a little more on that?
if you make an interface in Java, you have to take the method(s) you have and package them up into a new interface class (100s more lines of code), then create multiple instances which extend the interface (100s more lines of code), etc. Combine that with factories to make the instances ... more code. vs Clojure you first should be working with the data directly, you may need no "interface" at all, it's just keys in a map that return different things. if you're moving up to functions as a point of abstraction, then function invocation (particularly with map opts) doesn't need anything "extra" - you might just take a function with a known shape. If you want open extension, you do have a bit more work with either multimethods or protocols, but (thanks to macros), that can also be made smaller, if it's worth the time to do it.
You can write some standard Clojure code and get things working and then if you really want to work on it, making it more robust, more generic, more reusable - I find that process usually results in code that is smaller. Doing the same process in Java usually tends to make things bigger, often much bigger. It is not uncommon to see 10:1 or even 100:1 Java to Clojure code comparisons. Its astonishing to me how much (by volume) of Java code is just "maps of attributes" once you can see it that way, and it would be better that way.
yeah that I agree and I'm all in for open map entities for information systems, I was focusing on this particular example and the problem of having to commit too early to some concrete type for a field. I don't think you should put interfaces everywhere in java either, but when you don't feel commiting to a concrete type for a field, that is pretty straight forward with interfaces and not much verbose if you do small interfaces
in Clojure if you are returning a map with a key pointing to something you are not sure you wan't to change tomorrow you have the option of creating a protocol and a type for it, or a set of functions to make working with that key opaque so you can change it tomorrow without changing all users of that data, which I think is kind of the same as interfaces in Java unless I'm missing something
thanks for the thoughts, @U3JH98J4R! this is really insightful
@U0739PUFQ interfaces would not allow promotion from char
-> String
no matter how I did it
interface RenderOptions {
char complete();
char incomplete();
}
Committing to the contents of the field isn't really the issueAnd I think this is descriptive - there are value judgements to make, but they are distinct from the existence of the tension
I was talking more in general, for this specific example I think for complete and incomplete I would choose something that can be stringified (that is the user interface). Since anything can overwrite .toString(), I would just use Object complete;
where you can put Char, String or anything else, and you can change it later to anything that overwrites toString(), which is probably what the Clojure implementation will do with str to support char and String
how is that? I mean, how using Object there is inferior to Clojure maps from keywords to Objects for this case? I think just because Clojure maps gives you Object everywhere by default it doesn't mean you don't need to foresight fields possibly changing. You can't return a user entity like {:name "John" :birthdate (Date. ..)} and then one day expect to change :birthdate to a string, or a long, without it breaking
For return types, yes. This is an "input type" though. Broadening the contract of what's allowed is always okay
The problem is that I need to dictate the vocabulary of allowed interactions for that input type. This means I need to say "you get a char" out or know to resist saying that and instead say "it's an object"
I think what you need to make explicit is again the interface you are going to use on some input field, which is important since you need to use that input somehow. In Clojure if you are doing arithmetic on some input and someone now sends you a string it is going to break
so here by declaring complete as "something stringifiable" you just commit to that, to something that can have a string representation because you need strings in the end
interface RenderOptions<Length, Format, Complete, Incomplete> {
Length length();
Format format();
Complete complete();
Incomplete incomplete();
}
void render(RenderOptions<int, String, char, char> opts) {
// ...
}
interface RenderOptions<Length, Format, Complete, Incomplete> {
Length length();
Format format();
Complete complete();
Incomplete incomplete();
}
void render(RenderOptions<int, String, char | String, char | String> opts) {
// ...
}
so there isn't a reason to care if someone makes a RenderOptions<boolean, boolean, List, Void>
but why not just?
public record RenderOptions {
final int length;
final String format;
final Object complete;
final Object incomplete;
}
public record RenderOptions {
final int length;
final String format;
final char complete;
final char incomplete;
}
whereas with clojure you can start with a narrow input contract and expand it later without breaking consumers
sorry not sure I follow, maybe we are just talking about different things, but is ok, we can disagree :hugging_face:
I think you are talking about what the design could be - yes I could take a record with two Object
fields and that fits the requirement of Character | String
. Probably I would just take String
, but I get the idea.
I am talking about how unless I "hide" things, I cannot evolve the design from an initial design that only wanted char
as I see it if you want to move from Object to char on input it is "requiring more" in Rich terms, and you are going to break, no matter if Java or Clojure. In clojure if you have a fn that accepts a map of keys -> objects, but suddenly your code needs to only accept a map of keys -> Longs, then you are going to break some users
we have a proverbial map of keys -> Longs and we want to accept a map of key -> Object
and that breaks because requiring less for input means we provide less for people who introspect the input object
oh sorry, I thought you said the other way around, but that case you can evolve from char to Object with no problem in Java or Clojure
its fine if its
void f(char c) {}
to
void f(char c) {
f((Object) c);
}
void f(Object c) {}
why not?
and how does Clojure scapes that? if there is that exact same code? I mean, if there is code already doing (String/valueOf((:c m)))
sorry not sure I follow, kind of confused now tbh XD and have to go back to work. Always fun to explore this kind of things!
oh I think now I understand what you meant by String s = String.valueOf(in.c());
breaking, you will need to add a cast there, I see, interesting
Is it correct to assume that future
is better suited for short lived tasks, and core.async/go
for longer running tasks? (Assuming no other CSP/core.async features are used)
future
will use a dedicated thread in a caching thread pool. It doesn’t really have constraints on short vs long running. go
is for coordination. “long running” can be a bit ambiguous. an infinite loop pulling from channels, a bit of data manip, and then sending to other channels is long running but a great use case for go
. A single execution that selects from a database, does data manip and then puts back into a db is a terrible scenario for a go
block
In the core.async world you'd normally use core.async/thread
instead of a future
. Using go vs thread isn't about long running or not, but more about whether the task is CPU or IO bound.
I’ve never tried to do this before, and I just assumed it would work. I can always dispatch on the names of the enums, but I thought I might be able to do so on the objects themselves
enums are not handled any differently by clojure than other classes and methods and fields, so same evaluation behavior, etc, and the same caveats with literals apply to case
there are weird hacks and stuff you can do to make either the reader eval the read of the enum object from the static field on the enum class
you can also do things like write you own macro that embeds the enum objects in the expansion to case, but that depends on the compilers ability to "decompile" arbitrary objects into bytecode and then reconstruct them when running the bytecode, which can be spotty
My brain was elsewhere, and I was thinking that something like jakarta.json.JsonValue$ValueType/STRING
was a constant. But of course it’s a symbol. I’m switching languages too much and my brain isn’t keeping up.
this is something I feel bad doesn't work :( I do think we have a ticket about it somewhere
java's switch does let you use enums, but I think it is a little tricky then you might expect, because there is nothing stopping you from compiling against one version of enum E and then having a different version of enum E at runtime
In other contexts that can cause runtime errors. I don’t know what it would do for a different version of an Enum in a comparison like that :face_with_spiral_eyes:
But, to be honest, if you’re compiling with one class, and then replace it with another class at runtime, you’re asking for whatever happens to you. (and yes, I’ve been the bunny who has tried to do this in a Tomcat service in the past. Classpaths can be painful. But it was my own fault)
I think it is more an area of concern with static languages where you want some completeness guarantees
case
won't check completeness like switch so wouldn't matter from clojure perspective
https://clojure.atlassian.net/browse/CLJ-1368 has a link to a google groups discussion as well that mostly has a survey of some different hacks to make enums work in case
is it consistency and limiting complexity that case doesn't work with enums like this? or are there technical limitations too?
I wrote a ecase macro once for this use case that dispatches on the enum value, you could probably extend it to check for completeness at compile time
pretty sure enums didn't exist yet when case was created, so that's why they were not considered, but don't know
of course, completely forgot that enums were a "recent" addition to java
enums were Java 5, case was Clojure 1.2
java's switch I believe requires you to add a default case with enums, incase the code is used at runtime with a version of the enum with more values
@U3JH98J4R oh, just a small utility in some code that needs to parse JSON-LD. I haven’t had time to write a JSON-LD parser for Clojure, so I’m using a Java one called Titanium. (I’m not a fan)
Then I discovered that I need to parse some JSON, and rather than bring in yet another external lib, I wondered if I could just use what had already been loaded for the JSON-LD parser.
Again, I’m not a fan. 🙂
But it was very little code to convert it to a Clojure object, so it didn’t bother me. I just found myself frustrated that I couldn’t use case
on the enum for the JsonValue type
with clojure's case as is, you would get an exception at runtime without a default case if an enum was added to
(defmacro ecase
{:style/indent 1}
[e & clauses]
(let [t (:tag (meta e))
clazz (resolve t)
values (Reflector/invokeStaticMethod ^Class clazz "values" (object-array []))
syms (map (comp symbol str) values)
ords (map #(.ordinal ^Enum %) values)
mapping (zipmap syms ords)
lookup (fn [x] (or (mapping x)
(throw (ex-info "Value not in enumeration" {:value x
:syms syms}))))
[clauses else] (if (odd? (count clauses))
[(butlast clauses) [(last clauses)]]
[clauses []])
clauses (->> clauses
(partition 2)
(map (fn [[clause expr]]
[(if (sequential? clause)
(map lookup clause)
(lookup clause))
expr]))
(apply concat))]
`(case (.ordinal ~e)
~@clauses
~@else)))
you can add a check that all enum cases are covered, tooyou can check that that all the values of the enum you are linked against at compile time are handled
enum Thing {
A,
B
}
....
void f(Thing thing) {
switch (thing) {
case A -> System.out.println("A");
case B -> System.out.println("B");
}
}
the ordinal values will be swapped, but I would be surprised if the hypothetical f
method would change its behavior
At least in the code I wrote, the programmer specifies the cases as enum symbols, so if you link against different orders it doesn't really matter as long as you compile your ns after the java enum was compiled, no? The ordinals will be resolved at macro expansion time
it is the same thing, the version you inspect at macro expansion time could end up different from the version at runtime
well if there is a new version of the java enum that links against AOT compiled clojure code there could be an issue, right?
that kind of thing is somewhat less of an issue with clojure because we tend to always build from source and mostly don't distribute aot'ed code, but some people do that
im super curious now but working with javac is a bit painful - i'll try the hypothetical and report back later
class Main {
Main();
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
static void f(Thing);
0: getstatic #7 // Field Main$1.$SwitchMap$Thing:[I
3: aload_0
4: invokevirtual #13 // Method Thing.ordinal:()I
7: iaload
8: lookupswitch { // 2
1: 36
2: 47
default: 55
}
36: getstatic #19 // Field java/lang/System.out:Ljava/io/PrintStream;
39: ldc #25 // String Hello
41: invokevirtual #27 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
44: goto 55
47: getstatic #19 // Field java/lang/System.out:Ljava/io/PrintStream;
50: ldc #33 // String World
52: invokevirtual #27 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
55: return
}
class Main$1 {
static final int[] $SwitchMap$Thing;
static {};
0: invokestatic #1 // Method Thing.values:()[LThing;
3: arraylength
4: newarray int
6: putstatic #7 // Field $SwitchMap$Thing:[I
9: getstatic #7 // Field $SwitchMap$Thing:[I
12: getstatic #13 // Field Thing.A:LThing;
15: invokevirtual #17 // Method Thing.ordinal:()I
18: iconst_1
19: iastore
20: goto 24
23: astore_0
24: getstatic #7 // Field $SwitchMap$Thing:[I
27: getstatic #23 // Field Thing.B:LThing;
30: invokevirtual #17 // Method Thing.ordinal:()I
33: iconst_2
34: iastore
35: goto 39
38: astore_0
39: return
}
Yeah, it really is a map thing, mapping from ordinal to position in array, where position in array is fixed at compile time
Your can't really do that with a macro on top of case, because you likely cannot get the compiler to hoist the map creation into a static context
(I think spectre has a similar issue with the inline caches its macros expand to use, and does some shenanigans)
What do you usually use when trying to transform heterogeneous maps? E.g. given some input data like this:
(def sample-colors
{:black "#000"
:white "#fff"
:lime
{:50 "#f7fee7"
:100 "#ecfccb"
:200 "#d9f99d"
:300 "#bef264"
:400 "#a3e635"
:500 "#84cc16"
:600 "#65a30d"
:700 "#4d7c0f"
:800 "#3f6212"
:900 "#365314"
:950 "#1a2e05"}
:orange
{:50 "#fff7ed"
:100 "#ffedd5"
:200 "#fed7aa"
:300 "#fdba74"
:400 "#fb923c"
:500 "#f97316"
:600 "#ea580c"
:700 "#c2410c"
:800 "#9a3412"
:900 "#7c2d12"
:950 "#431407"}
...})
that you want to transform it to something like:
{:black "#000"
:white "#FFF"
:lime-50 "#f7fee7"
:lime-100 "#ecfccb"
:lime-200 "#d9f99d"
...
:orange-50 "#fff7ed"
:orange-100 "#ffedd5"
...
:smth-else-50 "fefefe"
...}
My “go to” library for transformations is #CFFTD7R6Z but the transformation that you are talking about probably doesn’t warrant meander’s power. Instead, I think this could be handled in clojure straighforwardly.
(fn f [p m]
(if (string? m)
[[(keyword (apply str (interpose \-) p)) m]]
(for [[k v] m
i (f (conj p (name k)) v)]
i)))
ah so the trick is to make a list with a single element in case of string values in order to make them kinda homogeneous with the others
this is the same problem as https://clojurians.slack.com/archives/C03S1KBA2/p1680025724114089
I'm building an app for which I'd like to delegate authz/n to an OIDC identity provider. That is, it is my goal that my users can use whatever OIDC provider that they want (AWS IAM, Keycloak, Okta, etc.), provided they launch my app with a config file pointing to their identity provider. I've spent some time looking for a nice little clj/s library that bundles up the various steps of the standard OIDC connection flow into a few functions that I can wire up to my frontend / ring / my database, but haven't found anything to that effect. I built an OIDC integration at my last company so I know the steps involved (it isn't tooooo bad,) but I'd like to avoid having to get too deep into it if I can. Any thoughts? I can't be the only one who's run into this.
FusionAuth is open source, so with some pain you could potentially build on top of its API https://github.com/fusionauth Metabase's SSO stuff also might be open sourced (I think)
I'm looking for a way to get a consistent reload experience regardless of IDE. Reloading with tools.namespace
was okay but if I goofed or made an error, the reload would blow up, forcing me to lose all of my context/vars and require a REPL restart. Previously, using Cursive, if I made an error and reloaded my old state was preserved and I could fix the error and continue on. Is there a way to mimic my Cursive reload experience with tools.namespace
?
cursive is mostly likely not actually reloading, just re-evaluating the contents of the current file
most editor integrations have something like that, alternatively you can call load-file on the file, or just copy and paste the file contents into the repl
actually cursive may be doing something between what tools.namespace calls reloading, and just loading the file contents
cursive is maybe doing something more akin to (require 'the-namespace :reload-all)
when reloading with tools.namespace, if you hit a compilation error, you will get only half your namespaces (or less) reloaded and you would feel like you lost everything and tend to restart the repl. When this happens, instead reloading the "main" namespace (the one that pulls everything else) with whatever editor is enough to bring everything back and start fixing the compilation errors
you still need a way of stopping and restarting resources like threads, db connections etc, which you can accomplish with any of the "reloaded workflows"
I usually have a snippet in my ide which stops the system if tools.namespace reloading fails. Making sure to grab the reference beforehand
> Reloading with tools.namespace
was okay but if I goofed or made an error, the reload would blow up, forcing me to lose all of my context/vars and require a REPL restart.
An intentful setup can avoid this.
For instance, require and refer key tools.namespace stuff in user.clj, and disable reloading for user.clj https://github.com/clojure/tools.namespace#disabling-refresh-in-a-namespace
Leave user.clj thin, just for that and a few other things that should be able to salvage a failed refresh.
And use dev.clj as your 'real' user.clj with app-specific helpers, etc.
This way, if a (refresh)
fails, user.clj is still defined as-is, so you can repair your code, hit (in-ns 'user)
and refresh
again from there
Thanks for the feedback everyone. A few things I'm going to try.
• Re: hiredman
- Been having some luck with (require 'the-namespace :reload-all)
so I'll keep playing around with that. And possibly make my own helper func to do it since I'm working in a Polylith context.
• Re: jpmonettas
- I'll have to double check but I'm fairly certain Calva's reload capabilities cannot recover your repl when tools.namespace
borks it. But I'll double check that and try some of your suggestions. I'm using Stuart Sierra's component library so I've got myself a nice reloaded workflow.
Re: vemv
:
> For instance, require and refer key tools.namespace stuff in user.clj, and disable reloading for user.clj
That's a neat trick. I'll give that a shot.
Having mentioned it, reload-all is kind of terrible, reloading code without tracking transitive dependencies can really break things in confusing ways, which is why the tools.namespace is the way it is, and why it is so scorched earth about it
Some people completely eschew that kind of reloading tooling in favor of being disciplined about repl usage because of the trade offs involved https://youtu.be/gIoadGfm5T8 (you can find other videos of Sean talking about repl usage searching on yt as well)
A relevant discussion from the past: https://clojurians.slack.com/archives/C03S1KBA2/p1645029644318459
I also have a custom snippet in user.clj
that does stop the system before the reload.
There's also a trick - sometimes, when something breaks the reload, it can leave things un-reloadable. tools.namespace
have a way to "clear" the incremental reload state and reload everything
My snippet is basically keeping a state if the last reload was an error, and if it was, clear the whole thing before trying to reload again