Fork me on GitHub

Why isn't halt-when implemented like:

(defn halt-when'
  (fn [rf]
      ([] (rf))
      ([done] (rf done))
      ([acc e] (if (pred e) (reduced (rf acc e)) (rf acc e))))))

 (halt-when' #{4})
 [1 2 3 4 5 6 7 8 9])
[1 2 3 4]
Or is there another transducer that does this?


I think that halt-when should stop when the predicate returns true, and not include the result. However, it tries to give you a function to give you a strategy for what to do when it does, so you can decide what to do. But I think there's a problem with the way it's implemented. Firstly, it seems to return input where it should be returning the result:

(into [] (halt-when #{4}) (range))
class java.lang.Long cannot be cast to class clojure.lang.ITransientCollection (java.lang.Long is in module java.base of loader 'bootstrap'; clojure.lang.ITransientCollection is in unnamed module of loader 'app')
So I think this stops it from throwing class cast exceptions:
(defn halt-when1
  ([pred] (halt-when1 pred nil))
  ([pred retf]
   (fn [rf]
       ([] (rf))
        (if (and (map? result) (contains? result ::halt))
          (::halt result)
          (rf result)))
       ([result input]
        (if (pred input)
          (reduced {::halt (rf (if retf (retf result input) result))})
          (rf result input)))))))
(into [] (halt-when1 #{4}) (range)) ;; => [0 1 2 3]
However, I think that the retf function is now coupled to the transducing context. So if you want to include the 4 then you need to know when you create the transducer that it will be used in a specific context and that you need to call conj or conj! or whatever. So I think that this signature of the function makes more sense:
(defn halt-when2
  ([pred] (halt-when2 pred nil))
  ([pred retf]
   (fn [rf]
       ([] (rf))
        (if (and (map? result) (contains? result ::halt))
          (::halt result)
          (rf result)))
       ([result input]
        (if (pred input)
          (let [r (if retf
                    (retf rf result input)
            (reduced {::halt (rf r)}))
          (rf result input)))))))
and now you get to actually decide what to do:
(into [] (halt-when2 #{4} (fn [rf r i] (rf r i))) (range)) ;; => [0 1 2 3 4]
Does that make sense?


@didibus There's take-while:

(into [] (take-while (complement #{5})) (range 10))


Ya, but that doesn't include the one that matches the pred πŸ˜›


Greetings! I have a little conundrum: What is the correct type hint to use for the return type of something that returns {:a "map" :of "stuff"}?

Alex Miller (Clojure team)13:08:34

Type hints are for Java interop. That’s Clojure data and no type hint is needed


There are situations in which returning a hint for such a defn is justified. What are you trying to do / why?


Well, another advantage of type hints is implicit documentation


when naming is ambiguous, it helps to quickly check if I'm returning a list or a map on things like scan-datapoints


docstrings are the correct vehicle for that


so I was just trying to be consistent


(defn ^String foo [] {:x 1})

(foo) ;; => {:x 1}
Type hints can lie


fair enough. This is old code that I'm going through, so adding hints was another way to make things readable without changing it for now


Adding superfluous type hints will make it less readable

πŸ’― 9

docstrings ftw




Still, disagree hard


Type hints tell you nothing about semantics

πŸ’― 9

In principle type hints are the wrong tool for documentation. It's objective - it often will emit technically wrong code Docstrings or Spec are more idiomatic

Alex Miller (Clojure team)13:08:18

If you want to actually make a checkable descriptive result, write a spec


this will eventually be refactored to use spec


but while figuring out the call stack it helps to see what is going around many levels deep


You can write superfluous spec - one that's not checked. Just as a documentation.


Yes, the problem with type hint is that the wrong hint will potentially break things or hurt performance. Where as wrong doc or spec won't


Yes or bespoke metadata like ^{::kind :map} if you feel that helps you during refactoring Anything but abusing type hints πŸ™


yeah, but I like the idea of bespoke


I need to annotate these things somehow before starting to break them

πŸ™‚ 3

speced and instrumented functions sound like the best plan


The use case #2 per would be interesting here (TIL about that specific capability from spec-provider... that's why I love answering questions :) )


@rcarmo If you want, use meta, but don't use hints. For example:


(defn foo
  ^:String [^:int a ^:int b]
  (str a b))
Because if you add type hints, and the hint is wrong, you can technically break the code or slow down the performance of it


I don't actually recommend this, I think doc-string or spec is better, but if you insist on having the type inline, plain meta doesn't have the risk that type hint does.


You could also consider pre/post and something like Truss, that would also give you a guarantee your types are somewhat correct: and is more lightweight than full-blown specs


Hi there, what is the pattern for merging maps keeping into consideration subtractive operations? (i.e. (merge m1 m2) where a key in m2` does exist at all). I know merge-with would not work in this case because there is not key in m2


@richiardiandrea I think you'd need to do (select-keys m1 (keys m2)) for that?


ah cool yeah that would definitely work...


Thank you Sean I felt I was missing some core function but after a bit of googling it seems I am not missing anything


(and then merge m2 into that)


let's say I have some process that could produce one or more results, possibly in an asynchronous manner. a consumer would want to handle the results in order until the process is complete, like pipe the results to a web client. is there some generic interface I could put in front of this process so that I wouldn't have to pick one particular async lib (i.e. core.async, manifold, etc.)?

βœ… 3

I think callbacks are often the most general and can be adapted into any of the other abstractions


hmm good point


I tried something similar, ended up having the API with core.async for simplicity (in my case the clients implementations where using core.async anyway), but in case it can be of use:

(defprotocol Handler
  :extend-via-metadata true
  (handle* [this val cb] "Handle new value, calls cb with true if value was handled properly, false otherwise"))

(extend-protocol Handler
  (handle* [this val cb] (try (this val)
                              (cb true)
                              (catch Exception _
                                (cb false))))

  (handle* [this val cb] (try (a/put! this val cb)
                              (catch Exception _
                                (cb false)))))


The most comprehensive API to mimick would be the reactive streams api. It supports all the cancellation/failure stuff afaik.

βž• 2

I guess if consumers would want to schedule the actual work the process does using their async lib of choice then I would want a lib that implements it using that async lib anyway