The Luminus framework is a good start as well. Great docs. It's by the same author as Web Dev book mentioned by @denis.baudinot https://luminusweb.com/docs/guestbook
thanks @delaguardo
Clojure is amazing but Its hard to learner than other programming language!:hugging_face:
Hi. if
is a special form which only evaluates a single condition branch. If that is the case, why an exception is thrown in the following scenario? That is, why isn't `(1)` a compile time exception as well? Both `(1)` and `(nil)` should throw an exception, or none at all
so to summarize, if i understand correctly, the reason that in my original message calling (nil)
throws a read-time exception while (1)
doesn't is because in java null
is not an object? that is, this is an implementation detail of the host language we cannot go around by?
A syntax error for (nil)
is thrown because it's not possible to call nil
as a function
.. in java (right?)
Yes, functions seem to be invoked by calling the invoke
method on objects
thanks for the thorough answer. side question: does this also occur in cljs?
Yes, the cljs compiler doesn't allow it
thanks fvr. much obliged 🎩
You're welcome! Trying to read and understand parts of the Java source of Clojure seems approachable, but the caveat is that I've never programmed in Java, so I hope I've understood things correctly.
> the reason that in my original message calling (nil) throws a read-time exception while (1) doesn't is because in java null is not an object?
right - the runtime is such that (nearly?) anything other than nil
could be extended or replaced between read time and execution, but not nil
thanks @noisesmith!
because this is compile time exception
why isn't (1)
a compile time exception as well then?
both (1)
and (nil)
should throw an exception, or none at all
this is actually what i was asking sorry if i wasn't clear
edited the question
hm… which version of clojure? it works fine for me
1.10
mine is 1.10.3
that's what i meant: only (nil)
throws an exception, not (1)
replace (1)
with (nil)
and you get an exception
but both statements are illegal
https://github.com/clojure/clojure/blob/b1b88dd25373a86e41310a525a21b497799dbbf2/src/jvm/clojure/lang/Compiler.java#L7102-L7103 because compiler checks it explicitly
and I think this is an obvious (and cheap) compile time check.
I think "if does not evaulate both branches" is meant as a performance optimization, not as part of the semantic.
i don't understand why it is implemented this way. it feels inconsistent
kinda, but it will be some significant performance penalty if it will check other types as well
and it is not possible to do in general for the case of if
without evaluation
@teodorlu It’s definitely required and not just a performance optimisation. Even if you don’t do anything with the returned value, a branch could have side effects. That’s actually why if
can’t be a simple function and must be a macro
What's required? Not sure we disagree.
That it doesn’t evaluate both branches
Didn't Ory just provide a case where it evaluates both branches?
I guess "syntax error" might be considered different from evaluation.
This error thrown during compile time. Nothing is evaluated yet )
As @delaguardo is pointing out, the error happens in the compiler, at compile-time, before evaluation. if
does indeed not evaluate both branches, as part of it's semantics, but this doesn't prevent the compiler from seeing the form (nil)
.
Maybe a reason for this explicit check is to give a more informative error message than NullPointerException
.
more like prevent NullPointerException from happening in case it will be definitely thrown in runtime. And this can happen with significant delay
(defn foo [] (+ 1 (nil)))
this form will fail during load not when foo function is calledi still don't understand why (nil)
is a special case that causes failure in the reader, and not just let it fail in the evaluator like every other illegal expression. i don't see the advantage in treating (nil)
differently
Maybe think of it as a null-pointer check?
what is so special about this check compared to every other check?
It doesn't ever make sense to call null
as a function
so does (1)
or ("")
or a lot of other cases
yeah, but null check is cheap and others are costly
True, but not such a clear case. After all, (:key m)
is a valid function call. Maybe in the future integers will also be callable?
> but not such a clear case i don't understand why this clear case is so imporatnt
Probably because NullPointerException is not really nice way to tell developers about the problem.
For example (1)
is throwing ClassCastException with enough information
If I understand correctly, anything that implements clojure.lang.IFn
can be called as a function. But null
cannot have methods.
I encountered this "issue" when trying to write a macro that received something that might be a function:
(defmacro maybe-call [a]
`(if (fn? ~a)
(println "result of function call: " (~a))
(println "not calling")))
maybe-call
works fine when it receives a function or a number but not when it receives nil
(maybe-call (constantly true))
(maybe-call 3)
(maybe-call nil)
Syntax error (IllegalArgumentException) compiling at (*cider-scratch*:14:1).
Can't call nil, form: (nil)
Isn't it a unexpected consequence of the nil
call check?
Yes, but what do you mean by unexpected?
(if (fn? nil)
(println "result of function call: " (nil))
(println "not calling"))
it expand to that. So - yes, it is a consequence of nil
call checkIt's unexpected in the sense that it's hard to predict. A similar code in a function instead of a macro works fine
Ahh, I see. Yes, unexpected from programmer's point of view, but not from the compiler's (macroexpanded) point of view :)
is there some official clojure authority we can ask about this?
@ory.band "but both statements are illegal", yes, but for two very different reasons. And only one of these reasons can be checked in the compiler.
my point is that none of these should be checked by the compiler - it fails in unexpected scenarios such as the one Yehonatan demonstrated
or, at least, i don't see why it was implemented this way. i guess there's a good reason which i don't understand yet
One reason I see is that immediately after the null-check, the compiler calls if(op.equals(FN))
. So a nullpointer error will be thrown regardless in case op == null
A good place to post this question would be on http://ask.clojure.org
And I think a good way to look at this is to remember that there is a phase where Clojure must emit jvm bytecode. So it isn't evaluating anything, but it does have to emit bytecode with instructions on how to take both branches. And there's no good way to do that with (nil)
And interpreter might not have any problems with this, and in fact, bb
has no problems returning 1 from (if true 1 (nil))
. But if you need to emit code that performs both branches and then only jump to one of them, there isn't a great way to do this
I don't know enough jvm to know if (nil)
would be invalid bytecode or if its just patently clear that it will fail at runtime
@ory.band do I understand correctly that your point is that the compiler should check neither cases, because it leads to unexpected behaviour?
yes. it should fail/crash at the eval stage, not the read stage
Can I ask why?
more precisely, i'd like to know why (nil)
is treated in a special manner here
> Can I ask why?
i don't understand what's so special about (nil)
compared to (1)
or ("")
or any other scenario
A soft (and maybe wrong) answer: Because null
is not an object
1. this is a java implementation detail being unintentionally (i guess) exposed in clojure 2. is null the only non-object in java?
nil
doesn't have a type, 1
and ""
do.
is there any other thing in java that doesn't have a type?
i'm really asking, i'm not too familiar with java
I'm sorry I didn't make clear, in my last post I was talking about Clojure types.
ok, same question then. is nil
the only non-type in clojure? why do i even have to think about this when writing clojure
Clojure doesn't hide the fact that it's a hosted language
"`nil` doesn't have a type, 1
and ""
do."
but, (defprotocol T (t [this]))
(extend-type nil T (t [this] 7))
(t nil) => 7
somebody call rich already and have him settle this discussion lol
actually pretty sure in Clojurescript one can extend-type nil IFn
so that (nil)
is valid
not quite
cljs.user=> (extend-type nil IFn (-invoke ([this] 0)))
nil
cljs.user=> (nil)
Unexpected error (ExceptionInfo) compiling at (<cljs repl>:1).
Can't call nil at line 1
cljs.user=> (-invoke nil)
0
@octo221 that's interesting. Can you try this with numbers?
yes:
(extend-type js/Number IFn (-invoke ([this op x] (op this x))))
#object[Function]
cljs.user=> (71 + 171)
242
cool, thanks! 🙂
therefore:
cljs.user=> (extend-type js/Number IFn (-invoke ([this] this)))
(warning ignored)
#object[Function]
cljs.user=> (1)
#object[Number 1]
Cool! It looks like a difference is that in Clojure, clojure.lang.IFn
is a Java interface, while in Clojurescript, IFn
is a Clojure protocol.
is there a way to immediately return from a short-hand literal fn?
;; doesn't work
(map #(%) [1 2 3])
;; works
(map (fn [x] x) [1 2 3])
The point being that Clojure protocols can be implemented for any existing type, including nil
, while Java interfaces seem implemantable only when using making new types, eg. using defrecord
or reify
.
You could try #(do %)
(or just use identity
)
ah thank you, identity
is what I was looking for
fyi there is no early return in clojure
there is reduced
btw a little trick to see how reader macros expand is to quote them and wrap them into read-string like so: (read-string (str '#(%)))
it would expand like so: (fn* [%1] (%1))
which shows why you’d have a problem when mapping over a vector of numbers
(Reduced is not the same thing as early return)
user=> (read-string (str '#(%)))
(fn* [p1__2#] (p1__2#))
user=> '#(%)
(fn* [p1__6#] (p1__6#))
user=>
@ory.band clojure and the JVM are flexible enough that 1
could implement clojure.lang.IFn
by the time you execute the code, this isn't the case for nil
I don't think sane code would ever do such a thing, but the fact is that it's possible
oh I see someone up thread already demonstrated
> why do I have to think about this when writing clojure? as mentioned before I think, clojure is explicitly a hosted language, and embraces the details of its host, there are other languages (racket is quite similar and very well thought out) that do intend to abstract the host implementation away, but you'll find that the reason clojure is as popular as it is is that it explicitly embraces the VM (thus works well in integration with Java or JS projects)
ty @hiredman!
Quick question about stm/atoms
Given something like the code below, I'd like the update-state!
function to return the value of the parameter k
. Whenever I try to return that key, the state
atom defined on line 1 does not get updated. I'm pretty rusty here so I was wondering if anyone could help me out. Thanks!
(def state (atom {}))
(defn add-key! [state k v]
(swap! state assoc-in [k] v))
(defn remove-key! [state k]
(swap! state update-in [k] #(dissoc % doc-id)))
;; Updates the state but returns the whole atom
(defn update-state! [state k v]
(dosync
(remove-key state k)
(add-key! state k v)))
;; These both return the value `k` but do not update the state
(defn update-state! [state k v]
(dosync
(remove-key state k)
(add-key! state k v)
k))
(defn update-state! [state k v]
(dosync
(remove-key state k)
(add-key! state k v))
k)
Instead of providing a more concise/minimal example, I gave a completely different example thinking this was more to do with STM than lazy evaluation. In my implementation, I use add-key!
within a call to map
wherein that call to map
would either be evaluated when it's returned or if I force it with dorun
. What I actually need to do ( I think ) is use doseq
instead of map
. Better example implementation below:
;; state, add-key!, remove-key! from above
(defn add-keys! [index k vs]
(map (fn [[k v]] (add-key! index k v)) vs))
;; which should be, I think
(defn add-keys! [index k vs]
(doseq [v vs]
(add-key! index k v)))
> that call to map would either be evaluated when it's returned or if I force it with dorun to be clear, returning something never realizes it, but it gives the caller a chance to realize it eventually if they want to
you can also use run!
which is exactly like the two-arg version of map, except it returns nil and is eager
(ins)user=> (run! println "hello, world")
h
e
l
l
...
nil
I’m pretty sure you don’t really want dosync
in there, and I’m definitely sure you don’t need remove-key
, since you immediately assoc a new value with that same key. I’d say just change the name of add-key!
to set-key!
and get rid of dosync
and remove-key!
(unless you want to keep it as a stand-alone function to delete keys from the map entirely).
Also I agree with using assoc
instead of assoc-in
, given that you’re just updating the value for a single key in an un-nested map.
What is doc Id? It might be irrelevant but it's not defined.
I think instead of dosync your update fn should just do both operations.
I recommended against aliasing core fns unless they do something novel and it's shared. E.g just do (swap! S assoc k v) Atoms are mulithreaded state management that retry the logic on conflict. Do you need that?
Does this do what you want?
;; Updates the state, returns k
(defn update-state! [state k v]
(swap! state assoc k v))
(swap! state update k #(dissoc % doc-id)))
k)
If not then (assoc m k v) is sufficient. A context where you need an atom would be if you were running a web server and multiple http request handlers were trying to coordinate. Say to return a count of times across all browsers a button was clicked.
@frwdrik if op wants those actions to be one transaction (all of nothing) then they all need to be on one fn passed to swap!
Hey guys, thanks for the feedback but I think the purpose of the question has been missed through a poor explanation of the problem by me. What I was looking for was dorun
to force the swap!
to happen without having to rely on returning the value of the swap!
for evaluation. Thanks again for the feedback
Would you like to explain a bit more what you want to accomplish? Calling dorun
is used to force side effects out of lazy sequences
Can you try running this code? ;; make an atomic list (def players (atom ())) ;; #'user/players ;; conjoin a keyword into that list (swap! players conj :player1) ;;=> (:player1) It updates the state and swap! Does return the current value of the state.
Do run is, as fvr said, for lazy collections
to be clear, the only laziness in clojure is when something like map / filter etc. return a lazyseq, assoc is never lazy