This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2020-06-10
Channels
- # announcements (1)
- # babashka (178)
- # beginners (216)
- # bootstrapped-cljs (1)
- # brompton (5)
- # calva (3)
- # chlorine-clover (1)
- # clj-kondo (2)
- # cljdoc (37)
- # cljfx (4)
- # cljs-dev (2)
- # clojure (360)
- # clojure-chile (8)
- # clojure-europe (3)
- # clojure-italy (5)
- # clojure-nl (9)
- # clojure-spec (2)
- # clojure-sweden (1)
- # clojure-uk (61)
- # clojuredesign-podcast (1)
- # clojurescript (83)
- # clr (2)
- # conjure (4)
- # core-async (14)
- # cursive (20)
- # data-science (2)
- # datomic (15)
- # docker (11)
- # emotion-cljs (1)
- # figwheel-main (28)
- # find-my-lib (1)
- # fulcro (46)
- # helix (16)
- # honeysql (14)
- # jobs (10)
- # jobs-discuss (17)
- # joker (1)
- # juxt (9)
- # kaocha (8)
- # leiningen (3)
- # meander (3)
- # news-and-articles (1)
- # off-topic (110)
- # pathom (7)
- # pedestal (4)
- # protojure (2)
- # re-frame (12)
- # reagent (25)
- # ring (4)
- # shadow-cljs (109)
- # spacemacs (9)
- # specter (1)
- # sql (3)
- # tools-deps (23)
fun bug I just tracked down, caused by a fact I didn't know: sorted-set-by
uses the comparator to assert identity
I had something akin to:
(sorted-set-by :order #{:order 2 :foo "bar"} #{:order 2 :foo "baz"})
The comparators guide article on http://Clojure.org mentions this and many other useful facts about using comparator functions in Clojure
Without that behavior, there would be no way to maintain a consistent order between elements considered equal by your comparator function, unless perhaps one created another kind of sorted collection that had a “tie breaker” comparator for things considered equal by the primary comparator fan
yeah. for my case, I don't need a stable order between elements whose orders are equal
on the Clojure website at https://clojure.org/reference/metadata, where it describes vary-meta
, it reads
Returns an object of the same type and value as obj, with (apply f (meta obj) f & args) as its metadata.
Shouldn’t it be
(apply f (meta obj) args)
@srijayanth Alex merged my PR so vary-meta
is now correct on that page. Thank you for spotting that and mentioning it, so the docs can be improved!
Looks like a typo, the docstring says
user=> (doc vary-meta)
-------------------------
clojure.core/vary-meta
([obj f & args])
Returns an object of the same type and value as obj, with
(apply f (meta obj) args) as its metadata.
nil
user=>
The arguments are listed incorrectly too.
@srijayanth Good catch on that. I submitted a PR to fix it.
Since we have namespaces keywords now, is this acceptable or just a hack?
(defprotocol Service
(start [this])
(stop [this]))
(extend-protocol Service
APersistentMap
(start [this]
(if-let [start-fn (::start-fn this)] ; Note: namespaced key
(start-fn this)
(throw (ex-info "This map does not satisfy protocol 'Service'"
{:map this}))))
(stop [this]
;; Similar to start, omitted
))
Then all maps having the right namespaced keys "automatically" satisfy the protocol without using with-meta
.Nothing wrong with with-meta
except it is per map. In the above approach, you write the rule only once.
You are right, it is still per map. Every map still needs to add the right key. with-meta
is slightly longer I think.
@hindol.adhya I'm not sure what problem that solves. ::start-fn
resolves to a specific qualified keyword, based on the namespace that code is in. How is that better than adding metadata to a map that refers to protocols?
You've introduced a convention (that might be different for each library) compared to a standard way to interact with every protocol.
I meant, the ns
that is using this will have the :<right ns>/start-fn
key. ::
is just for this namespace where I have the protocol.
Right, but that is a convention that is separate from the protocol itself.
You've called it start-fn
(and, presumably, stop-fn
) in your service -- but other protocols in other namespaces could do something differently.
It's much better to use the fully-qualified names of the protocol functions themselves.
You've also restricted it to just a hash map -- protocols can be satisfied via metadata for any IObj
(so, in answer to your question, I'd say it's a horrible hack!)
I agree with the "deviation from standard convention is a bad thing" bit. But I would argue a fully qualified protocol fn is equivalent to a fully qualified keyword.
Protocols can be extended to a lot more types than a qualified keyword can participate in.
(with-meta [1 2 3] {'com.stuartsierra.component/start (fn [v] ...)})
for example
Yes, and the protocol is still available for every other type. I meant this just for maps.
Indeed, I do that here: https://github.com/seancorfield/next-jdbc/blob/master/src/next/jdbc/connection.clj#L276
@hindol.adhya I think it's a mistake to take a generalized feature like this and add a convention that restricts it to a narrower type...
I am kind of convinced now that this is a bad idea. Mainly because with-meta
is more explicit and the above approach is more implicit.
$ clj -e '(clojure.lang.PersistentQueue/EMPTY)'
#object[clojure.lang.PersistentQueue 0x25ddbbbb "clojure.lang.PersistentQueue@1"]
$ clj -e 'clojure.lang.PersistentQueue/EMPTY'
#object[clojure.lang.PersistentQueue 0x2c104774 "clojure.lang.PersistentQueue@1"]
Calling a field like a method with no args is valid in Java interop?Heh.
user=> (macroexpand '(clojure.lang.PersistentQueue/EMPTY))
(. clojure.lang.PersistentQueue EMPTY)
But I don't think I've seen it documented anywhere.Huh. Given this line
maybeField = Reflector.getMethods(c, 0, munge(sym.name), true).size() == 0;
and what follows it in Compiler.java
, it seems that (. A x)
will call x if it's a method and will resolve x if it's an attribute.Actually, it is documented at https://clojure.org/reference/java_interop#_the_dot_special_form
If the second operand is a symbol and no args are supplied it is taken to be a field access - the name of the field is the name of the symbol, and the value of the expression is the value of the field, unless there is a no argument public method of the same name, in which case it resolves to a call to the method.
has anyone perhaps used https://github.com/xsc/rewrite-clj to dissoc keys from maps in defs in clj source? i'm trying but i don't grok zippers all that well, or i don't get how this lib works 😅 perhaps you, @borkdude?
ok i'll dig some more, thanks
A new toy to rewrite code: https://github.com/babashka/pod-babashka-parcera
could i zip to find, and then drop back to basic node ops from there?
yes, you can can find the node and then use z/replace
and transform the node using some function
ok nice, i think i got it
a java server. i got it right. culling unused namespaces from our i18n dictionaries 😆
is there a way to have edn parsing "leave alone" a tag literal? I.e. {:foo #something "here" #another "here"}
i want to specify a custom data-reader for the #something
tag, but #another
should be left alone (i.e. i need to manipulate a edn file but ignore some tag literals)
A potential solution is to use {:default (fn [t v] (tagged-literal t v))}
as options for edn/read
When you read EDN, you get a Clojure data structure as an output. The reader tags intrinsically cannot be preserved there as they exist only in the scope of reading stuff (thus the name, "reader tag").
Ah, I see now that I misunderstood that statement in parentheses in OP.
Seems like your solution should works, yes. But only if you handle the resulting object explicitly. E.g. if you want to process them in any way, you will have to unwrap and wrap the values in tagged-literal
.
In this particular case, as I am merely manipulating a edn file with babashka, i wanted to only do something with some of the tags, and keep the others as tag literals (to be eventually read by another program somewhere else down the pipeline)
Yeah, I see now, sorry for the confusion. I didn't realize initially that you're writing the file back.
@U0AD3JSHL yes, :default tagged-literal
should take care of this
https://twitter.com/andrestaltz asked on Twitter > What’s the software or tool or language or framework where you’ve felt the most productive? Clojure seems to be pretty popular aswer. 🎉 If you want to answer his poll, it’s still open for 6 hours: https://twitter.com/andrestaltz/status/1270438843880259590
Hi, I'm new here! I wanted to get a few recommendations for must have books that teach systems architecture and design. I became interested in clojure back in 2012 after starting my first programming job but only got the chance to use it for toy projects. Now I've come a long way but still don't feel have a true reference for large scale system design. Specifically at my job I've been tasked with rewriting a large batch transaction processing system and would like to read about the state of the art in this field.
“Designing Data-Intensive Applications” has been recommended to me multiple times and it is currently sitting at the top of my to-read pile 😛
I am in need of an “ordered” set: a set that I can conj objects onto and use a custom comparator so that when I retrieve them, they are in sorted order. but multiple objects can have the same order, e.g.:
(ordered-set
(comp compare :height)
{:height 0 :foo "bar"}
{:height 3 :foo "asdf"}
{:height 0 :foo "baz"})
any thoughts on what a good data structure might be for this?There is sorted-set https://clojuredocs.org/clojure.core/sorted-set
sorted-set-by to specify comparator https://clojuredocs.org/clojure.core/sorted-set-by
(sorted-set-by
(comp compare :height)
{:height 0 :foo "bar"}
{:height 3 :foo "asdf"}
{:height 0 :foo "baz"})
;; => #{{:height 0 :foo "baz"}
;; {:height 3 :foo "asdf"}}
JS, but if there’s a good example of this on the JVM I would appreciate that as well
My initial thought was https://docs.oracle.com/javase/7/docs/api/java/util/PriorityQueue.html#PriorityQueue(int,%20java.util.Comparator)
@lilactown (def key (juxt :height identity))
? Or what would "the same order" mean?
(sorted-set-by
(comp < :height)
{:height 0 :foo "bar"}
{:height 3 :foo "asdf"}
{:height 0 :foo "baz"})
This for me returns a set of the 3 elements@lilactown If "same order" in your case doesn't care about the order of the items with the same height, then you can just add the item itself to be a part of the key, no?
Remember how keywords are 1- and 2-arity functions? :) Well, a comparator is a 2-arity function...
@lilactown look at the “second-<-with-tie-break” example here https://clojuredocs.org/clojure.core/sorted-set-by
It says:
;; Here is one way to write a good comparison function. When the two
;; objects are equal in the parts we care about, use the tie-breaker
;; 'compare' on the whole values to give them a consistent order that
;; is only equal if the entire values are equal.
Heh, pretty much the same as what I offered, only with a more manual/granular approach.
Passing a function that returns positive or negativer numbers seems to not remove same keys
(sorted-set-by
(fn [a b] (if (> (:height a) (:height b))
1
-1))
{:height 0 :foo "bar"}
{:height 3 :foo "asdf"}
{:height 0 :foo "baz"})
=> #{{:height 0, :foo "baz"} {:height 0, :foo "bar"} {:height 3, :foo "asdf"}}
It may seem fine, but it will most definitely result in unpredictable behavior given enough data, simply because in your case among two maps with the same height, the first one is always smaller than the second one. Even if you swap them.
When you say unpredictable, is that for the same elements, it might not always return the same order?
(compare {:height 0 :foo "bar"} {:height 3 :foo "asdf"})
Execution error (ClassCastException) at (REPL:1).Yea… no examples here… either https://clojuredocs.org/clojure.core/compare I guess you can say “this map is equal to that”; but hard to say when a map is “less” or “bigger than” another map
user=> (def height (juxt :height hash))
user=> (defn compare-height [a b]
(compare (height a) (height b)))
user=> (sorted-set-by
compare-height
{:height 0 :foo "bar"}
{:height 0 :foo "baz"}
{:height 3 :foo "asdf"}
{:height 2 :foo "jkl"})
#{{:height 0, :foo "bar"} {:height 0, :foo "baz"} {:height 2, :foo "jkl"} {:height 3, :foo "asdf"}}
@lilactown possibly, yes… if you care about same instance only (I think)
in my case, I care about same instance only. I’m not actually using maps, but deftype
objects
How would identity be used instead of hash? hash on maps make them comparable, but identity doesn’t
@U6CN6JQ22 yea… not sure
One possible way I’m thinking is str… not sure how consistent is that, but that is comparable
(defn height-with-tie-break [x y]
(let [c (compare (get x :height) (get y :height))]
(if (not= c 0)
c
;; Otherwise we don't care as long as ties are broken
;; consistently.
(compare (str x) (str y)))))
(def s3 (sorted-set-by
height-with-tie-break
{:height 0 :foo "bar"}
{:height 3 :foo "asdf"}
{:height 0 :foo "baz"}))
Oh, wow. I must be sleeping or something - I treated the topic of comparing maps and the topic of hashes as two completely different topics and didn't realize that they're closely related.
@lilactown If I understand "I care about same instance only" correctly, then you should be able to use System/identityHashCode
. Although I'm not sure how portable it is since there are no explicit guarantees about it.
yeah, I only need it to be stable across different “height”, intra-height ordering I don’t think I care about
@lilactown another way is to turn the maps into vectors for comparison; I believe this is stable; but you should double-check:
(defn height-with-tie-break [x y]
(let [c (compare (get x :height) (get y :height))]
(if (not= c 0)
c
;; Otherwise we don't care as long as ties are broken
;; consistently.
(compare (vec (seq x)) (vec (seq y))))))
(def s3 (sorted-set-by
height-with-tie-break
{:height 0 :foo "bar"}
{:height 3 :foo "asdf"}
{:height 0 :foo "baz"}
{:height 2 :foo "baz"}))
s3 => #{{:height 0, :foo “bar”} {:height 0, :foo “baz”} {:height 2, :foo “baz”} {:height 3, :foo “asdf”}}
this is a pretty tight loop, I’d like to avoid allocating as much as possible. I’ll need to fiddle with it
But I’m not sure about “equivalence” across both… but independently should work on each
@lilactown yea this is not the most performant option I think; wasn’t sure what’s your perf requirement here
This was probably stated clearly above already, but one way to use sorted sets as already built into Clojure is to make a comparator function that is "more specific" than you actually care about, if you want to keep "duplicates", i.e. if you really only care about sorting by height, but you have some other value you can compute from, or extract from, the objects you want to sort, and it is unique across all of them, e.g. an id field, a name, or some vector that is a combination of multiple such things that you know must be unique, and it is comparable, e.g. strings, numbers, keywords, symbols, etc., then you can use those tie-breaker values in your comparator function, and you will never "eliminate duplicates", because according to the comparator function, there won't be any.
Clojure's compare function does not work on maps, because for those values, and sets, there is no good way I can think of to define a total order among all possible maps (nor between sets).
You can make a comparator function that compares two maps, but they will be custom for the kind of maps you want to compare, and you get to define the field or fields that are significant for comparing them.
I think I can reframe this to be, what I want is really a “partial order” w.r.t. some field
Clojure's sorted sets and maps work with comparators that give a total order. Comparator functions cannot represent partial orders.
They can represent total orders with bugs 🙂. But in order to represent a partial order, there would have to be a way to return a value meaning "no ordering relationship exists in the partial order between the two values given as parameters"
I believe what you want is a total order, but with many things that are different according to clojure.core/=, but equal according to your comparator, to be stored independently in the collection, with "duplicates" allowed ("duplicates" meaning equal according to your comparator function, but different according to clojure.core/=). Clojure's built in sorted sets and maps don't support that, as you have already learned.
My suggestion above was to "make up" a total order, where things you want to be equal according to your comparator function, have some "I don't care so much what it is, but it is determinstic" total ordering between items you don't care what order they occur in.
The main reason I suggest that is: you then can use the existing sorted set and map implementations, rather than creating a new custom one.
I think I would rather create my own custom data structure than bastardize the sorted-sets
the code that uses this data structure lives at the center of a framework for doing reactive computations. I’m trying to be very sensitive to both time and space when it comes to how I build this, even if it is just a first pass
to me, an array of sets seems like a clear win over trying to construct a total order on top of what I know to be partial order
Or perhaps a sorted-map where the values are sets of things you want to consider as equal?
FWIW I rewatched this video again https://youtu.be/G6a5G5i4gQU after the discussion here, which is basically someone explaining how they implemented in OCaml exactly what I’m trying to build. at https://youtu.be/G6a5G5i4gQU?t=2852 they explicitly talk about moving away from a total order to a partial order of the nodes and what data structure they used (an array of lists) 😄
the way I need to use this structure is I’m taking the “first” element from the set, doing an operation, and then adding a bunch of new elements. so the ordering is rebalanced frequently
Thanks @lilactown! This looks promising
I would use a queue and a separate "seen" set to avoid re-processing
I just find it to be clearer in my head. I doubt the perf difference matters as it's probably dominated by the processing
general shape:
(loop [q (conj (PersistentQueue/EMPTY) root)
seen #{}]
(when-let [item (peek q)]
(if (seen item)
(recur (pop q) seen)
(do
(process item)
(recur (pop q) (conj seen item))))))
if you need a custom "ordering" for processing
a general question about practice here: it seems safe to generate and distribute a selfsigned certificate pem and keystore.jks that only refers to localhost
and say www.localhost
to use for a local devserver
Does anyone know of examples using datafy
to wrap an HTTP API? Is this a bad idea? :thinking_face:
i’m waiting for the day that someone builds a datafy
/ nav
interface for HATEOAS / REST 😄
"What do you mean I can't use datafy
on this API because it's not REST and just HTTP? It says RESTful right there!"
lol yeah. the utility of such a thing would be close to the number of APIs that are actually REST - aka approaching 0 😛
but maybe it’s the incentive we need! Make it REST, and you get datafy
/ nav
support!
It may sound like a story about unicorns, but I actually know a guy that has implemented a perfectly fine REST API with a client for it. Later he said something along the lines of "never again" and "better go ad-hoc".
This is from another discussion, but I was wondering if somebody knows: how consistent is (str {:a 1}), aka (str …) on maps… does it produce the same string every time for the same map (I assume yes)? What about across Clojure vs ClojureScript?
for the same instance, yes, seq order is stable
for clj vs cljs, not guaranteed and I wouldn't rely on them being the same
@alexmiller thanks! That’s actually the important part; I assume same instance, you mean like consistent version of Clojure? Or that can change across JVM instances of the same version?
if you mean same as in identical?
yes, but it will definitely print equal small maps differently from one another
user=> (let [a {:a 0 :b 1}
b {:b 1 :a 0}]
[(= a b) (= (str a) (str b))])
[true false]
I mean if you have the same object in the same vm and print it twice, it will be the same
no guarantee across jvm versions, clojure versions, jvm executions, etc
maps are unordered, don't rely on order
@alexmiller I see, makes sense; thanks again!
the actual order is based on map impl, key hash, and possibly on the history of the map. hashing has changed in the past (in Clojure 1.6 most recently)
yea… hm… that stemmed from another discussion… what’s the best way to to (compare …) maps; is that even recommended to do?
as in comparator, for sorting?
you can compare by hash
(ins)user=> (hash {:a 0 :b 1})
1561470772
(ins)user=> (hash {:b 1 :a 0})
1561470772
of course hash is lossy...
right, a collision would not be sorted in a stable way
and don't use hash alone to remove dups, obviously
but other than that, the sorting will work
you could compare hash, and when not identical, compare the set of hashes of entries
but this is half baked, it's an interesting problem
you're going to get to a point too where you want to ignore some differences and then you're really sunk
@raspasov if you have some domain logic that lets you select keys and be sure of types, you could compare the vector of "interesting" keys
@noisesmith It was somebody else’s problem 🙂 I just got curious…
See my next comment 🙂 It’s in the context of comparators… so in addition to “is something equal”, figure out a way to say a map is “less than” or “bigger than” another map
(defn height-with-tie-break [x y]
(let [c (compare (get x :height) (get y :height))]
(if (not= c 0)
c
;; Otherwise we don't care as long as ties are broken consistently
(compare (vec (seq x)) (vec (seq y))))))
(def s3 (sorted-set-by
height-with-tie-break
{:height 0 :foo "bar"}
{:height 3 :foo "asdf"}
{:height 0 :foo "baz"}
{:height 2 :foo "baz"}))
Sort by :height, if that is equal, how to tie break consistentlyPossibly, this is the wrong approach about this… a map cannot be fundamentally “bigger than” or “less than” another map because it is, like @alexmiller said, un-ordered
so trying to use consistent order where there isn’t one by definition is probably the wrong way of solving that problem
Thanks @dpsutton @noisesmith @alexmiller for input
clojure's persistent maps are unordered. but you're talking about ordering the set of maps which contain keys :height
and :foo
. that doesn't seem fundamentally impossible
There was a great talk many years ago about how to help a team migrate to clojure(or just new languages in general, but using Clojure to illustrate it)
I can’t remember whose talk it was and I can’t find it any more. Did I hallucinate the whole thing?
@dpsutton right, if you limit your domain to those two keys :height and :food - yes… I was trying to invent a general way (which seems like a bad idea)
If you want a general way, then you need a general way to create a total order on all possible immutable values that can be map keys, which is a lot of values whose types we know now, and many that haven't been created yet.
The http://clojure.org guide on comparators has some ramblings and code near the end where I started trying to come up with a general comparator that works for arbitrary pairs of immutable values, which if it isn't one of Clojure's built in collection types or a handful of others, compares them by their JVM class name first, but if you get a JVM object of an unknown class, that has no compareTo method, then you are stuck
Pick a reasonable subset of values that you can write a "nearly fully general" comparator function for, and delimit/document what those are, and you can then use that to devise a comparator function for two arbitrary maps
There just isn't that much need for such a thing, that I am aware of.
i think this should work
(sorted-set-by
#(let [f (juxt :height :foo)]
(compare (f %1) (f %2)))
{:height 0 :foo "bar"}
{:height 3 :foo "asdf"}
{:height 0 :foo "baz"}
{:height 2 :foo "baz"})
I guess the problem becomes… in the case of map… what if those two keys are equal again, :height and :foo - how do you tie break then in the case of a map? 🙂 It’s like a never ending story
@dpsutton right… I guess if your maps are guaranteed to NOT have any other keys except for :height and :foo, you can do it
(sorted-set-by
#(let [f (juxt :height :foo)]
(compare (f %1) (f %2)))
{:height 0 :foo "bar"}
{:height 0 :foo "bar" :c "HERE I COME"}
{:height 3 :foo "asdf"}
{:height 0 :foo "baz"}
{:height 2 :foo "baz"})
My personal conclusion is, “don’t do it” lol… unless you are really sure you just want those two keys (:height and :foo)
(sorted-set-by
(fn [a b]
(let [f (juxt :height (fn [m] (into [] (apply sorted-map m))))]
(compare (f a) (f b))))
{:height 0 :foo "bar"}
{:height 3 :foo "asdf"}
{:height 0 :foo "baz"}
{:height 2 :foo "baz"})
would this work?it would if matching keys always shared matching vals, and I think (sort m)
would be the same as that anonymous fn in behavior, with less complexity
(sorted-set-by
(fn [a b]
(let [f (juxt :height (fn [m] (into [] (apply sorted-map m))))]
(compare (f a) (f b))))
{:height 0 :foo "bar"}
{:height 0 :foo "bar" :c "HERE I COME"}
{:height 3 :foo "asdf"}
{:height 0 :foo "baz"}
{:height 2 :foo "baz"})
No value supplied for key: [:c “HERE I COME”]hm, is it because the maps don't have the same keys? if so, could add a step to add missing keys @raspasov
@isak I think, fundamentally, trying to apply order where there isn’t one is just not a very good approach; a comparator requires not only “equal” semantics, but also “less than” and “greater than”
Extracting “less than” and “greater than” semantics from a generalized un-ordered collection sounds like a mathematical impossibility; maybe a certain heuristic approach can work reasonably well, but I don’t think a general and consistent solution is possible
That’s why if you start out with vectors, it’s much easier, they have order built-in
I think you can - worst case you just add more things to the comparison, like .getType, but I won't argue. Imo the real problem would be to make a generalized solution that is also efficient
here’s a POC using vectors of sets:
(defn poset [order-fn & els]
(loop [v (vec (repeat (count els) #{}))
els els]
(if-some [el (first els)]
(recur (update v (order-fn el) conj el)
(rest els))
(with-meta v {:order-fn order-fn}))))
(defn conj-poset [poset el]
(let [order-fn (:order-fn (meta poset))
order (order-fn el)
;; grow the vector in case order > size of poset
poset' (into poset (repeat (- order (count poset)) #{}))]
(update poset' order conj el)))
(defn disj-poset [poset el]
(let [order-fn (:order-fn (meta poset))
order (order-fn el)]
(update poset order disj el)))
(defn first-poset [poset]
(ffirst poset))
v
has the size of (count els)
, but later you index it by order-fn
. So order-fn
has to always return something less than (count els)
. Given the previous example with :height
, it seems strange.
yeah that’s probably an error, it just so happened that my REPL testing always had :height
< (count els)
I would want to either pass in an arg for how much to allocate or get the max order of the els passed in and allocate that much
Also, you may end up with a vector that's mostly empty. E.g. one call order-fn
returns 0 and another one returns 1,000,000. Not great.
And what if order-fn
returns something that's not a non-negative integer?
I would probably use a sorted map of the values of order-fn
to the sets of the elements.
I might distill this into a lib that implements the appropriate interfaces/protocols
ahaha, right. sorry, this was a solution to a problem I posted about above: https://clojurians.slack.com/archives/C03S1KBA2/p1591805338491800
the problem is that sorted-set-by
is built for a total ordering, so you end up having to hack on to the comparison fn how to deal with when a piece of data that your ordering is =
but the elements are actually distinct
👋 I’ve been starting up a socket repl and specifying the port as 0, which IIRC is a unix convention for “find some available port”. This has been working well for me, but then I have to find out what the actual port is before I can connect.
Just in case it might be helpful to anyone, here’s what I’m running when I start my REPL:
(println "Socket REPL listening on port" (.getLocalPort (get-in @#'clojure.core.server/servers ["repl" :socket])))
why don't you just tell it a port to use?
Because I’m sometimes working on multiple projects simultaneously, each with its own socket repl. If I hard-code the port numbers, then they’ll either conflict, or I’ll have to remember which port number I’d assigned to which project. Which I don’t think I can do…
(and perhaps worth noting there can be multiple Socket REPLs with names corresponding to the JVM option name used to start each, and so the get-in
would need a different string from "repl"
)
It's interesting, I hadn't even thought to try port 0
. That is neat. But discoverability is kind of ugly.
https://docs.oracle.com/javase/7/docs/api/java/net/ServerSocket.html#ServerSocket(int) > A port number of 0 means that the port number is automatically allocated, typically from an ephemeral port range. This port number can then be retrieved by calling getLocalPort.
Yeah I saw that in the Java docs, but it doesn’t say where the convention came from. The docs don’t claim it originated in Java. (Not saying that that’s proof that it did not.)
I am pretty sure the port=0 choosing a local free port on the system originated with the BSD socket API, at the C level.
From https://www.grc.com/port_0.htm → > The designers of the original Berkeley UNIX “Sockets” interface, upon which much of the technology and practice we use today is based, set aside the specification of “port 0" to be used as a sort of “wild card” port. When programming the Sockets interface, the provision of a zero value is generally taken to mean “let the system choose one for me”. Programmers who specify “port 0” know that it is an invalid port. They are asking the operating system to pick and assign whatever non-zero port is available and appropriate for their purpose.
yeah, it's a Posix thing for sure
seanc@DESKTOP-QU2UJ1N:~/clojure$ clj -J-Dclojure.server.repl='{:port,0,:accept,clojure.core.server/repl}' -e "(.getLocalPort,(get-in,@#'clojure.core.server/servers,[\"repl\",:socket]))" -r
60438
user=>
seanc@DESKTOP-QU2UJ1N:~/clojure$ clj -J-Dclojure.server.repl='{:port,0,:accept,clojure.core.server/repl}' -e "(.getLocalPort,(get-in,@#'clojure.core.server/servers,[\"repl\",:socket]))" -r
60439
user=>
(that's WSL 1 on Windows, BTW).Ah, the berkeley socket api is closely followed by windows too, makes sense that it's pretty universal then :)
I only learned of it recently myself, via Simon Willison, when he created this ticket: https://github.com/encode/uvicorn/issues/530
s/closely followed/copy pasted/ :D
@seancorfield can you think of some trick to get my project to automatically print out the port every time I start my repl with a particular tools.deps alias? I tried to naïvely add it to :main-opts
as a -e
arg but ran into the usual issues with escaping. And then I discovered that -r
and -e
seem to be mutually exclusive, so if I specify -e
then the process exits after the expression is evaluated, and if I specify -r
along with the -e
then the repl starts but the expression doesn’t seem to be evaluated.
oh wait, it seems to have worked for you in the snippets you posted above — I’ll try again 😅
Confirmed that it works on Powershell (Windows) too -- I added :socket-zero
to my dot clojure file, ran clj -A:socket-zero -r
on Powershell, then attached to it from WSL:
$ telnet 127.0.0.1 60468
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.
user=> (System/getProperty "user.dir")
"C:\\Users\\seanc\\clojure"
user=>
@aviflax https://github.com/seancorfield/dot-clojure/commit/c19c0224f072e9e9a8aa6da0979599f8dbecba86 -- note the comment about -r
in that comment 🙂
Here's where I start it in Powershell:
PS C:\Users\seanc\clojure> clj -A:socket-zero -r
60468
user=>
Yup, I’ve got nearly the exact same thing set up for myself now, working great in bash on MacOS
I off work today so I'm on my ancient little laptop: Windows with WSL 1 (since it can't run any virtualized stuff).
I'll try it on macOS when I'm back at work tomorrow. I should update the README in my dot clojure project to describe the new alias.
you could also -e and start the server manually (via clojure.core.server/start-server), which returns the ServerSocket
Good point, that might be less brittle, in that maybe it wouldn’t require a specific name
clj -e "(.getLocalPort ((requiring-resolve 'clojure.core.server/start-server) {:port 0, :accept 'clojure.core.server/repl :name \"repl\"}))" -r
The downside to this is that it won't work on Clojure 1.8 or 1.9 (which the combined JVM option/`-e` approach allows). May not matter to anyone but wanted to mention it.
needs the commas etc to go in an alias
$ clj -e "(.getLocalPort ((requiring-resolve 'clojure.core.server/start-server) {:port 0, :accept 'clojure.core.server/repl :name \"repl\"}))" -r
57739
user=>
@aviflax Still needs to specify the name in there (right, @alexmiller)
is that an issue?
if you want a random name (str (gensym))
doesn't unless you're starting multiple
name is used in the thread names and to key the per-server state
Would it be possible to put the port # in the name, somehow? I know about the trick of asking for port 0 (to get a randomly available port), immediately unbind from that port, then rebind it directly by number…
@deactivateduser10790 probably better to rename than to try and claim & unclaim a port
Yeah - that was where I started getting stuck too. 😉
I know that elsewhere the “bind twice” pattern is somewhat common / accepted. Just don’t know if there’s a better way in Clojure.
the code could do so, but it doesn't right now
(and of course it has a race condition)
I'm not sure why that would be usfeul
thread names are mutable
so it's quite easy to do
So that if you’re looking at thread names (e.g. in a dump) it’s more obvious which thread is bound to which port.
well that's why it's named :)
you started the server and gave it a name, so ...
But you don’t know the actual port number until after the thread is initially named (though thread renaming could get around that, of course).
if you start it explicitly, then you have the server socket and know the port and can do whatever you want
Right. Either you double bind (as is done in some other languages), or you mutate the thread name. They’re both a bit icky (though not terrible). 😉
Mutating the thread name seems OK. But really you just need to compose the pieces together yourself at this point. The abstraction doesn't hold any more, so compose the smaller pieces (SocketServer & starting a thread)
@dpsutton how do you retrospectively change the thread name in things like log files? I mean it’s possible, but situations like these are icky.
abusing mutable thread names has a long tradition in Java
it's a great hack to actually use the thread name to leak a state variable (like a system wide tap>
)
you can then watch it in a mbean or profiler via the debug apis
Yeah - or a correlation id of some sort, to track a single logical “transaction” through multiple threads / JVMs / whatever.
Anyhoo, seems like the simplest thing would be to just rename the thread to “repl-on-port-<concrete port number>” after the socket is bound, and try not to do anything in that thread before the rename (i.e. to avoid logging with the wrong thread name, or other side-effecty shenanigans).
Little warning when you compare lines of code between Clojure and other languages - clojure docstrings count as lines of code, not a comment (tested with tokei)
I wonder if this is how ClojureScript is no longer #1 for LOC here: https://medium.com/dailyjs/a-realworld-comparison-of-front-end-frameworks-2020-4e50655fe4c1 @jacek.schae
That is correct, cloc
thinks that doc strings are LoC. In the article, or rather the repo — there are no docstrings, so the reason why CLJS is not #1 is not the docstrings. I believe it’s because people created DSLs, such as Imba to reduce LoC
ohcount
is good too: https://github.com/blackducksoftware/ohcount
The downside to this is that it won't work on Clojure 1.8 or 1.9 (which the combined JVM option/`-e` approach allows). May not matter to anyone but wanted to mention it.