clojure

New to Clojure? Try the #beginners channel. Official docs: https://clojure.org/ Searchable message archives: https://clojurians-log.clojureverse.org/
Illia Danko 2021-02-16T10:05:27.184600Z

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?

Schpaa 2021-02-16T10:14:53.185900Z

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))))))

Schpaa 2021-02-16T10:16:10.186300Z

I’m just curious…

Illia Danko 2021-02-16T10:32:36.186400Z

Not sure is it the right channel to ask, apologists if isn't

p-himik 2021-02-16T10:35:45.186600Z

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

Illia Danko 2021-02-16T10:42:34.186800Z

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.

p-himik 2021-02-16T11:18:28.187200Z

This? https://clojurians.slack.com/archives/C06MAR553/p1613334757313600

👍 1
tvirolai 2021-02-16T11:22:38.188800Z

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.

vemv 2021-02-16T11:31:20.190100Z

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

Illia Danko 2021-02-16T11:36:21.190200Z

Interesting, this approach I was looking for, but it was referred by some other source.

p-himik 2021-02-16T12:02:24.191500Z

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.

💯 1
p-himik 2021-02-16T12:03:03.192100Z

Also, you can combine destructuring: (fn [{[_ r] :ref}] ...).

💯 1
😀 1
Schpaa 2021-02-16T12:09:11.192500Z

Thanks for the input folks!

Schpaa 2021-02-16T12:13:28.194100Z

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.

vemv 2021-02-16T12:14:40.194300Z

some-fn and every-pred are the 'or' and 'and' for these cases

Schpaa 2021-02-16T12:14:59.194500Z

ah, interesting!!

p-himik 2021-02-16T12:15:25.194700Z

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.

p-himik 2021-02-16T12:15:53.194900Z

Ah, you edited your message, I see.

Schpaa 2021-02-16T12:15:58.195100Z

yes 🙂

p-himik 2021-02-16T12:16:33.195300Z

Huh, usually Slack adds the edited flag to such a message. Or am I imagining things.

Schpaa 2021-02-16T12:16:46.195500Z

I think you are imagining things right now

Schpaa 2021-02-16T12:17:01.195700Z

Doesn’t matter, I get your point.

p-himik 2021-02-16T12:17:26.195900Z

Nope, it definitely used to be the case. :) Anyway, yeah.

Yehonathan Sharvit 2021-02-16T13:16:01.197Z

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))

Yehonathan Sharvit 2021-02-16T13:18:01.197400Z

Example:

(concat-while 12 (repeat 10 (range 5))) ; => [0 1 2 3 4 0 1 2 3 4 0 1 2 3 4]

Yehonathan Sharvit 2021-02-16T13:18:36.198Z

I am looking for a clearer formulation of what the function calculates

2021-02-16T13:33:26.199300Z

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."

Yehonathan Sharvit 2021-02-16T13:51:48.200200Z

I like it more.

ghadi 2021-02-16T14:19:14.201500Z

Why this formulation and not take + concat?

ghadi 2021-02-16T14:19:34.202Z

or take + cat transducer?

gcaban 2021-02-16T14:19:58.202200Z

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.

gcaban 2021-02-16T14:21:06.202400Z

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 😉

2021-02-16T14:25:48.203100Z

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)

ghadi 2021-02-16T14:25:54.203500Z

How is the logic different? I didn’t notice

2021-02-16T14:26:18.203700Z

into already does transients, and cat already does the concatenation

gcaban 2021-02-16T14:27:32.203900Z

the sample output is 15 elements, not 12, right?

2021-02-16T14:29:07.204100Z

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]

2021-02-16T14:30:28.204300Z

yeah - that tripped me up too, the count limit is only imposed at subcollection boundaries, not on the element count

ghadi 2021-02-16T14:33:26.205100Z

yeah the example is not helpful because all the colls are the same range

2021-02-16T14:37:02.205400Z

in fact, that trickiness is the real job the doc string should be addressing :D

➕ 2
gcaban 2021-02-16T14:40:13.205800Z

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.

2021-02-16T14:41:01.206200Z

how about chunked-concat

2021-02-16T14:41:27.206500Z

except that might be confused with seq chunking so that's out

flowthing 2021-02-16T14:44:18.206700Z

(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)
🤪

2021-02-16T14:50:22.207Z

I like that it's a transducer, but the doc string is false

2021-02-16T14:50:55.207200Z

it terminates based on read-count not write-count, based on input count not result count

2021-02-16T14:51:19.207400Z

apply-concat-at-least ?

flowthing 2021-02-16T14:52:31.207600Z

Oh yeah. :thumbsup::skin-tone-2: I’ll admit that aped the docstring from a core function without thinking about it too much.

kwladyka 2021-02-16T15:50:05.210900Z

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.

2021-02-16T15:55:53.211Z

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

2021-02-16T15:56:28.211500Z

If you create such "closed" specs they should always give you failures whenever a key appears that you have not explicitly allowed.

2021-02-16T15:57:14.211700Z

(at the article I linked, search for the word "closed")

kwladyka 2021-02-16T15:57:19.211900Z

I need this functionality only for developing purpose.

2021-02-16T15:58:07.212100Z

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.

👍 1
kwladyka 2021-02-16T15:59:58.212400Z

BTW nice FAQ

2021-02-16T16:00:03.212600Z

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.

kwladyka 2021-02-16T16:02:36.212800Z

it will be open, but it should make start easier.

kwladyka 2021-02-16T16:02:40.213Z

thank you

Yehonathan Sharvit 2021-02-16T16:07:30.213200Z

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

Yehonathan Sharvit 2021-02-16T16:08:14.213400Z

chunked-concat is my favourite for now

2021-02-16T16:18:34.214300Z

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 let

2021-02-16T16:21:42.215100Z

I 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)

Yehonathan Sharvit 2021-02-16T16:23:55.215200Z

@flowthing what does rf stand for in your custom transducer?

2021-02-16T16:25:38.217300Z

it's a standard parameter name for the arg to a transducing function (see for example the one arity clause in (source map)

flowthing 2021-02-16T16:25:41.217500Z

reducing function, I based the thing on halt-when from core.

dpsutton 2021-02-16T16:26:14.218300Z

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?

2021-02-16T16:27:16.218900Z

right, as written if a is smaller than b, it takes mod of the number itself

2021-02-16T16:27:59.219700Z

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

2021-02-16T16:28:42.220400Z

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

alexmiller 2021-02-16T17:52:04.220700Z

loop and let are literally the same code in the compiler

‼️ 2
👀 1
alexmiller 2021-02-16T17:52:55.221100Z

as in, there is no LoopExpr, only LetExpr with an "isLoop" flag

Mark Gerard 2021-02-16T18:28:37.221700Z

😲

2021-02-16T18:35:02.222800Z

reminds me of how bare minimum scheme implementations expand let into a lambda with an inline arg provided for each binding

raspasov 2021-02-16T20:47:48.223Z

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

raspasov 2021-02-16T20:49:21.223200Z

It highlights the “logical thread” of uses of “a”

raspasov 2021-02-16T20:49:55.223400Z

Here it highlights the first two “a”, and stops there

2021-02-16T21:35:07.224200Z

or how Haskell’s do syntax is syntactic sugar for bind >>=

2021-02-16T21:39:36.224400Z

In some sense this is also true in test.check, which has gen/let which is actually implemented as gen/bind