Fork me on GitHub
#clojure
<
2019-06-09
>
nulligor01:06:57

hey anyone here has any experience with plumatic/schema?

seancorfield01:06:38

@igor.larcs Some, a long time ago. Back when it was Prismatic/Schema I think.

nulligor02:06:11

oh I was gonna ask on how to completely replace s/each since it can't work with coercions properly turns out the docs says to use abstract-map and that solved it 🙂

ikitommi19:06:52

was there some safety issues in reading strings from external source into keywords in Clojure? e.g. keywordize json keys.

seancorfield19:06:47

I believe it used to be that you could fill up memory by pumping unique JSON keys into code that keywordized them all, but I also believe that got fixed at some point -- changing keywords so they get GC when nothing refers to them?

Alex Miller (Clojure team)21:06:39

Yes, they get gc’ed via soft references

jjttjj20:06:20

is it a legitmate/idiomatic thing to do to create stateful transducers by wrapping existing transducers with the state, ie

(let [vold (volatile! nil)]
                    (map (fn [x]
                           (let [old @vold]
                             (vreset! vold x)
                             ;;trigger if last value was below but current
                             ;;value is above
                             (if (and old
                                      (< (get old :ema) (get old :sma))
                                      (> (get x :ema) (get x :sma)))
                               (assoc x :trigger true)
                               x)))))

potetm20:06:06

@jjttjj Well, the answer to that is… complicated.

potetm20:06:05

Take, for example, the map-indexed transducer:

([f]
   (fn [rf]
     (let [i (volatile! -1)]
       (fn
         ([] (rf))
         ([result] (rf result))
         ([result input]
          (rf result (f (vswap! i inc) input)))))))

potetm20:06:53

You see, there is some state that it tracks. And, if you know about transducers, there is a specific place to initialize that state.

potetm20:06:15

Now, your example is a little different than that.

potetm20:06:22

You’re wanting to look at a stream of values (e.g. from a channel or a seq), look at two values at a time: previous and current

potetm20:06:11

It just so happens that’s a concept clojure covers: partitioning

potetm21:06:58

Unfortunately, clojure doesn’t ship with a partition+step transducer

potetm21:06:56

IMO xforms pretty much a necessity when doing significant work w/ transducers.

🙂 4
đź‘Ť 4
jjttjj21:06:36

@potetm Yes, I believe clojure core partition transducers support a "step", i've used partition from https://github.com/cgrand/xforms which does work, as in:

(defn map-cross-over [k1 k2 f]
  (comp (x/partition 2 1 (comp
                          (x/into [])
                          (map (fn [[old new]]
                                 (if (and (< (get old k1) (get old k2))
                                          (> (get new k1) (get new k2)))
                                   (f new)
                                   new)))))))
but for one I've found it a little slower, but more importantly this is just one example of kind of a lot of custom transducers i'm doing and some are more complex than things that can be expressed easily even with a library like xforms and really need their own custom state

jjttjj21:06:20

I guess I'm mainly trying to figure out if there's any reason i'm missing to have the state fully outside of the transduer, rather than between the (fn [rf] ... and the other 3 arity transducing function?

potetm21:06:26

I’m having trouble imagining a scenario where either partitioning or reduction doesn’t cover it.

potetm21:06:28

At any rate: the whole concept of keeping state is antithetical to a mapping operation

jjttjj21:06:10

ok the first step is setting the triggers with the mapping above, the second step i need to start at each trigger, mark that map as an "entry" (by associng something), count six more X's from that and mark an exit, ignoring any triggers between the entry and exit. I think i definitely need some state in here that's not easily expressed by a more general function?

jjttjj21:06:48

but i might be missing a something obvious

jjttjj21:06:36

complete example:

(into []  (comp
           (map-indexed #(assoc %2 :i %1))
           
           (let [vold (volatile! nil)]
             (map (fn [x]
                    (let [old @vold]
                      (vreset! vold x)
                      ;;trigger if last value was below but current
                      ;;value is above
                      (if (and old
                               (< (get old :ema) (get old :sma))
                               (> (get x :ema) (get x :sma)))
                        (assoc x :trigger true)
                        x)))))
           (let [n  5
                 vi (volatile! nil)]
             (map (fn [x]
                    (let [i @vi
                          i (when i (vswap! vi inc))]
                      (cond
                        (and (not i) (:trigger x))
                        (assoc x :enter true :held (vreset! vi 0))

                        (and i (= i n))
                        (do (vreset! vi nil)
                            (assoc x :held i :exit true))

                        (and i (< i n)) (assoc x :held i)
                        :else           x))))))

      [{:ema 67.15, :sma 67.15}
       {:ema 67.14619047619048, :sma 67.13}
       {:ema 67.15321995464853, :sma 67.16}
       {:ema 67.15767519706296, :sma 67.17}
       {:ema 67.13884898781886, :sma 67.128}
       {:ema 67.1180062270742, :sma 67.09333333333333}
       {:ema 67.09057706259095, :sma 67.05571428571429}
       {:ema 67.06766496139181, :sma 67.03}
       {:ema 67.03455401268783, :sma 66.99555555555555}
       {:ema 66.9969774400509, :sma 66.96000000000001}])

potetm21:06:23

Having thought about it for all of 5 seconds: I would start with transduce (or maybe even regular reduce) and shove a lot of that logic into the reducing fn.

potetm21:06:26

In general, reduce is the go-to for tracking state across items.

jjttjj21:06:51

yeah originally i was using a reducing function, with a sort of context map to build up "state". but now i'm trying to make things work asynchronously and considering just putting everything in transducers so i can just stick it in a core.async chan

jjttjj21:06:29

so the con is that i have to use mutable state but the pro is i can use the same functionality for old data and live streaming data

potetm21:06:43

Not sure how the operations you just described could be easily parallelized.

jjttjj21:06:37

but what about if the new data doesn't exist yet, ie i have a chan just waiting for data i'd like to apply this to

potetm21:06:20

Then I would look at a specialized stateful transducer. (Not composing from existing fns like map.)

potetm21:06:41

But remember: stateful transducers in clojure.core cannot be parallelized.

potetm21:06:14

volatile gives consistent reads, but no write guarantees. So you’ll lose information for sure.

jjttjj21:06:12

gotcha, but is there a reason why say my way with the let wrappers containing state around (map ...) is worse than kind of just copying the source for the transducer version of map and sticking my state inside that? Is it just not idiomatic?

potetm21:06:57

Two reasons: The semantics of maps don’t allow for state. The semantics of transducers are that they’re arbitrarily composable.

potetm21:06:11

So take this example:

(into []
      (comp (map #(do (println %)
                      (inc %)))
            (filter odd?))
      (range 100))

potetm21:06:37

In this case, you get a println for every item, even though the output is only half the items.

potetm21:06:53

Imagine now that, instead of println, you kept some state.

potetm21:06:19

Well it turns out you were tracking state about items that were never emitted by the transducer chain.

potetm21:06:34

And that map operation has no way of knowing what another operation will do.

potetm21:06:25

It’s kinda like the question, “Why can’t I keep state in a mapping operation on a sequence?” Well, if you happen to force the whole sequence, that might just work. But the semantics of lazy evaluation doesn’t guarantee they’ll get run at all. So you’re kinda setting yourself up for confusing bugs.

jjttjj21:06:38

Gotta run for now but thanks a lot for this input!

đź‘Ť 4
jmclavijo22:06:21

Has anyone worked with Onyx? I have some questions.

seancorfield23:06:18

There's an #onyx channel here @jclavijo -- not sure how active it it. I think there was a pretty active Onyx channel on Gitter, at least a while back.

seancorfield23:06:09

https://gitter.im/onyx-platform/onyx is one of them. There's a learn-onyx channel on Gitter too. looks like that channel hasn't been active for a few years