Fork me on GitHub
#clojure
<
2022-08-08
>
pinkfrog04:08:50

(clj-java-decompiler.core/decompile (fn [n]
                                        (let [m (int n)]
                                          (loop [i (int 0)]
                                            (if (< i m)
                                              (recur (inc i))
                                              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 = Numbers.inc(i)) {}
        return Numbers.num(i);
    }
    
    @Override
    public Object invoke(final Object n) {
        return invokeStatic(n);
    }
}

pinkfrog04:08:32

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

pinkfrog06:08:04

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

pinkfrog08:08:45

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

(comment

  (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)))))
Output:
// 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

didibus18:08:58

Hum, I didn't know that. The clojure doc: https://clojure.org/reference/java_interop 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

didibus18:08:56

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

didibus18:08:04

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

didibus18:08:57

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 🙂

didibus18:08:35

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

didibus18:08:47

Looking at the bytecode answers the same question.

didibus18:08:11

I think I'd ask this on http://ask.clojure.org , 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.

didibus18:08:09

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.

Fernando17:08:57

Hello, is someone from Mexico?

p-himik17:08:14

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

chronno17:08:33

Yep :raised_hand:

borkdude17:08:48

#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 - https://www.youtube.com/watch?v=gIoadGfm5T8

hiredman20:08:17

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

rolt20:08:34

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
1
hiredman20:08:33

a big difference with python is exactly the process model

hiredman20:08:48

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
chrisbroome20:08:04

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.

hiredman20:08:25

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

chrisbroome20:08:35

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.

hiredman20:08:51

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

hiredman20:08:08

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

hiredman20:08:31

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

Kelvin20:08:04

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/Foo
 (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

Kelvin20:08:44

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

hiredman20:08:42

p/-valid-foo?
is not a total predicate, so not a good spec

Kelvin20:08:11

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

Kelvin20:08:45

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

Kelvin20:08:03

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

hiredman20:08:59

this is the protocol capture thing

hiredman20:08:25

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

hiredman20:08:00

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

hiredman20:08:21

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

hiredman20:08:45

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

Kelvin20:08:54

Huh when I tried that I got this error:

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

hiredman20:08:56

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

Kelvin20:08:54

Aha that worked!

Kelvin20:08:37

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

Kelvin20:08:39

Thank you!

hiredman20:08:54

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]
 p/Foo
 (-valid-foo? [_] true))

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


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

hiredman21:08:13

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

hiredman21:08:41

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

hiredman21:08:58

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

Kelvin21:08:13

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

Kelvin21:08:31

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

hiredman21:08:10

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

hiredman21:08:24

it is basically this

(def f +)

(def g (partial f 1))

(def f -)

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

Kelvin21:08:14

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

hiredman20:08:13

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

hiredman20:08:32

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

Kelvin20:08:36

> 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

diego.videco22:08:19

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?

hiredman22:08:26

you should set error reporting to stderr so you get something more informative https://clojure.org/reference/repl_and_main#_as_launcher

hiredman22:08:31

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

hiredman22:08:07

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)

diego.videco22:08:56

it was a missing classes directory