This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-08-18
Channels
- # announcements (17)
- # babashka (42)
- # beginners (34)
- # calva (78)
- # cherry (1)
- # cider (7)
- # clojure (64)
- # clojure-europe (19)
- # clojure-nl (2)
- # clojure-norway (54)
- # clojure-uk (3)
- # clojurescript (21)
- # cloverage (1)
- # conjure (1)
- # core-async (11)
- # cryogen (16)
- # cursive (1)
- # data-oriented-programming (1)
- # datahike (5)
- # fulcro (2)
- # girouette (1)
- # helix (10)
- # hyperfiddle (1)
- # jobs (1)
- # kaocha (4)
- # nbb (7)
- # off-topic (6)
- # pathom (4)
- # polylith (21)
- # rdf (9)
- # releases (2)
- # shadow-cljs (3)
- # sql (12)
- # squint (68)
- # vim (33)
- # xtdb (29)
I have a vector of integers called ranks and I would like to return a hash-map where the key is the integer and the result is the number of occurrences of that integer in the input vector, how to do nicer than this? (zipmap ranks (map (fn [rank] (count (filter #(= rank %) ranks))) ranks))
Purely as an exercise for an alternative implementation
(reduce (fn update-counts [acc item]
(update acc item (fnil inc 0)))
{}
coll)
Oh, I just checked how frequencies
is actually implemented, and my "alternative implementation" is pretty much how frequencies is implemented. They use transients for the perf.
could use some help with a macro 🧵
I want to use the is
macro in a threading context
(deftest foo1
(-> {}
(assoc :foo 10)
((fn [m]
(is (= 10 (:foo m)))
m))))
;; The above works but is kinda ugly
(defn check [m f expected]
(is (= expected (f m)))
m)
(deftest foo2
(-> {}
(assoc :foo 10)
(check :foo 11)))
;; This works, but with a not very helpful error message
;; FAIL in (foo2) (NO_SOURCE_FILE:39)
;; expected: (= expected (f m))
;; actual: (not (= 11 10))
;; check needs to be a macro
(defmacro checkm [m f expected]
`((fn []
(is (= ~expected (~f ~m)))
~m)))
(deftest foo3
(-> {}
(assoc :foo 10)
(checkm :foo 11)))
;;Seems to work, but symbols are ofcourse expanded with namespace
;; FAIL in (foo3) (NO_SOURCE_FILE:63)
;; expected: (clojure.core/= 11 (:foo (assoc {} :foo 10)))
;; actual: (not (clojure.core/= 11 10))
I feel there should be an existing solution to this problem but I haven’t found one. Any ideas, or improvements I could make to checkm
?
maybe like this :
(defmacro checkm [m f expected]
`((fn []
(is (~'= ~expected (~f ~m)))
~m)))
not thinking about the problem, just replaced = by ~'= so it doesn't expand with the namespace
seems there are more problems with this:
(deftest foo4
(-> {}
(assoc :foo 10)
(checkm :foo 10)
(assoc :bar 10)
(checkm :bar 10)
(assoc :baz 10)
(checkm :baz 11)
))
;;FAIL in (foo4) (NO_SOURCE_FILE:40)
expected: (clojure.core/= 11 (:baz (assoc (checkm (assoc (checkm (assoc {} :foo 10) :foo 10) :bar 10) :bar 10) :baz 10)))
actual: (not (clojure.core/= 11 10))
Ran 2 tests containing 10 assertions.
so each test case receives the unevaluated expanded threading formthat wont work if you have more checks, right?
(-> start-value
(transform 1)
(check)
(transform 2)
(check))
(deftest foo3
(-> {}
(assoc :foo 10)
(doto (-> (= {:foo 11}) is))
(update :foo inc)
(doto (-> (= {:foo 12}) is))))
works ... but i can't say it's any more readable than your originalif you really want a macro, this might work?
(defmacro is= [x y]
`(let [x# ~x]
(is (= x# ~y))
x#))
(deftest foo4
(-> {}
(assoc :foo 10)
(is= {:foo 11})
(update :foo inc)
(is= {:foo 12})))
don’t think this solves the problem of multiple evaluations, but I need to brush up on my macro knowledge
in your checkm
you're evaling m
twice which will often cause problems in macros. to get around this, you can use let
and x#
to generate a symbol to store the results of evaling x
only once.
good point! thanks. I’m now at this:
(defmacro checkm [m f expected]
`(let [m# ~m]
(is (= ~expected (~f m#)))
m#))
(deftest foo4
(-> {}
(assoc :foo 10)
(checkm :foo 10)
(assoc :bar 10)
(checkm :bar 10)
(assoc :baz 10)
(checkm :baz 11)
))
FAIL in (foo4) (NO_SOURCE_FILE:45)
expected: (clojure.core/= 11 (:baz m__96399__auto__))
actual: (not (clojure.core/= 11 10))
This is usable, but not sure how to get the expected line to output the value of m__96399__auto__
. ~m#
isn’t possible. Makes sense since there is no symbol m#
defined.how about
(defmacro checkm [m f expected]
`(let [m# ~m]
(is (= ~expected (~f m#)) m#)
m#))
(deftest foo4
(-> {}
(assoc :foo 10)
(checkm :foo 10)
(assoc :bar 10)
(checkm :bar 10)
(assoc :baz 10)
(checkm :baz 11)))
?FAIL in (foo4) (NO_SOURCE_FILE:45)
input: {:foo 10, :bar 10, :baz 10}
expected: (clojure.core/= 11 (:baz m__96643__auto__))
actual: (not (clojure.core/= 11 10))
This is acceptable, thanks for the assistance @U0P0TMEFJ!If it weird that docstrings can't be added to defmethod
? Is that on the roadmap to fix?
No, and no. The defmulti defines the function.
Thanks for the explanation!
I guess the reason it felt like an omission to me is that Integrant defines the defmulti
. As a user, I add defmethod
s for my components.
there is only one function, and it's docstring is defined by the defmulti - the defmethods are impls of that and there really is no place to hang a docstring (plus they are dynamically added on load)
@U021RHDFFHN — you can think about it like this: Where would you expect to read the docstring?
In API docs, IDEs, etc., you would call ig/init-key
, and see its docstring, as defined with defmulti
. It documents the contract.
The only way to see documentation for a defmethod
is in the code itself, here you could just use a comment as necessary.
Anyone regularly uses a solution such that (get m k)
fails if all keys in m are of a different type of that of k
?
e.g. m
is a map of strings, so calling (get m :foo)
is very likely a bug (due to a bad refactoring etc)
Like anyone else, I can hack something, but I'm specifically asking if someone has had a long-time favorite solution.
I tend to not do type checking. Instead, I’d resort to spec or malli. Both of these also support instrumentation, where you can define what your functions expect and automatically throw assertion errors should you call the function with invalid parameters.
What you are describing sound more a safeguard against programmer mistakes. You should validate data from external sources anyway — again I’d recommend malli for this 🙂
the main case where this happens is with sorted maps, which have a comparator that makes type assumptions. should not happen with regular maps.
Clojure follows Java in throwing in this case
With help of the I got this:
(fn [table]
{:pre [(check! [:map-of :string :any] config-table
:string table)]}
(-> config-table
(get table)
:foo))
...which probably will do the job, however it illustrates why @U031CHTGX1T's answer doesn't quite address my question
Here the check is at the fn
boundary, but I want it at the get
boundary.
In this example it might look the same, but if the get
happened to be far away from any function boundary, it would not be nearly as easy.
I think get
, get-in
etc could get some official love from spec or malli. They're functions where any type-like facility meets high dynamism so they're hard to get rightNot sure if I follow. get
does not care about the structure of its map. It is totally OK to give it keys of different types.
What you want is that your own function is guarded against being called with an invalid table
argument, so it’s your function that should check or be spec’ed
user=> (def sm (sorted-map 1 1 2 2 3 3))
#'user/sm
user=> (get sm :a)
Execution error (ClassCastException) at user/eval1 (REPL:1).
class java.lang.Long cannot be cast to class clojure.lang.Keyword
> It is totally OK to give it keys of different types.
It is technically OK (and a poster child of 's ideas) but spec and malli both have map-of
which shows how it's very often pragmatic to keep maps homogeneous.
Anyway I'd want to abort any possible discussion before it happens - we all have slightly different ideals about types.
(If anyone has a specific answer for my get
question, that's welcome!)
I'd be interested in a generic mechanism. A protocol might be ok if it's not domain-specific
Well, much of the point of gradual typing is to spec things as much (or as little) as one deems necessary.
> use a protocol, extend-via-metadata, stick a validator on an instance I can only approximately imagine this - care to share a snippet?
(defprotocol IFoo
:extend-via-metadata true
(-get [m k]))
(-get (with-meta {:a 1} {'user/-get (fn [m k] (assert (keyword? k)) (get m k))}) 1)
Could be. I also thought in the meantime of a function that transformed a schema into a get
ter
(def Config [:map-of [FancyString :map]])
(def get (schema->getter Config))
(get m "foo") ;; fails if "foo" it's not a FancyString
the cool part being that schema->getter
is completely generic
you could go as far as linting that you always use generated get
s (if one is into castle building and all that)hmm, looks like there's already a https://clojuredocs.org/clojure.core/vector-of
If you limit yourself to primitives, you might also get the benefit of a more efficient thing
Nope, but if I cared, I would use malli or Clojure spec and validate the map against the spec in key places.
I don't understand why you'd need to validate this everytime you call get? Seems unnecessary? Either validate it after you've inserted into the map, or like in some key places where the map is received, why on each get? On the get you might want to just validate that the key exists, but not that like all other keys are of the expected type, that seems unnecessary and overengineered
Well, if I understood it's enforcing that every key in the map contains a string no? Not just the one from the get? Maybe I misunderstood
I just read it as a request to enforce the get
but yeah I agree, for whatever purpose you'd probably want to enforce on the insertion side
If you're not making a new type though, and instead just dressing up the insertion on a plain map, you might as well make a custom get too, to not waste lookups on keys that aren't strings
Ya, I guess you could use meta to tag a Map<string, any> kind of "type" and have a get that validates you're giving it a key of the right type and getting back a value of the right type. If that's all you want to enforce.
Maybe, you just tag the map with it's spec? And then have the get validate the spec on read.
It kinds of break the conceptual thought behind spec though. In that, normally with standard "types" things have types, the type of the thing is part of the thing. But with spec, it's more that at a certain time you ascertain that the thing is now in the form you expect at this time. It's a different mindset. You don't go, what type is this map, you go, is this map what I want it to be at this moment.
Practically it means:
(get m k ::spec)
;; Get the value at key k from m, and make sure it's valid to our spec.
Whereas with types it would be more like
(get m k)
;; Get the value at key k from m, and make sure k makes sense for the type of m.
The difference is about what owns the "type". Is it the code calling get that is in charge of defining the type of what it wants. Or is it the code that creates m that is in charge of defining the type that the code calling get is allowed to use?That's one point I understood from the Maybe Not? talk. In the classical "type" style, you either begin to overload your types with a bunch of optional things, that will be mandatory but only in certain places/times so you have to make everything optional eventually. Or you have to create a bunch of iterations of the type, NotRegisteredCustomer, RegisteredCustomer, RegisteredNotVerifiedCustomer, etc. Which gets you closer to spec, but also couples it a bit with the structure and more verbose, and doing combinations become hard. With spec you could do, here I need a map who has the key/values that a registered customer should have and those that a verified customer should have.