beginners

Getting started with Clojure/ClojureScript? Welcome! Also try: https://ask.clojure.org. Check out resources at https://gist.github.com/yogthos/be323be0361c589570a6da4ccc85f58f.
jamesleonis 2021-06-30T02:08:54.258300Z

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

quan xing 2021-06-30T06:52:04.258600Z

thanks @delaguardo

quan xing 2021-06-30T06:53:50.258800Z

Clojure is amazing but Its hard to learner than other programming language!:hugging_face:

Ory Band 2021-06-30T10:07:35.259900Z

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

Ory Band 2021-07-01T11:28:44.297200Z

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?

Fredrik 2021-07-01T13:32:43.297700Z

A syntax error for (nil) is thrown because it's not possible to call nil as a function

Ory Band 2021-07-01T13:56:11.300200Z

.. in java (right?)

Fredrik 2021-07-01T13:56:36.300400Z

Yes, functions seem to be invoked by calling the invoke method on objects

Ory Band 2021-07-01T13:57:17.300600Z

thanks for the thorough answer. side question: does this also occur in cljs?

Fredrik 2021-07-01T13:59:23.300800Z

Yes, the cljs compiler doesn't allow it

Ory Band 2021-07-01T14:03:31.301800Z

thanks fvr. much obliged 🎩

Fredrik 2021-07-01T14:07:41.302Z

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.

2021-07-01T15:48:11.305100Z

> 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

Ory Band 2021-07-01T15:48:43.305300Z

thanks @noisesmith!

2021-06-30T10:08:28.260300Z

because this is compile time exception

Ory Band 2021-06-30T10:08:44.260500Z

why isn't (1) a compile time exception as well then?

Ory Band 2021-06-30T10:08:59.260700Z

both (1) and (nil) should throw an exception, or none at all

Ory Band 2021-06-30T10:09:16.260900Z

this is actually what i was asking sorry if i wasn't clear

Ory Band 2021-06-30T10:10:09.261300Z

edited the question

2021-06-30T10:10:16.261500Z

hm… which version of clojure? it works fine for me

Ory Band 2021-06-30T10:10:23.261700Z

1.10

2021-06-30T10:10:41.261900Z

mine is 1.10.3

Ory Band 2021-06-30T10:10:55.262100Z

that's what i meant: only (nil) throws an exception, not (1)

Ory Band 2021-06-30T10:11:10.262300Z

replace (1) with (nil) and you get an exception

Ory Band 2021-06-30T10:11:29.262500Z

but both statements are illegal

2021-06-30T10:29:47.263500Z

and I think this is an obvious (and cheap) compile time check.

teodorlu 2021-06-30T10:52:26.263700Z

I think "if does not evaulate both branches" is meant as a performance optimization, not as part of the semantic.

Ory Band 2021-06-30T10:53:07.263900Z

i don't understand why it is implemented this way. it feels inconsistent

2021-06-30T10:54:27.264100Z

kinda, but it will be some significant performance penalty if it will check other types as well

2021-06-30T10:56:24.264300Z

and it is not possible to do in general for the case of if without evaluation

solf 2021-06-30T12:01:04.264500Z

@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

teodorlu 2021-06-30T12:02:11.264800Z

What's required? Not sure we disagree.

solf 2021-06-30T12:02:24.265Z

That it doesn’t evaluate both branches

teodorlu 2021-06-30T12:03:10.265200Z

Didn't Ory just provide a case where it evaluates both branches?

teodorlu 2021-06-30T12:03:52.265400Z

I guess "syntax error" might be considered different from evaluation.

2021-06-30T12:13:47.265600Z

This error thrown during compile time. Nothing is evaluated yet )

👍 2
Fredrik 2021-06-30T12:16:17.265800Z

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) .

👍 1
Fredrik 2021-06-30T12:21:23.266100Z

Maybe a reason for this explicit check is to give a more informative error message than NullPointerException .

2021-06-30T12:24:08.266300Z

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 called

👍 1
Ory Band 2021-06-30T12:25:48.266500Z

i 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

➕ 1
Fredrik 2021-06-30T12:27:22.266700Z

Maybe think of it as a null-pointer check?

Ory Band 2021-06-30T12:27:47.266900Z

what is so special about this check compared to every other check?

Fredrik 2021-06-30T12:28:01.267100Z

It doesn't ever make sense to call null as a function

Ory Band 2021-06-30T12:28:17.267300Z

so does (1)

Ory Band 2021-06-30T12:28:27.267500Z

or ("") or a lot of other cases

2021-06-30T12:28:51.267800Z

yeah, but null check is cheap and others are costly

Fredrik 2021-06-30T12:29:57.268100Z

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?

Ory Band 2021-06-30T12:33:30.268300Z

> but not such a clear case i don't understand why this clear case is so imporatnt

2021-06-30T12:34:53.268500Z

Probably because NullPointerException is not really nice way to tell developers about the problem.

2021-06-30T12:35:45.268700Z

For example (1) is throwing ClassCastException with enough information

Fredrik 2021-06-30T12:36:00.268900Z

If I understand correctly, anything that implements clojure.lang.IFn can be called as a function. But null cannot have methods.

Yehonathan Sharvit 2021-06-30T12:36:00.269100Z

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

Yehonathan Sharvit 2021-06-30T12:36:09.269300Z

(maybe-call (constantly true))
(maybe-call 3)
(maybe-call nil)

Yehonathan Sharvit 2021-06-30T12:36:19.269500Z

Syntax error (IllegalArgumentException) compiling at (*cider-scratch*:14:1).
Can't call nil, form: (nil)

Yehonathan Sharvit 2021-06-30T12:36:44.269700Z

Isn't it a unexpected consequence of the nil call check?

Fredrik 2021-06-30T12:37:19.269900Z

Yes, but what do you mean by unexpected?

2021-06-30T12:38:11.270100Z

(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 check

Yehonathan Sharvit 2021-06-30T12:47:29.270300Z

It's unexpected in the sense that it's hard to predict. A similar code in a function instead of a macro works fine

Fredrik 2021-06-30T12:54:10.270500Z

Ahh, I see. Yes, unexpected from programmer's point of view, but not from the compiler's (macroexpanded) point of view :)

Ory Band 2021-06-30T13:01:41.270700Z

is there some official clojure authority we can ask about this?

Fredrik 2021-06-30T13:01:57.270900Z

@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.

Ory Band 2021-06-30T13:03:14.271100Z

my point is that none of these should be checked by the compiler - it fails in unexpected scenarios such as the one Yehonatan demonstrated

Ory Band 2021-06-30T13:03:43.271300Z

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

Fredrik 2021-06-30T13:08:22.271500Z

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

dpsutton 2021-06-30T13:09:11.271800Z

A good place to post this question would be on http://ask.clojure.org

dpsutton 2021-06-30T13:10:34.272Z

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)

dpsutton 2021-06-30T13:12:07.272200Z

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

dpsutton 2021-06-30T13:12:44.272400Z

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

Fredrik 2021-06-30T13:24:07.272600Z

@ory.band do I understand correctly that your point is that the compiler should check neither cases, because it leads to unexpected behaviour?

Ory Band 2021-06-30T13:24:44.272800Z

yes. it should fail/crash at the eval stage, not the read stage

Fredrik 2021-06-30T13:25:11.273Z

Can I ask why?

Ory Band 2021-06-30T13:25:15.273200Z

more precisely, i'd like to know why (nil) is treated in a special manner here

Ory Band 2021-06-30T13:25:45.273400Z

> Can I ask why? i don't understand what's so special about (nil) compared to (1) or ("") or any other scenario

Fredrik 2021-06-30T13:26:16.273600Z

A soft (and maybe wrong) answer: Because null is not an object

Ory Band 2021-06-30T13:27:22.273800Z

1. this is a java implementation detail being unintentionally (i guess) exposed in clojure 2. is null the only non-object in java?

Fredrik 2021-06-30T13:28:20.274400Z

nil doesn't have a type, 1 and "" do.

Ory Band 2021-06-30T13:28:44.274600Z

is there any other thing in java that doesn't have a type?

Ory Band 2021-06-30T13:28:54.274800Z

i'm really asking, i'm not too familiar with java

Fredrik 2021-06-30T13:29:41.275Z

I'm sorry I didn't make clear, in my last post I was talking about Clojure types.

Ory Band 2021-06-30T13:30:17.275200Z

ok, same question then. is nil the only non-type in clojure? why do i even have to think about this when writing clojure

Fredrik 2021-06-30T13:38:48.275400Z

Clojure doesn't hide the fact that it's a hosted language

octahedrion 2021-06-30T13:52:13.275600Z

"`nil`  doesn't have a type, 1 and "" do." but, (defprotocol T (t [this])) (extend-type nil T (t [this] 7)) (t nil) => 7

Ory Band 2021-06-30T13:53:32.275900Z

somebody call rich already and have him settle this discussion lol

octahedrion 2021-06-30T13:58:57.276200Z

actually pretty sure in Clojurescript one can extend-type nil IFn so that (nil) is valid

octahedrion 2021-06-30T14:14:13.276400Z

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

kongeor 2021-06-30T14:14:47.276600Z

@octo221 that's interesting. Can you try this with numbers?

octahedrion 2021-06-30T14:21:11.276900Z

yes:

(extend-type js/Number IFn (-invoke ([this op x] (op this x))))
#object[Function]
cljs.user=> (71 + 171)
242

kongeor 2021-06-30T14:21:55.277100Z

cool, thanks! 🙂

octahedrion 2021-06-30T14:27:06.277400Z

therefore:

cljs.user=> (extend-type js/Number IFn (-invoke ([this] this)))
(warning ignored) 
#object[Function]
cljs.user=> (1)
#object[Number 1]

Fredrik 2021-06-30T14:59:43.277900Z

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.

orpheus 2021-06-30T15:48:40.279600Z

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])

Fredrik 2021-06-30T15:48:45.279700Z

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 .

Fredrik 2021-06-30T15:51:12.279900Z

You could try #(do %) (or just use identity )

orpheus 2021-06-30T15:54:00.280300Z

ah thank you, identity is what I was looking for

😀 1
ghadi 2021-06-30T15:59:53.280600Z

fyi there is no early return in clojure

👍 1
dgb23 2021-06-30T16:01:44.280900Z

there is reduced

dgb23 2021-06-30T16:03:57.281100Z

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 '#(%)))

dgb23 2021-06-30T16:04:13.281300Z

it would expand like so: (fn* [%1] (%1))

dgb23 2021-06-30T16:04:58.281500Z

which shows why you’d have a problem when mapping over a vector of numbers

ghadi 2021-06-30T16:09:19.282200Z

(Reduced is not the same thing as early return)

2021-06-30T16:44:13.282500Z

user=> (read-string (str '#(%)))
(fn* [p1__2#] (p1__2#))
user=> '#(%)
(fn* [p1__6#] (p1__6#))
user=>

😄 1
2021-06-30T16:59:16.283100Z

@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

👍 1
2021-06-30T16:59:35.283300Z

oh I see someone up thread already demonstrated

2021-06-30T17:02:59.283500Z

> 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)

dgb23 2021-06-30T17:41:18.283900Z

ty @hiredman!

2021-06-30T19:15:19.286500Z

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)

2021-07-01T19:22:28.339700Z

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 doseqinstead 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)))

2021-07-02T17:51:16.414800Z

> 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

2021-07-02T17:52:18.415100Z

you can also use run! which is exactly like the two-arg version of map, except it returns nil and is eager

2021-07-02T17:52:39.415300Z

(ins)user=> (run! println "hello, world")
h
e
l
l
...
nil

2021-06-30T19:30:17.287500Z

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).

2021-06-30T19:32:27.287700Z

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.

2021-06-30T21:13:22.288700Z

What is doc Id? It might be irrelevant but it's not defined.

2021-06-30T21:14:52.288900Z

I think instead of dosync your update fn should just do both operations.

2021-06-30T21:23:34.289500Z

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?

Fredrik 2021-06-30T21:25:04.289700Z

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)

2021-06-30T21:26:24.289900Z

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.

2021-06-30T21:28:29.290100Z

@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!

2021-06-30T21:32:00.290300Z

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

Fredrik 2021-06-30T21:34:16.290600Z

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

2021-06-30T21:44:34.290800Z

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.

2021-06-30T21:45:01.291Z

Do run is, as fvr said, for lazy collections

2021-06-30T22:36:48.291300Z

to be clear, the only laziness in clojure is when something like map / filter etc. return a lazyseq, assoc is never lazy