This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-08-08
Channels
- # announcements (4)
- # aws (1)
- # babashka (4)
- # beginners (75)
- # biff (4)
- # calva (13)
- # clojure (76)
- # clojure-android (1)
- # clojure-austin (9)
- # clojure-europe (14)
- # clojure-mexico (3)
- # clojure-nl (2)
- # clojure-norway (11)
- # clojure-uk (14)
- # clojurescript (19)
- # conjure (14)
- # cursive (30)
- # datomic (13)
- # gratitude (6)
- # hyperfiddle (71)
- # introduce-yourself (2)
- # juxt (5)
- # malli (5)
- # nbb (5)
- # nrepl (10)
- # off-topic (32)
- # re-frame (3)
- # releases (1)
- # shadow-cljs (5)
- # sql (38)
- # tools-deps (24)
- # xtdb (19)
(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);
}
}
Loop locals and function arguments primitives are only supported for long and double by the clojure compiler
i is a long, so after the int cast method call you'll see a lcast bytecode instruction which promotes it to a long
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.
(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);
}
Yep, I didn't say anything about let bindings, only loop locals and function arguments
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
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.
Why should I do that? I already found out the hard way, decompiled to bytecode and now I know how it behaves
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.
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.#off-topic is also a good channel to start chats like this, more so than the #clojure channel
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?
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
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
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
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.
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.
works great with any editor, and you are not beholden to any particular editor tooling
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.
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
@U03P01XST0W's question is precisely addressed here https://mikelevins.github.io/posts/2020-12-18-repl-driven/
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
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
In my actual library I defined default protocol impls on Object
and nil
so there it would be.
The issue is that I want the second s/valid?
to return true
, not return false
nor throw an exception
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 specHuh when I tried that I got this error:
; Execution error (NullPointerException) at java.util.regex.Matcher/getTextLength (Matcher.java:1770).
; null
looks like spec doesn't like a var as a predicate? try (def foo-spec #(p/-valid-foo? %))
I guess that way the protocol fn is only resolved at runtime, rather than when the spec is defined at compile-time
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 issuethe 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
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 redefinitionInteresting 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-type
s 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 https://clojure.org/reference/repl_and_main#_as_launcher
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 wasmy 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)
Thanks
it was a missing classes
directory