Fork me on GitHub

(clj-java-decompiler.core/decompile (fn [n]
                                        (let [m (int n)]
                                          (loop [i (int 0)]
                                            (if (< i m)
                                              (recur (inc i))
Why m is compiled to int, but i is still a long?
public final class demo$fn_line_28__15421 extends AFunction
    public static Object invokeStatic(final Object n) {
        int m;
        long i;
        for (m = RT.intCast(n), i = RT.intCast(0L); i < m; i = {}
        return Numbers.num(i);
    public Object invoke(final Object n) {
        return invokeStatic(n);


Maybe not a why. But just the [i (int 0)] is unnecesary.

Ben Sless04:08:22

Loop locals and function arguments primitives are only supported for long and double by the clojure compiler

Ben Sless04:08:56

Floats and ints will be promoted correctly


That said, m is indeed a int.

Ben Sless08:08:47

Look at the byte code

Ben Sless08:08:35

i is a long, so after the int cast method call you'll see a lcast bytecode instruction which promotes it to a long

Ben Sless08:08:11

Then, when m and i are compared, m will be promoted to long, too, you'll see another lcast instruction


looks like in loop construct the vars are defined as long, but not the case with let construct.


  (dev/decompile (fn [x]
                   (loop [x (int x)
                          y (int 0)]
                     (prn x y))))

  (dev/decompile (fn [x]
                   (let [x (int x)
                         y (int 0)]
                     (prn x y)))))
// loop
    public static Object invokeStatic(final Object x) {
        final long x2 = RT.intCast(x);
        final long y = RT.intCast(0L);
        return ((IFn)demo$fn_line_21__15546.const__2.getRawRoot()).invoke(Numbers.num(x2), Numbers.num(y));
    // let
    public static Object invokeStatic(final Object x) {
        final int x2 = RT.intCast(x);
        final int y = RT.intCast(0L);
        return ((IFn)demo$fn_line_26__15550.const__2.getRawRoot()).invoke(x2, y);

Ben Sless09:08:05

Yep, I didn't say anything about let bindings, only loop locals and function arguments


Hum, I didn't know that. The clojure doc: doesn't mention the difference for loop. But ... I'm actually not sure it's a long, it's declared as a type long, but when the value is added to i it is casted to int


What does:

long i = (int) 0;
Do in Java? Does that make i an int or a long?

Ben Sless18:08:51

long. You'll have three opcodes, lload 0, icast, lcast


You should try to overflow it and see what happens, does it overflow at the int max range or the long?


Oh, interesting. It's quite strange, I would say this might be an omission. Like not sure this was the intended behavior.

Ben Sless18:08:19

Why should I do that? I already found out the hard way, decompiled to bytecode and now I know how it behaves

Ben Sless18:08:29

decompiled java isn't the source of truth, bytecode is

Ben Sless18:08:41

I tried to outsmart the clojure compiler and got a performance drop 🙂


Ya, I wrote that before you posted, we had a posting race condition.


Looking at the bytecode answers the same question.


I think I'd ask this on , seems like a possible improvement. But it does explain why I've noticed that it's just best to stick to long and double always, and never use int and float.


I think this example is wrong as well:

static public float asum(float[] xs){
  float ret = 0;
  for(int i = 0; i < xs.length; i++)
    ret += xs[i];
  return ret;
(defn asum [^floats xs]
  (areduce xs i ret (float 0)
    (+ ret (aget xs i))))
Because ret will be double and i will be long. I'm not even convinced the floats when called with + will not be casted to a double as well.


Hello, is someone from Mexico?


FWIW, there's #clojure-mexico but not very active.


Yep :raised_hand:


#off-topic is also a good channel to start chats like this, more so than the #clojure channel

Pedja Zolinsky19:08:02

I'm not sure I understand the power of REPL driven development in Clojure. How is this different than executing the "python" command in the terminal and then typing in single lines of code in the interpreter and seeing the result?

R.A. Porter20:08:11

Whether you use it in production or not depends on your comfort level, your team/company's policies, and any security/auditing strictures in place for your industry. But production use aside, a Clojure REPL is very different from running commands in an interpreter. Firstly, you should rarely find yourself typing directly in the REPL; you would normally be loading namespaces and sending s-expressions from your connected editor to the REPL. Iterative development is sped up because as you make incremental changes to your functions, you can re-evaluate them by sending them to the REPL. You dynamically change the functionality of your code as you go, without need for any sort of compile/restart cycle. As you intuited, you can also effect state as needed; you can likewise inspect state with little effort. There are many videos, blog posts, and tutorials available; you might want to check this one from Sean Corfield as an excellent intro -


I mean, that is usually the use case for a live production repl, something isn't working so you hot patch in some debug logging, or something is broken and you hot patch in a fix, or some query function isn't doing what you expect so you fiddle with it, or some data is wrong due to a bug or something, and you can fix it by just writing a little loop over your production state


for me: the lisp syntax make it so much easier (evaluating or extracting subforms). In python you usually evaluate the whole function. immutable & readable datastructures, pure functions: i gain a lot more insight from evaluating a part of the program. production repl is a bonus, i seldomely used it

💯 1

a big difference with python is exactly the process model


a clojure repl is often not just a clojure repl, but a clojure repl in the same process as your app, with the webserver, database, in memory state, whatever all there

💯 1

So, i had the same question that the OP had for a LONG time. after messing around with Clojure on-and-off for the better part of 4 years, I finally stumbled on what the clojure community calls a REPL sessions vs what literally every other language calls a REPL (incidentally Tony Kay's #fulcro video series is what finally showed me the difference). IMO the problem is that what Clojure calls REPL-driven is actually more like interactive-session-driven-development. When devs hear the term REPL they immediately think console/command line. Clojure has taken the term REPL and – though it's technically a Read-Eval-Print-Loop – it's not meant to be used the same as other languages. You're supposed to set up your editor to connect to a running "REPL" (which i like to call a session) and have your Clojure statements (forms) evaluated in the running REPL's context. Using the term REPL is, imo, extremely confusing and is a failure of marketing.


I would hard disagree it has anything to do with editor integration


There's also not really any official documentation (last i checked) on how to get new users set up in their editor of choice - be it intellij, vscode, vim, emacs. This is leaves a big gap for newcomers to adopt the language and development style. At least this has been my personal experience.


I generally manually copy and paste code into repls I am running


works great with any editor, and you are not beholden to any particular editor tooling

👍 2
Jimmy Miller20:08:31

Well, looks like @U0NCTKEV8 would hard disagree. But I might hard disagree back 🙂 I still think the biggest difference is about working in your editor with the code. Maybe not strictly necessary. But clojure is designed for this kind of interaction and it makes a massive difference (personally) in my speed to get things out there and explore solutions. Figured I’d share a quick video just to give a flavor of what that looks like.

💯 2
Jimmy Miller20:08:01

Not only is this style of coding efficient. It changes the way you write code, like the actual shape. I’ve worked multiple clojure jobs. I can always tell from someones code if they work this way or not


clojure is designed for interaction at the repl, your editor may or may not provide stuff on top of that


Curious behavior revolving protocols and how they interact with specs - specs that rely on protocol functions can work with new defrecord definitions, but not with extend-type:

;; Protocol Namespace

(ns protocols)

(defprotocol Foo
  (-valid-foo? [this]))

(extend-type Foo Object
 (-valid-foo? [_] false))

(extend-type Foo nil
 (-valid-foo? [_] false))

;; Intermediate Namespace

(ns specs
 (:require [protocols :as p]))

(def foo-spec p/-valid-foo?)


(ns impl
 (:require [clojure.spec.alpha :as s]
           [protocols :as p]
           [specs :refer [foo-spec]))

(defrecord FooImpl [x]
 (p/-valid-foo? [_] true))

(extend-type String p/Foo
 (p/-valid-foo? [_] true))

(s/valid? foo-spec (->FooImpl "foo")) ; => true
(s/valid? foo-spec "foo") ; => false


This could end up being a serious issue, since I’m trying to make the library I’m working on extensible via extend-type and extend-protocol


is not a total predicate, so not a good spec


In my actual library I defined default protocol impls on Object and nil so there it would be.


In that case the second s/valid? would return false instead of throwing an exception


The issue is that I want the second s/valid? to return true, not return false nor throw an exception


this is the protocol capture thing


if you defined the spec after extending the protocol it would work


basically everytime you extend a spec it mutates the values of the vars of the protocol functions


(def foo-spec p/-valid-foo?)
is capturing the value of the var from before the extension and using that in the spec


(def foo-spec #'p/-valid-foo?)
should make it work


Huh when I tried that I got this error:

; Execution error (NullPointerException) at java.util.regex.Matcher/getTextLength (
; null


looks like spec doesn't like a var as a predicate? try (def foo-spec #(p/-valid-foo? %))


Aha that worked!


I guess that way the protocol fn is only resolved at runtime, rather than when the spec is defined at compile-time


Thank you!


it really has nothing to do with spec at all

;; Protocol Namespace

(ns protocols)

(defprotocol Foo
  (-valid-foo? [this]))

;; Intermediate Namespace

(ns specs
 (:require [protocols :as p]))

(def foo-spec p/-valid-foo?)


(ns impl
 (:require [protocols :as p]
           [specs :refer [foo-spec]]))

(defrecord FooImpl [x]
 (-valid-foo? [_] true))

(extend-type String p/Foo
 (-valid-foo? [_] true))

(foo-spec "Foo")
has the same issue


the issue is (def foo-spec p/-valid-foo?) takes the value of #'p/-valid-foo? right when it runs assigns #'foo-spec to that value


then extend-type mutates the value of #'p/-valid-foo?


so #'foo-spec is left pointing to the old value


I’m still curious why I didn’t encounter this with defrecord


Must be related to how defrecord and extend-type implement the protocol internally


with an inline protocol definition in a defrecord it doesn't mutate the protocol definition ,and instead uses an interface that is generated behind the scenes for the protocol


it is basically this

(def f +)

(def g (partial f 1))

(def f -)

(g 1)
but extend-type is hiding the redefinition


Interesting that defrecord (and I presume deftype) work differently from extend-type under the hood, but it makes sense given the latter affects a pre-existing type


instead of just returning true or false, it returns true, false, or throws an exception if the argument doesn't implement the protocol


that is how protocol functions work, if you call them on something that doesn't implement the protocol, you get an exception


> is not a total predicate, so not a good spec I edited my example to include extend-types on Object and nil in order to make it a total predicate


This might be slightly OT, but I am having a strange error while trying to AOT compile a file in a docker container with a github action. I am getting the following error:

Syntax error macroexpanding clojure.core/ns at (org/project/kafka_serdes/core.clj:4:1)
The command is this one: docker build -t $REGISTRY/$REPOSITORY:$IMAGE_TAG -f $DOCKERFILE_PATH . The weirdness comes from the fact that building the image locally, called with basically the same command, works fine. Has someone else experienced something similar?


you should set error reporting to stderr so you get something more informative


Syntax error macroexpanding
basically tells you a macro threw an exception while expanding, the full error report (which by default gets printed to a file in /tmp, which isn't very useful with docker) will tell you what the exception was


my wild guess would be some resource used by a macro in some way on line 4 of that namespace is missing for some reason in the github action, but available locally (maybe different sets of aliases, forget to check something in, etc)


it was a missing classes directory