Fork me on GitHub
#clojure
<
2023-03-28
>
Drew Verlee15:03:59

My idea, which I would appreciate feedback on, Is that during local development between a browser client and the server, if the browser client sends a request to the server that throws an exception, that exception should get raised as if I manually triggered the exception in the server program. I might not be describing this properly, so let me put it another way.. if I evaluate (/ 0 0) from emacs, cider/nrepl captures it, and I get a buffer with the exception. If the browser client sends a request to the server to process (/ 0 0) the exception just ends up in the logs, where it's hard to find. How do people tighten the feedback loop? build tools on top of the logs? Just have tests for everything?

jpmonettas15:03:01

you can configure your exception handling so at dev time the unhandled exceptions end up in the response instead of the logs. I guess it depends on the stack, but can be accomplished by a ring middleware that you only add on the dev system

👀 2
respatialized15:03:57

Alternatively, you can add an optional NREPL config to your server so you can REPL into it at dev time

👀 2
lukasz15:03:59

This will depend on your setup, but if you use something like Component, you can call your server code from the REPL just like anything else: • start the system • call the http handler with the same request data that your client sends that throws the exception Or you can include exception info in the HTTP response - although that's not quite as interactive as the REPL-based approach

👀 2
👍 2
lukasz15:03:42

(Component is not strictly required, but it makes this approach a lot easier)

👀 2
Drew Verlee16:03:41

Interesting, i'm going to have to stew on this for a bit. I might be barking up the wrong tree a bit. I probably want to focus my efforts around being able to capture the state coming from the client, then just using that at points of interest in my application. The exceptions should get triggered if i do that. I think this is close to what Lukasz is suggesting.

jpmonettas16:03:13

@U0DJ4T5U1 I recentrly demoed how I do that kind of stuff with FlowStorm in a "Show me your repl" episode. The request exploration and handling is here https://youtu.be/2nH59edD5Uo?t=2288 maybe it helps you

🙏 2
👍 2
lukasz16:03:29

There's many ways to capture incoming request - tap> def, an atom in your request handler namespace.

👍 2
lukasz16:03:29

Ah, that YT video is basically how I work but much much much less fancy - just Monroe + nrepl

🙏 2
seancorfield16:03:17

I do what @U0JEFEZH6 suggests: I start my server app from my REPL, and I use tap> very heavily with Portal. So I see all the server-side exceptions, and I can send requests and responses to Portal easily with a bit of middleware.

practicalli-johnny17:03:27

I concur with Sean, although I use Integrant rather than component (or mount / donut , etc) I have https://practical.li/clojure/data-inspector/portal/#repl-commands and it's a great experience. I also sent the mulog event logs to portal if I want more information

jdkealy17:03:44

I've got a deeply nested map that is sometimes different. In all cases there's a string in the middle and I just want to get its value. e.g. {:hits {:hits {:_source {:name "John"}... {:hits {:hits [{:_source {:name "John]... {:hits {:hits [{:_source {:hits {:_source {:name "John.. I'm having a hard time writing different functions for different structures, I want to just traverse and call vals if it's a map, take the first if it's a vector, and keep going until I either get a string or null.

pppaul17:03:23

aggregating all strings is pretty easy to do with walk. I can post in a few minutes

p-himik17:03:19

(filter string? (tree-seq ... data)) should do it. Just gotta provide the right .... Probably (filter string? (tree-seq coll? identity data)).

pppaul17:03:55

nice tree-seq example, was just going to mention that

jdkealy18:03:11

ok it works well thanks!

jdkealy18:03:33

how would you use for ?

hiredman18:03:52

tree-seq but with more control

hiredman18:03:11

(fn f [m]
  (cond (string? m)
        [m]
        (coll? m)
        (for [i m
              ii (f i)]
          ii)))
and then you can do all sorts of tweaks in the for and the cond, like adding the taking the first of a vector logic

cheewah14:03:06

i recently discovered https://github.com/redplanetlabs/specter and like it a lot so here you go...

(use '[com.rpl.specter :refer [select codewalker]])
(select (codewalker string?) {:hits {:hits {:_source {:name "John" :nickname "Also John"}}}})
; ["John" "Also John"]

pppaul17:03:34

very nice specter example, i didn't know that it had walking functions.

pppaul17:03:14

this is a walk version of what specter is doing

(let [data {:hits {:hits {:_source {:name "John"}}}}
      acc (volatile! [])]
  (->> data
    (clojure.walk/postwalk
      (fn [form]
        (when (string? form)
          (vswap! acc conj form))
        form)))
  @acc)
@U02SMNATXCY does specter walk let you operate on context switches (when you enter & leave a node)?

cheewah15:08:24

@U0LAJQLQ1 my apologies somehow i didn't see your question addressed to me when it was posted, and only see it today when i happened to scroll down on my slack threads. i am not sure what type of operation you want to perform, perhaps if you can provide some sample input and output data so that i can try to figure it out? (it had been a long time already, so feel free to ignore this message if it is no longer needed)

pppaul16:08:59

i don't know if a context based walk is something that can be done without a bit of a complicated walk, but it basically means that if you can identify a node, then you can tell when you enter and leave that node, so you can do 2 operations per walk. i use this a lot to build accumulators for things that i haven't built a proper graph out of (just regular clojure trees).

(defn- context-prewalk [before-f after-f form]
  (walk/walk (partial context-prewalk before-f after-f) after-f (before-f form)))

(defn materialize-type-hierarchy [o]
  (let [types (volatile! [])]
    (->> o
         (context-prewalk
           (fn [form]
             (match form
                    {:type ?type} (let [new-type (vswap! types conj ?type)]
                                    (assoc form :type new-type))
                    :else form))
           (fn [form]
             (match form
                    {:type ?type} (do
                                    (vswap! types pop) ;; assume that types are 1 arity
                                    form)
                    :else form))
           ))))
this is an example where i take a tree with :types on nodes, and accumulate, per node, all the parent types. keep in mind that this doesn't mean every node has a type, but all the nodes that do have a type will have a list of all their parent types. @U02SMNATXCY

cheewah09:09:22

spectre walker/codewaker doesn't have context... to achieve what you described the spectre way, i think recursive navigator is the way to go https://github.com/redplanetlabs/specter/wiki/Using-Specter-Recursively

(ns rmain
  (:require [com.rpl.specter :refer [recursive-path collect-one keypath ALL if-path FIRST STAY if-path nthpath STOP transform]]))


(def TYPE-COLLECTOR
  (recursive-path [] p [(collect-one (keypath :type))
                        ALL (if-path [FIRST #(= :type %)] STAY
                                     [(if-path [(nthpath 1) #(map? %)] [(nthpath 1) p] STOP)])]))

(defn xform [& args]
  [:type (into [] (drop-last args))])

(transform [TYPE-COLLECTOR] xform {:type :a,
                                   :b :c,
                                   :d {:type :e,
                                       :f :g
                                       :g {:i {:j :k
                                               :type :l}}}})

; {:type [:a], :b :c, :d {:type [:a :e], :f :g, :g {:i {:j :k, :type [:a :e nil :l]}}}}
TYPE-COLLECTOR recursively search for key-value pair with :type key and collects it value into a vector, and also 'stay' at the key-value pair location (i.e. target it for transform) transform then feed its output to xform function, which then replaces the target key-value with a transformed from of collected values

pppaul13:09:23

thanks! i'll try that out.

Joshua Suskalo18:03:33

Is there anything in particular that makes it a very bad idea to attach reactive/watch-style functions to an agent as a "validator-fn" which always returns true?

Alex Miller (Clojure team)18:03:51

depends what your expectations are wrt concurrency

Joshua Suskalo18:03:33

right, that makes sense, since the result of the validate-fn would occur before the value is made available in the agent.

Alex Miller (Clojure team)18:03:52

well, I think it's the opposite actually

Alex Miller (Clojure team)18:03:29

iirc, the agent changes state and only after (outside that scope) notifies watches/validators

Joshua Suskalo18:03:29

Thanks! I'll read over that.

Joshua Suskalo18:03:39

it's been a while since last time

Alex Miller (Clojure team)18:03:50

so validators are in the change scope so I think it would be generally bad for those to be effectful

Joshua Suskalo18:03:18

makes sense. I'll go forward with not relying on a validator for reactivity then.

Janet A. Carr19:03:20

In a transducer signature, there is an argument rf in the documentation here https://clojure.org/reference/transducers#_creating_transducers but I can't find what rf stands for/means. I've been calling it "reducing function", but I feel like that might be wrong.

p-himik19:03:09

It's not wrong. At the very top, the page describes the terminology. Although it doesn't explicitly tell that "rf" stands for "reducing function".

Janet A. Carr19:03:38

ah okay. Good to know I haven't be passing off falsehoods on the interwebz

ghadi19:03:42

rf = reducing fn = the thing that transducers transform you'll also see xf for the transducer itself (in the signature of transducing contexts, like c.c/transduce)

thom08:03:53

It's reasonable to be confused by this because in many ways the terms are being overloaded with multiple meanings and reduction isn't in any way key to transducers. The 'reducing function' (also called a 'reducing step function' in other parts of the docs) isn't always what you'd think of as the normal arity-2 function passed to reduce itself. Take a look at the reducing functions in clojure.core - the 3-arity, innermost function returned inside all the transducers. They almost never do anything with result which would be the accumulator in a normal reduction - they just pass it on or return it. In fact, it wouldn't make much sense for transducers to do any sort of reduction because if they did, you'd no longer be doing a step-wise transform over a sequence, and you could just use normal function composition because you'd have a single value to pass forward. The reduced mechanism is only really used in transducers to short circuit, so even there the name clouds the meaning. The only time that something we'd recognise as reduction is happening is when you call transduce and specify a reducing function as its f argument. You'll have seen lots of examples with + as the reducing function because it handily returns 0 when called with no args, acts as identity with 1 arg, and reduces (actually reduces!) with two args. But instead look at sequence which can take an xform. It's implemented in Java code, but the inner reducing function it uses completely ignores the accumulator value! Everywhere you're using result inside a reducing function? It's just null and ignored. But sequence elsewhere stashes away the (transformed) output of the transducer stack and lets you have it back as a sequence. No reducing is happening, just transforming sequences. Elsewhere, into supports an xform argument - what's its reducing function? It's just conjing stuff back together! So to me, the interesting bits of transducers are that they give you an efficient and composable way of creating sequence-to-sequence transformations. Any and all mentions of reducers are necessary plumbing but slightly distracting from the core ideas.

clojure-spin 2