Hello there. Two weeks ago or so, I saw the topic about approaching different main application's targets using tools.deps somewhere (reddit or here don't remember). Mainly it's about passing a function name and the function's arguments to cli. Can anyone remember the title of the article/post?
How would you go about and write this using comp
for the predicate instead of a fn
?
(->> (:docs result)
(remove (fn [{:keys [ref]}]
(let [[_ r] ref]
(or (= r "cache")
(not= "fake-" (subs r 0 5))))))
I’m just curious…
Not sure is it the right channel to ask, apologists if isn't
Not sure whether this is what you meant but here's the documentation of the feature: https://clojure.org/reference/deps_and_cli#_executing_a_function
Thanks, as I remember it isn't about manipulating aliases or deps.edn, it's a contrived approach to run any public func from any namespace with just passing the function name, and it's arguments to CLI. And it was just ~10 lines of Clojure code.
This? https://clojurians.slack.com/archives/C06MAR553/p1613334757313600
Maybe like this:
(->> (:docs result)
(remove (comp #(clojure.string/starts-with? % "fake-") second :ref)))
Checking for (= r "cache")
seems redundant. I think using a plain predicate function like in your example is more readable than this kind of reversed pipelines.Yes it's particularly unfortunate that ->>
goes in one direction and comp
in the opposite
It's trivial though to implement a custom comp
that also reads left-to-right
Interesting, this approach I was looking for, but it was referred by some other source.
IMO when you end up using a lambda fn within comp
, it's simpler to just use a single lambda fn and do all the work there.
Also, you can combine destructuring: (fn [{[_ r] :ref}] ...)
.
Thanks for the input folks!
I think the thing I was curious about was how comp
would compose, so to speak, with or
and and
etc. I completely agree that it is more readable with a lambda, and that would be the most important thing.
some-fn
and every-pred
are the 'or' and 'and' for these cases
ah, interesting!!
Do you really think that
(comp #(clojure.string/starts-with? % "fake-") second :ref)
is more readable than
#(-> % :ref second (clojure.string/starts-with? "fake-"))
I just don't see that. The order is wrong, the string is longer, the %
is buried.Ah, you edited your message, I see.
yes 🙂
Huh, usually Slack adds the edited
flag to such a message. Or am I imagining things.
I think you are imagining things right now
Doesn’t matter, I get your point.
Nope, it definitely used to be the case. :) Anyway, yeah.
I need some help with the formulation of a doc string
(defn concat-while
"Returns a sequence of the concatenation of the elements of the collections in `coll-of-colls`
while the number of elements in the sequence is less or equal to `n`.
When we reach `n` elements keep concatenating the current collection and stop the concatenation.
"
[n coll-of-colls]
(persistent! (reduce (fn [acc c]
(if (>= (count acc) n)
(reduced acc)
(into! acc c)))
(transient [])
coll-of-colls)))
(defn into!
[to from] (reduce conj! to from))
Example:
(concat-while 12 (repeat 10 (range 5))) ; => [0 1 2 3 4 0 1 2 3 4 0 1 2 3 4]
I am looking for a clearer formulation of what the function calculates
This might not be better, but perhaps something like: "Like (apply concat coll-of-colls), except ignore all elements of coll-of-colls after the result contains at least n elements."
I like it more.
Why this formulation and not take
+ concat?
or take + cat transducer?
I personally find the function name confusing and perhaps that’s one of the reasons why it’s hard to write a doc string for it. I’d expect concat-while
to be equivalent off
(take n (apply concat coll-of-colls))
this function could be called concat-until-at-least-n-results
or something like that and I’d be ok with a such a long name, because it’s something fairly specific that you’re doing here.The logic is not the same as take
+ concat
, but it took me a moment to realise it too. Thanks for proving my point @ghadi 😉
not what you asked, but unless I'm reading it wrong, this is a verbose way to write (into [] (comp cat (take n)) coll-of-colls)
How is the logic different? I didn’t notice
into already does transients, and cat already does the concatenation
the sample output is 15 elements, not 12, right?
oh the difference is that it limits around collection boundaries, not an absolute count of the result:
(ins)user=> (concat-while 10 (repeat (range 3)))
[0 1 2 0 1 2 0 1 2 0 1 2]
(ins)user=> (into [] (comp cat (take 10)) (repeat (range 3)))
[0 1 2 0 1 2 0 1 2 0]
yeah - that tripped me up too, the count limit is only imposed at subcollection boundaries, not on the element count
yeah the example is not helpful because all the colls are the same range
in fact, that trickiness is the real job the doc string should be addressing :D
and the function name! concat-while
suggests this is a simple, generic combinator, while this is clearly something fairly application specific and imho that should be reflected in the name.
how about chunked-concat
except that might be confused with seq chunking so that's out
(defn halt-at
[n]
"A transducer that ends transduction when the result has n or more elements."
(fn [rf]
(fn
([]
(rf))
([result]
(rf result))
([result input]
(if (>= (count result) n)
(reduced result)
(rf result input))))))
(transduce
(halt-at 12)
concat
(repeat 10 (range 5)))
;;=> (0 1 2 3 4 0 1 2 3 4 0 1 2 3 4)
🤪I like that it's a transducer, but the doc string is false
it terminates based on read-count not write-count, based on input count not result count
apply-concat-at-least
?
Oh yeah. :thumbsup::skin-tone-2: I’ll admit that aped the docstring from a core function without thinking about it too much.
I have a API response which is not described and I have to make spec
for this. Do we have any tool which show difference between huge spec nested structure and data which are not in spec?
In very simple example:
{:a 1 :b 2}
Let’t say I have only :a
in spec, but not :b
. I want to know about that.
In that way I can get all API responses and run against spec and see which values I have to add.
spec does not encourage creating "closed" specs, i.e. ones where any keys that aren't explicitly enumerated cause a spec failure, but you can still create such specs if you wish, e.g. there are some examples linked from this article (I haven't read the to see how effective they are): https://blog.taylorwood.io/2018/10/15/clojure-spec-faq.html
If you create such "closed" specs they should always give you failures whenever a key appears that you have not explicitly allowed.
(at the article I linked, search for the word "closed")
I need this functionality only for developing purpose.
Understood. You can make them closed while developing, and when you believe you are done defining all of the keys you expect, you can make them open again.
BTW nice FAQ
or leave them closed -- it is up to you what you prefer, really. The Clojure maintainers have mentioned in talks on spec that usually they find when people create a closed spec, they later wish they hadn't, as they expand their information model.
it will be open, but it should make start easier.
thank you
apply-concat-at-least
is not accurate.
Maybe concat-at-most
but it doesn’t convey the fact that the result might contain more than n elements
chunked-concat
is my favourite for now
a gotcha I somehow never discovered until now:
(defn gcd
[a b]
(loop [a (max a b)
b (min a b)]
(let [r (mod a b)]
(if (zero? r)
b
(recur b r)))))
this is broken because loop inits use the same scoping rules as letI think I only discovered this because it's not my clojure style, but a line by line rewrite of a low level program (for prototyping / debugging)
@flowthing what does rf
stand for in your custom transducer?
it's a standard parameter name for the arg to a transducing function (see for example the one arity clause in (source map)
reducing function, I based the thing on halt-when from core.
the b (min a b)
will use the a (max a b)
and the b
from the function args? so a and b could be equal?
right, as written if a is smaller than b, it takes mod of the number itself
I can't think of a use case for reusing loop bindings in further init clauses, but in terms of simplicity just using the same rules as let makes sense
I could have skipped the loop form altogether and just did the min/max every time even though it's only possible for them to be out of order on the initial call
loop and let are literally the same code in the compiler
as in, there is no LoopExpr, only LetExpr with an "isLoop" flag
😲
reminds me of how bare minimum scheme implementations expand let into a lambda with an inline arg provided for each binding
Not sure about other editors, but I find the Cursive highlighting indispensable for many “complex” cases like this one: Screen Shot 2021-02-16 at 12.47.02 PM.png
It highlights the “logical thread” of uses of “a”
Here it highlights the first two “a”, and stops there
or how Haskell’s do
syntax is syntactic sugar for bind >>=
In some sense this is also true in test.check, which has gen/let
which is actually implemented as gen/bind