clojure

New to Clojure? Try the #beginners channel. Official docs: https://clojure.org/ Searchable message archives: https://clojurians-log.clojureverse.org/
2021-01-18T01:32:21.187800Z

I've been pretty deep in this rabbit whole now, and looked at the code and all as well. And I think @andy.fingerhut is correct actually. Nil is not the empty list in Clojure, that's a departure explained here: https://clojure.org/reference/lazy but it used to be. And the code that defaults conj on nil to an empty list is 14 years old, I think it predates the switch to lazy-seq even, and it was written when conj actually took the coll last same as cons: (conj 1 []). At this point, I think its just what it is, 14+ years ago Rich just chose to default (conj nil 1) to use a PersistentList, and that's that. And much more recently, for transducers he chose to default the init for conj to vector.

2021-01-18T01:42:24.188200Z

My guess is that closer to Clojure's inception, Common Lisp idioms were often adopted, so big emphasis on lists and thus prepended elements when building them up, as well as pervasive nil-punning, thus treating empty list as nil and thus nil as empty list. So the original implementation of Clojure seem to behave consistently to that. Later, Rich started to realize that those CL idioms might not be ideal, and slowly started to break away from some of the CL idioms. The first big change was with making nil and empty list no longer the same thing. This is why sequences now don't return nil when they are empty, they return an empty construct of some sort instead, either an empty list, an empty sequence, or something of that sort. Now a seq is still either at least one element or nil. But a sequence can be empty, and a list can be empty. Even later, I think Rich started to realize that lists maybe just aren't even a very useful collection for user programs (even if useful for modeling code). So when implementing collections, vectors were used over lists for defaults.

2021-01-18T01:43:48.188500Z

Only Rich could corroborate this, but that's my best guess right now.

2021-01-18T01:46:44.188700Z

In practice that does seem to mean that collection functions when passed nil as the coll seem to default to treating it like a list, while sequence functions when passed nil seem to default to sequences, and transducers when not passed an init coll seem to default to vector.

2021-01-18T01:58:16.188900Z

In that sense, things are consistent, kind of like @p-himik was saying:

(conj nil 1) ;> (1)
(into nil [1]) ;> (1)
(conj) ;> []
(into) ;> []
But honestly, there are inconsistencies as well:
(pop '()) ;> java.lang.IllegalStateException: Can't pop empty list
(pop nil) ;> nil
(empty '()) ;> ()
(empty nil) ;> nil
So I'd go and say its "as consistent" as people managed to make it, but clearly overt time and as things were added it didn't always manage to follow the most consistent logic.

๐Ÿ‘ 1
2021-01-18T02:33:55.189200Z

The default for no-arg conj to return [] is as old as (conj nil x) returning (), I believe. What evidence do you have that it dates to when transducers were added @didibus ?

2021-01-18T02:37:25.189400Z

If you haven't watched this talk, or read the transcript, I would recommend it, if you are interested in how early some of these decisions were made (the talk doesn't explain all decisions, just some). https://github.com/matthiasn/talk-transcripts/blob/master/Hickey_Rich/ClojureIntroForLispProgrammers.md. It might help eliminate some of the guessing.

2021-01-18T02:50:10.189800Z

Hum, the code blame attributes it to the "transducer wip" commit. Maybe it was there before somehow as well, I didn't go further back in the history.

2021-01-18T02:53:21.190200Z

Yup, if I go further back in history, conj does not have a 0-arity, only the 2-ary.

2021-01-18T02:53:57.190700Z

This is the source of conj before the transducers commit:

(fn ^:static conj 
        ([coll x] (. clojure.lang.RT (conj coll x)))
        ([coll x & xs]
         (if xs
           (recur (conj coll x) (first xs) (next xs))
           (conj coll x)))))

2021-01-18T02:53:59.190900Z

how likely is a collision on (hash (fn []))?

2021-01-18T02:54:40.191Z

I see it now. Yes, 2014, "transducers wip" commit.

2021-01-18T02:55:15.191200Z

And the RT conj impl is the one that has the nil handling code:

static public IPersistentCollection conj(IPersistentCollection coll, Object x){
	if(coll == null)
		return new PersistentList(x);
	return coll.cons(x);
}

2021-01-18T02:55:54.191500Z

That has been there since 1.0 and probably since beginning.

2021-01-18T02:57:26.191700Z

Yup, since September 2007 it seems, before that it used to be:

static public IPersistentCollection conj(Object x, IPersistentCollection y) {
	if(y == null)
		return new PersistentList(x);
	return y.cons(x);
}

2021-01-18T02:58:05.192Z

Which is interesting, the coll came as the second arg

2021-01-18T02:59:00.192200Z

Are you interested academically, or are you planning on any part of your system relying upon that likelihood of collision? Depending on your goals, I'd recommend trying to avoid relying upon that likelihood of collision.

2021-01-18T02:59:43.192400Z

Oh interesting, I think Rich copy/pasted the code for cons, and renamed it conj. And then, in the next commit he changed the impl, that's why at first it has the arg order of cons.

2021-01-18T03:00:52.192600Z

I built a system on it being unique and now it looks like it collides pretty frequently in practice

2021-01-18T03:01:10.192800Z

Relying on hash codes being unique is almost always a bad idea.

2021-01-18T03:02:00.193Z

Unless you are talking about UUID's or something, with so many bits that are uniformly distributed that you can prove miniscule probabilities of collision.

2021-01-18T03:02:52.193200Z

With 32 bits of hash result, if they were uniformly randomly distributed, the so-called birthday paradox predicts about a 50% chance of at least one collision if you get up to 2^16 = 65,536 of them.

2021-01-18T03:03:18.193400Z

I have no reason to think that hash on Clojure functions is uniformly randomly distributed, though.

2021-01-18T03:05:24.193600Z

user=> (count (into #{} (take 10000 (repeatedly #(hash (fn [] ))))))
9988

2021-01-18T03:06:05.193800Z

yeah hmm i'll switch to a UUID of some sort

2021-01-18T03:06:07.194Z

There are 2 collisions already.

ghadi 2021-01-18T03:17:16.194800Z

These hashes are not cryptographically secure

ghadi 2021-01-18T03:17:54.195700Z

collisions are frequent, and the output is 32-bit

2021-01-18T03:20:13.195900Z

Actually, I wonder if that's why conj defaults to emptylist, since cons did, which made sense, since cons constructs a list, and the code was copy/pasted from it and already had that implementation, so I think that's what happened ๐Ÿ˜›

๐Ÿ‘ 1
emccue 2021-01-18T03:48:06.196100Z

always assume hashCode returns 1 unconditionally

๐Ÿ˜‚ 1
emccue 2021-01-18T03:48:17.196300Z

but map lookups are done via magic

2021-01-18T04:17:22.196600Z

Promise is definitely thread safe

2021-01-18T04:19:08.196800Z

But both of the implementations I'm seeing here don't seem to be thread safe

2021-01-18T04:20:07.197Z

But it's not clear what you mean by thread safe exactly. Like your code can have two callers race for who gets to set the value first

2021-01-18T04:20:37.197200Z

And that's true with deliver on a promise as well

2021-01-18T04:53:46.197400Z

Like I think you're trying to only run real-compute once?

zendevil 2021-01-18T05:14:04.197600Z

commenting out wrap-csrf gives a response of 500

zendevil 2021-01-18T05:14:18.197800Z

I expect a response of 2xx

zendevil 2021-01-18T05:33:05.198Z

thanks. The 500 was due to an error in the handler

zendevil 2021-01-18T05:33:09.198200Z

now it works

2021-01-18T06:38:36.199200Z

Oh wow, I missed that ClojureCLR now supports running on .Net Core, that's a big achievement. Seems everything works except AOT, and its up with Clojure 1.10 in featureset. https://groups.google.com/g/clojure-clr/c/MdxEbo0znAw

๐Ÿ‘ 6
๐Ÿ˜ฎ 4
simongray 2021-01-18T15:24:05.205100Z

Do any libraries even exist for ClojureCLR?

2021-01-18T17:24:02.212600Z

There's https://github.com/arcadia-unity/Arcadia but I think it might use its own clojureCLR fork

2021-01-18T18:21:50.216500Z

Well you can use interop with the .Net ecosystem, similar to Java interop for Clojure JVM I believe.

2021-01-18T18:23:10.216700Z

But I don't think ClojureCLR is widely used yet, so I haven't seen many Clojure based libs for it. I'd assume most lib that works for Cljs as well as Clojure would be easy enough to add support for ClojureCLR.

2021-01-18T07:03:43.199500Z

yup... i'm happy that there's an effort on this.. i tried the repl, it works fine so far... hope the rest (compilation, etc) will follow... definetly watching this space :-)

2021-01-18T07:05:35.199700Z

Ya, its been a while since I've used the .net ecosystem, but blazor and xamarin seem nice, and compilation to web assembly. So if ClojureCLR can get that working it could be interesting.

2021-01-18T07:19:33.199900Z

yup... i'm learning F# on the side also... in fact, i have both fsharp and clojure slack channels opened.. hehe.. i hope both communities interact more and share... there's bolero for F# (to do blazor stuff).. maybe clojureclr could leverage that as well

vemv 2021-01-18T17:18:00.210Z

Would you say this can be a normal scenario?

vemv 2021-01-18T17:18:50.211100Z

I don't think I have a leak because all my app's objects/classes get eventually GCed. But I'm not sure if it's intended usage to have so many classloaders around

dpsutton 2021-01-18T17:20:25.211400Z

are you using nrepl?

vemv 2021-01-18T17:20:34.211600Z

yes

dpsutton 2021-01-18T17:21:21.212400Z

can't remember the details but i think i remember that nrepl executions can result in new classloader objects. if you can compare against an instance using a socket server or just no repl at all you might see a large difference

๐Ÿ‘ 1
vemv 2021-01-18T17:26:54.212900Z

Thanks for the pointer! It lead me to https://github.com/nrepl/nrepl/issues/8 which looks related, as I'm using [org.clojure/tools.nrepl "0.2.13"] which predates that issue (that dep is outdated, I know)

motform 2021-01-18T18:01:59.216200Z

I am a bit confused to why I cannot use var in this function: (defn fname [f] (-> f var meta :name)) I understand the reader macro would not work (since it would be quoting the literal symbol in that ns?) and guess that it is because var is a special form that it fails, but why? Is the effect the same as if I would have used the reader macro?

2021-01-18T18:25:43.217400Z

var is a special form that works more like a macro than a function. (var f) also tries to look up f, not the value of f

๐Ÿ‘ 1
phronmophobic 2021-01-18T18:26:07.218Z

you probably want something like ns-resolve or resolve

phronmophobic 2021-01-18T18:27:17.218700Z

although that will only work if you have the actual symbol at some point

2021-01-18T18:28:41.219500Z

ns-resolve is very weird, but for whatever reason people always seem to grab that first rather than the simpler resolve

motform 2021-01-18T18:31:05.221100Z

I have a vector of functions (a middleware stack) and I want to make a simple tapper that tap>s the context after each middleware, function name included, but maybe they have to be quoted to be resolvable?

phronmophobic 2021-01-18T18:32:32.222200Z

I remember that someone was able to obtain a reasonable name from a function using a snippet they found in clojure's spec source code, but I can't seem to find it

2021-01-18T18:33:45.222700Z

yeah, resolve needs a symbol, clojure doesn't really encourage function / symbol cross translation

phronmophobic 2021-01-18T18:35:57.222900Z

maybe it was:

(defn- fn-sym [^Object f]
  (let [[_ f-ns f-n] (re-matches #"(.*)\$(.*?)(__[0-9]+)?" (.. f getClass getName))]
    ;; check for anonymous function
    (when (not= "fn" f-n)
      (symbol (clojure.lang.Compiler/demunge f-ns) (clojure.lang.Compiler/demunge f-n)))))

2021-01-18T18:36:41.223500Z

IMHO you can kind of make that work, but there's likely a much simpler solution that doesn't require turning functions into symbols

2021-01-18T18:37:22.224100Z

eg. providing the var rather than the function in the place where the middleware is added

phronmophobic 2021-01-18T18:39:24.224900Z

agreed. it wouldn't be my first choice.

borkdude 2021-01-18T18:52:57.225500Z

There is a hack like the above in the clojure.spec.alpha ns as well

borkdude 2021-01-18T18:53:57.225900Z

oh it's literally that code ;)

phronmophobic 2021-01-18T18:54:38.226700Z

I should have linked to it earlier, but I found it using jump to source in my editor before I could find it on github

borkdude 2021-01-18T18:55:06.227200Z

@love.lagerkvist You can also attach metadata to clojure functions, or keep a map of fn obj -> name

motform 2021-01-18T19:03:59.229800Z

oh, that a hack worthy of its name. @borkdude I guess that would be the saner choice, considering the number of functions is quite small. There is also (de-)munge I guess, not sure about the characteristics of that, but it sorta does what I want.

(require '[clojure.main :as m])

((fn [f] (-> f class str m/demunge)) util/qualified-keyed-ns-publics)
; => "class motform.bestรฅ.util/qualified-keyed-ns-publics"

2021-01-18T19:05:12.230300Z

the fact that "munge" is an ugly word is intentional

phronmophobic 2021-01-18T19:05:59.230800Z

I like recommending dirty hacks and appealing to authority at the same time ๐Ÿ˜ˆ

2021-01-18T19:06:40.231300Z

the authorities are working under design constraints that probably don't apply to your project

phronmophobic 2021-01-18T19:07:00.231600Z

if you only knew the power of the dark side