This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2018-02-04
Channels
- # arachne (108)
- # beginners (16)
- # boot (48)
- # cider (34)
- # cljs-dev (12)
- # clojure (237)
- # clojure-spec (50)
- # clojure-uk (5)
- # clojurescript (8)
- # cursive (1)
- # datascript (2)
- # datomic (6)
- # defnpodcast (1)
- # emacs (1)
- # fulcro (7)
- # hoplon (1)
- # keyboards (1)
- # off-topic (22)
- # re-frame (26)
- # reagent (2)
- # specter (16)
- # unrepl (6)
- # yada (1)
what's your best trick for when you need (key val)
semantics in your code, rather than having maps with just a single map entry? maps with single-map entries require a first
for every access.. or maybe they don't
aren't those just tuples? (in this case, 2-tuples represented as vectors of two items)
yes, and you can destructure them
in fn args, let bindings...
I'm looking for something that would give more clarity in the code, maybe destructuring would help with that, but I wish I could key
to get the key, or something like that that would make code more readable and semantically clear
(let [[k v] [:key :value]]
(println "key" k "value" v))
(defn give-me-a-better-name [[k v]]
{:key k :value v})
(let [{:keys [key value]} (give-me-a-better-name [:a :b])]
(println "key" key "value" value))
tuples are quite widespread, but maybe someone knows of smth
hey look!!!
(val (first {:a :b}))
=> :b
(key (first {:a :b}))
=> :a
got nothing more then 😞
or wear that "tuples are just data" hat and pretend the code would be readable down the road (lol)
you could possibly try a record btw
@matan what are you trying to do exactly? You're passing around vectors containing a single map?
and you want functions that look like (defn process-tuple [verb noun] ...)
or some such?
if so. probably the easiest thing is to (map (partial apply process-tuple) that-vector-of-tuples)
(map (comp
(partial apply process-tuple)
(juxt :your-key :your-value))
a-vector-of-maps)
for maximum clojure points you can take that map, drop the a-vector-of-maps argument, and use it as a transducer with into
instead
user=> (def tuple [:a :b])
#'user/tuple
user=> (get tuple 0)
:a
user=> (tuple 0)
:a
user=> (first tuple)
:a
user=> (second tuple)
:b
pick your poisonmy pick would be either a record (with object overhead) or a macro that plucks out the key and a macro that plucks out the value
it depends on what they expand to. you can't pass a macro around as if it's a function, so the moment you want to, say, throw it inside comp
you're stuck
if you want to alias a function, you just (def new-name f)
, if you want to alias a macro, you have to write a whole new macro that wraps it
meaning that the macro reading stage will recursively apply all macros in whatever order they are "nested" in the code
when we say they don't compose, we mean they can't be composed in the same way functions are - passed around, chained together, etc - during runtime
if you had a macro that expanded to (first some-thing)
where some-thing was the argument ...
you could not pass that macro in to anywhere that expected a function. at evaluation time it would be expanded in place - it needs some-thing right now, not later
user=> (defmacro thingy [arg] `(first ~arg))
#'user/thingy
user=> (thingy [:hi])
:hi
user=> (map thingy [:hi :there])
CompilerException java.lang.RuntimeException: Can't take value of a macro: #'user/thingy, compiling:(NO_SOURCE_PATH:4:1)
@matan To put it in perspective: we have about 75,000 lines of Clojure code at work and there are only 40 macros in all of that, and they are mostly with-...
context macros to save us writing anonymous zero-arity functions (thunks).
(and the majority of those are in test code to provide a mock context for stuff)
I have a similar ratio, I have one macro that tidies up some complicated gen-class definitions, and every single other macro starts with with-
... :)
Stuff like this
(defmacro with-tmp-file [binding & body]
`(let [~binding (File/createTempFile "image" ".jpg")]
(try
~@body
(finally
(.delete ~binding)))))
So we can say (with-tmp-file f
(do-some-tests-on f))
and reduce boilerplate.The first rule of macros is "don't use them". The second rule is "use them very sparingly". 🙂 Basically, write functions first, then use macros for a little syntactic sugar. Or only use a macro where you can't write it as a function.
This might sound stupid, but is it possible for a sorted-map-by
to have multiple values with same key?
no, it cannot
But this what I got, by doing assoc-in
into sorted-map-by
nested inside a regular map:
{
...
:state.camp.in-edit/script
{:start {:on-key {:1 []}},
:start {:default [], :on-key {:1 []}},
:stage-2 {:default [], :on-key {:1 []}}},
...
}
😮you can have a collection under a key, a collection is a single value
also, some readers (including the clojure compiler) accept :1
, but it's technically illegal and other readers will error when consuming it
you're allowed to use numbers askeys
oh - how are those keys generated?
either this is a serious compiler bug (unlikely?) or there's something going on with non-printing characters
Not sure. The code works fine with regular hash-maps. Problems started only when I changed into a sorted-map-by
. I can show you the spec failure.
wait, is the sorting function in any way non-deterministic?
user=> (into (sorted-map-by (fn [_ _] (rand-int Integer/MAX_VALUE))) [[:a 0] [:a 1] [:a 2]])
{:a 0, :a 1, :a 2}
it happens, but I'd consider it a user error to do so
Stage sorter in question: :::clojure (defn script-stage-sorter [x y] (cond (= x :start) true, (= y :start) false, (= x :start-when-machine) true, (= y :start-when-machine) false, :else (compare x y))) The intent is, :start goes to the top, after that :start-when-machine, rest of them are sorted naturally.
that's non-stable though, right?
But clearly, as you have demonstrated, it is possible to have multiple values with same key!!
right, by having a broken comparator
make a condition for (= x y) and have it return either x or y consistently
I bet that eliminates your issue
(and check it before checking the others)
No, it doesnt. Anyway, I should've probably used a vector of 2-tuples. The intricacies of how an unstable comparator can give weird bugs should be documented somewhere. Probably in the comment section of: https://clojuredocs.org/clojure.core/sorted-map-by . Could you please? Or should it be filed as a bug?
BTW my comparator is also broken in the sense that it returns bool in some cases and integer (using compare
) in other cases.
yeah - I'd change it to always return some number -1 / 0 / 1
I added a comment on clojuredocs
oh and when I said "returning either x or y" I should have said "return 0"
There is a guide page on comparators that might have some useful info: https://clojure.org/guides/comparators
If you had two maps that both had a :start key, the comparator function you showed above would return true when calling (compare x y), and also for (compare y x). That doesn't look good.
That is like saying (x < y) is true, and also (y < x) is true.
That is not a total order on the set of values in your domain.
Yeah. I just read the comparator guide, and it says for boolean comparators, it checks for both equals and less than. My comparator was broken on multiple levels.
It is really easy to write a comparator for simple cases, but it can get surprisingly mind-bending to do it for complex data values, and still have a total order.
The corrected one, for reference:
clojure
(defn script-stage-sorter
[x y]
(cond (= x y) 0,
(= x :start) -1,
(= y :start) 1
(= x :start-when-machine) 1,
(= y :start-when-machine) -1,
:else (compare x y)))
When you add an actual code snippet (plus sign left to chat input box, "Code or text snippet" there's an option for selecting language which may or may not highlight syntax.
Also not a total order among maps that both have a :start key, for same reason as your previous one.
If two maps x and y both have a :start key, (script-stage-sorter x y) returns -1, and so does (script-stage-sorter y x)
any sorting function will get confused by such a comparator if you give it a collection with more than one value that has a :start key.
oh, sorry, I am confusing myself here.
you are just comparing keys only here, got it.
You want key :start-when-machine to always be last in the sort order, and :start to always be first? If so, that looks reasonable.
I would like to add how big of help it was for me to have a spec of the state. 🙂 You don't figure out a map having non-unique keys without some prior experience or wasting an weekend or two! 😌
In case you haven't seen it, there is an open source 'ordering-map' implementation that might do exactly what you want, and is generalized to any number of custom keys that you want them to be before all others, and in the order you specify: https://github.com/amalloy/useful/blob/master/src/flatland/useful/map.clj#L243-L245
There is a mention of it in the Clojure cheat sheet, in the Sorted maps section
Your advice appreciated: I spend a lot of time erring in the shape of data when processing it, especially when coming back to code written few days back. You know, getting empty collections or stack traces when my new code doesn't follow the structure of the data well. In Java/Scala this would rarely happen due to the IDE knowing my types. What would you suggest for me if you are wearing your mentor hat? it has gotten really inefficient for me, developing data-centric code, and I only program two days a week so remembering the structures by heart is not an option. The rest of us, me included, do need cognitive aids in tailoring code to data, being the fuzzy inaccurate "thinking machines" that we are. Do you have super-helpful advice wearing your mentor or experienced tutor hat?
@matan when I first started clojure I often made tests to check functions kept working as expected, that’s probably still a good practice. More and more I directly check in the repl.
@gklijs thanks but the tests are not my problem though 😉 I'm specifically referring to turnaround time of developing new code on top existing data, and/or modifying code in lockstep with modifying the structure of existing data.
yes. it's one obvious thing coming to mind, but I feel this is only one ingredient for the overall workflow challenge
I agree that it is only one ingredient, but IMO spec, tests and well-written code with good naming conventions goes a long way
I started out trying to spec functions and the return types of higher-order functions etc and thing got out of hand pretty fast
but it’s great for describing data structures, and if you use it to spec the contents and layout of your vectors/maps etc it’s very useful and at least to me it doesn’t feel like bolted-on OO at all
@scmee just wondering quite personally, wouldn't you just use an OO language rather than spec, if you do need that sort of thing
no, I don’t want to deal with a multitude of classes all with ad-hoc APIs, I want to use a handful of data types all over the place, and Clojure is great for that. but it can get confusing when it’s just maps being passed around everywhere and spec helps alleviate that. with spec you get both a limited set of types to deal with (maps, vecs etc) and clarity, without going full OO, and that is the sweet spot to me
it’s a bit hard to explain what I mean by “spec data not types”, but as an example a while ago I specced a map functions to keywords. first I wrote a really complicated fdef
for the functions but that started to look like haskell, so in the end I settled on (s/map-of ifn? keyword?)
. my point is, IMO it’s best not to use spec as some sort of type system, but limit it to describe data only
I think I'll get it when I use spec myself... abstractly it sounds like a way of lazily describing some parts of what data should be, rather than building inheritance topologies
I guess I'd still get bugs from what I do not validate over the data, or end up writing so much specs that I'd regret not using records to begin with
an out-of-the-blue question: I’ve been reading about java.lang.invoke
, and I’m curious if it could be used instead of reflection to speed up Clojure somehow?
to my naive understanding, it seems like between indy and recent improvements in persistent data structures there is a lot of potential performance wins for Clojure?
indy won't help much clojure perf wise, we already have monomorphic callsite caches for most polymorphic dispatches
https://github.com/lacuna/bifurcan seems like an interesting option for immutable data structures, although if my understanding is correct most of the performance wins compared to clojure comes from different equality and hashing schemes
I’ve been doing some performance optimization recently and it’s got me thinking “in a perfect world, how fast can Clojure possibly get?”
the clojure compiler emits bytecode callsite caches for protocol methods and keyword lookups
https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Compiler.java#L3724
if you care for a full clojure reimpl there's https://github.com/clojure/tools.analyzer.jvm and https://github.com/clojure/tools.emitter.jvm
good stuff, I’m sure it will help my understanding a lot if I compare implementations in tools.analyzer and the Java compiler side-by-side
(when-let [[a b] nil]
2)
(when-let [[a b] [2 3]]
(+ a b))
when binding multiple values with when-let
, how does it decide to execute the body or to execute nil
?it always uses the right-hand side as the test:
user=> (source when-let)
(defmacro when-let
"bindings => binding-form test
When test is true, evaluates body with binding-form bound to the value of test"
{:added "1.0"}
[bindings & body]
(assert-args
(vector? bindings) "a vector for its binding"
(= 2 (count bindings)) "exactly 2 forms in binding vector")
(let [form (bindings 0) tst (bindings 1)]
`(let [temp# ~tst]
(when temp#
(let [~form temp#]
~@body)))))
nil
got it, so the key is the (when temp#
, which means
1. eval RHS side, store in temp#
2. (when temp#`
3. do destructuring inside that
thanks!
so I'm reading the JVM bytecode spec. Instructions like iload / istore / fload/fstore take a single BYTE as an argument. Does this mean that for JVM functions, # input args + + # locals <= 256 ? I don't think I've ever run into this issue in practice, but 256 actually seems low
You might want to read https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-4.html#jvms-4.11
(apply map vector (map (juxt first second) lst)) is another option
cljs.user=> ((juxt #(map first %) #(map second %)) [[:a :b] [:c :d]])
[(:a :c) (:b :d)]
cljs.user=> (seq (zipmap [:a :b] [:c :d]))
([:a :c] [:b :d])
cljs.user=>
it won't work with more than two, forget me
and what if you want an even more generic solution? (f [[:a :b :x] [:c :d :y] [:e :f :z]]) => [(:a :c :e) (:b :d :f) (:x :y :z)]
(partial apply map vector)
does that
+user=> ((partial apply map vector) [[:a :b :x] [:c :d :y] [:e :f :z]])
([:a :c :e] [:b :d :f] [:x :y :z])
or just (apply map vector coll)
if you just need to do it once and are not trying to define it as an operation
Anyone there for minor support regarding emacs + cider? I've familiarized with and tweaked emacs and installed cider. What I don't get is why sometimes the cider and repl menus show and sometimes not.
I feel like I'm having a mental block. How can I go from
{:a [:x :y]
:b [:x :y]}
To
[[:a :x] [:a :y] [:b :x] [:b :y]]
?Lots of way to do that
(into [] (mapcat (fn [[k vs]] (map #(vector k %) vs))) the-map)
(reduce-kv (fn [c k vs] (into c (map vector (repeat k) vs))) [] the-map)
@dominicm with specter: (select [ALL (collect-one FIRST) LAST ALL] data)
I must admit, this is one of the cases where the specter solution is more bewildering to me.
I always feel weird using a nested map
, I always assumed there was a more straightforward function lying in core.
some light benchmarking shows the specter version to be the fastest as well
user=> (time (dotimes [_ 1000000] (select [ALL (collect-one FIRST) LAST ALL] data)))
"Elapsed time: 751.67376 msecs"
user=> (time (dotimes [_ 1000000] (reduce-kv (fn [c k vs] (into c (map vector (repeat k) vs))) [] data)))
"Elapsed time: 1065.379155 msecs"
user=> (time (dotimes [_ 1000000] (into [] (mapcat (fn [[k vs]] (map #(vector k %) vs))) data)))
"Elapsed time: 1036.123062 msecs"
for is not a loop, it's a list comprehension
anyway, for
is one of my favorite macros, especially when used with :when
and :let
, all types of magic
Hey, I just integrated steam authentication (using OpenID) into an app I am working on. I'm trying to understand how to securely keep track of this across future requests. Ring has sessions, but it looks like they end up getting stored on the client via cookies or memory-stores (thus tamperable?)
Do clients only get a copy of the session values? Or is it stored exclusively client-side?
@jmromrell the default session is a token in a cookie, mapped to the actual data in the server memory
to securely store data in the client, and verify it came from your server, a decent option is JWT stored in localstorage
As long as the data is stored in non-tamperable form on the server (which you suggest sessions do?), that is sufficient for me
signing the authenticated principal (the steam account ID) into a JWT and using that as a bearer token is pretty standard pattern
(let [tag ...]
(case tag
a1 (f tag)
a2 (f tag)
a3 (f tag)
b1 (g tag)
b2 (g tag)
b3 (g tag)))
^-- is there a way to simplify this ?@jmromrell right, but it doesn't expand to multiple servers (or even persist across server restarts), which is why JWT starts to look more attractive
the usage of parens for grouping in case is one of my few pet peeves about clojure syntax - it's inconsistent with other usage of parens, and it would be nice to be able to tell new users "parens are not for grouping"
In this case, I think I can plan on the app remaining on one server for the foreseeable future. I think I'll stick with sessions and keep JWT in mind if needed in the future
yeah case
is a bit of an idiosyncratic corner, but since the tests must be compile-time constants it’s already a bit different than cond
and friends.
@noisesmith:
`(case tag
a 2)``
also confuses me, I don't expect it to amtch tag vs 'a, but I expect it to amtch tag vs value of
a
in local env
right, this is a problem with macros in general though, it's hardly limited to case - let and def do the same thing for starters
yes, it feels different, but it comes from the same underlying power of macros / special forms
does anyone have a rationale for why some of the clojure.core
functions like map
take the collection at the end of the arglist and others like assoc
take the data as the first parameter? until you learn the APIs I find having to toggle between ->
and ->>
for instance and it would be nice if it was uniform
it is uniform but there are two things going on here, not one
assoc is a collection function - it takes a collection and returns a function. all collection functions take the coll first
map is a sequence function - it takes a sequence (really a seqable) and returns a sequence (really a seqable)
those always take the sequence last
thanks — any reason in particular that sequence functions take the sequence last? why not first?
the rationale is detailed here: https://groups.google.com/forum/#!msg/clojure/iyyNyWs53dc/Q_8BtjRthqgJ
thanks!
Can anyone explain to me the difference between a namespaced keyword and a keyword :person/name
, where there is no namespace person
defined?
:person/name
is still namespaced - the namespacing of the keyword doesn't mean it belongs to a namespace, it means it has a namespace part
=> (namespace :person/name)
"person"
This is what I checked indeed. However, I got confused by an article recently posted on reddit (https://blog.jeaye.com//2017/10/31/clojure-keywords/) which states that :person/name
should be mostly avoided.
:person/name is still namespaced, the distinction that @jeaye defines there is not standard, it's one that he is making - he's arguing it's useful to avoid keywords that don't map to specific namespaces, but there's no implementation difference and it's not something believed in or enforced by the language
Indeed. "Syntactically, grouped keywords are namespaced keywords, but they’re not tied to a valid namespace."
@noisesmith Thank you! For instance, you answered my next question about implementation details.