Fork me on GitHub
#clojure
<
2021-08-19
>
didibus04:08:20

Why isn't halt-when implemented like:

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

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

Ed12:08:00

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]
     (fn
       ([] (rf))
       ([result]
        (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]
     (fn
       ([] (rf))
       ([result]
        (if (and (map? result) (contains? result ::halt))
          (::halt result)
          (rf result)))
       ([result input]
        (if (pred input)
          (let [r (if retf
                    (retf rf result input)
                    result)]
            (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?

dmarjenburgh08:08:06

@didibus There's take-while:

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

didibus22:08:06

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

rcarmo13:08:59

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

vemv13:08:53

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

rcarmo13:08:15

Well, another advantage of type hints is implicit documentation

rcarmo13:08:07

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

dpsutton13:08:24

docstrings are the correct vehicle for that

rcarmo13:08:27

so I was just trying to be consistent

delaguardo13:08:19

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

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

rcarmo13:08:21

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

ghadi13:08:16

Adding superfluous type hints will make it less readable

πŸ’― 9
ghadi13:08:31

docstrings ftw

rcarmo13:08:33

s/readable/understandable/

ghadi13:08:46

Still, disagree hard

ghadi13:08:57

Type hints tell you nothing about semantics

πŸ’― 9
vemv13:08:06

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

rcarmo13:08:20

this will eventually be refactored to use spec

rcarmo13:08:40

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

p-himik13:08:02

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

didibus22:08:23

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

vemv13:08:47

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

rcarmo13:08:36

yeah, but I like the idea of bespoke

rcarmo13:08:47

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

πŸ™‚ 3
dpsutton13:08:55

speced and instrumented functions sound like the best plan

vemv14:08:47

The use case #2 per https://github.com/stathissideris/spec-provider/tree/f9e4b3a9a54752a1c2c8df0291ff517679442a1a#use-cases would be interesting here (TIL about that specific capability from spec-provider... that's why I love answering questions :) )

didibus22:08:02

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

didibus22:08:11

(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

didibus22:08:23

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.

didibus22:08:07

You could also consider pre/post and something like Truss, that would also give you a guarantee your types are somewhat correct: https://github.com/ptaoussanis/truss#assertions-within-prepost-conditions and is more lightweight than full-blown specs

richiardiandrea22:08:04

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

seancorfield23:08:34

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

richiardiandrea23:08:51

ah cool yeah that would definitely work...

richiardiandrea23:08:40

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

seancorfield23:08:45

(and then merge m2 into that)

lilactown23:08:33

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
rutledgepaulv23:08:45

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

lilactown23:08:25

hmm good point

bortexz07:08:45

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
  Fn
  (handle* [this val cb] (try (this val)
                              (cb true)
                              (catch Exception _
                                (cb false))))

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

dominicm08:08:45

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

βž• 2
lilactown23:08:45

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