Fork me on GitHub
#beginners
<
2022-09-05
>
sheluchin12:09:05

Is there some core function that helps with adding a stateful transducer to an xform that has your own logic? Something to add a xform step that receives both the current value and the previous one to build things like dedupe?

skylize14:09:34

You need to close over a stateful data structure.

(defn foo-transducer
  [rf]
  (let [previous (volatile! nil)]
    (fn ([] (rf))
      ([result] (rf result))
      ([result input]
       (let [prev @previous]
         (vreset! previous input)
         (transform-somehow input prev))
       ))))
Search goog for "stateful transducer" to find more examples. The docs for transducer includes an example that uses a Java ArrayList instead of volatile!. https://clojuredocs.org/clojure.core/transduce#example-59523d78e4b06e730307db43

sheluchin14:09:52

@U90R0EPHA yes, I understand that's how the stateful transducers are implemented, but I'm wondering if there's a helper which kind of abstracts this process to something like a map xf but with access to the previous value. Guess not. It's not that difficult to write but I thought it might be a common enough case that there's a fn for it.

skylize14:09:09

Not in core. But maybe someone already has it in a library?

pithyless20:09:06

1. dedupe is already a transducer, but I'm guessing you mean implementing something similar? 2. are you familiar with https://github.com/cgrand/xforms ? It's a good toolkit of ready-to-use transducers (and I suppose can also be used as guide for building more complex ones) 3. your specific example of mapping over the current item and the previous, I would probably implement via xforms as: (x/partition 2 1 (x/reduce xf))

sheluchin10:09:32

Thanks @U05476190. I've been browsing over xforms a bit but using the functions you mentioned wasn't obvious to me. I ended up just implementing the transducer without any such helpers, but I'm curious to try those and other parts of the lib at some point.

Skiamakhos15:09:55

Quick question - in Emacs is there a keybinding in clojure-mode that sorts out indentations in clojure code? I'm fairly sure there's something like it in org-mode, but it's been a few months since I was looking at Emacs & Clojure, and I've forgotten a bunch.

agile_geek15:09:54

I tend to use paredit and call paredit-reindent-defun (M-q). You can call clojure-align from Clojure mode (I have that mapped to C-c SPC). Alternatively call cider-format-buffer or cider-format-defun but you’ll need to set key bindings for those in your init.el

👍 2
Joe Dumont22:09:29

Hi, I'm trying to troubleshoot a CORS error. I followed a couple previous suggestions here about using Ring wrap-cors middleware https://github.com/prestancedesign/todo-backend-reitit/blob/master/src/todo_backend/core.clj#L61 as well as trying someone's custom handler

(defn allow-cross-origin
  ([handler]
   (allow-cross-origin handler "*"))
  ([handler allowed-origins]
   (fn [request]
     (if (= (:request-method request) :options)
       (-> {:status 200 :body ""}
           (assoc-in [:headers "Access-Control-Allow-Headers"] "*")
           (assoc-in [:headers "Access-Control-Allow-Origin"] allowed-origins)
           (assoc-in [:headers "Access-Control-Allow-Methods"] "HEAD,GET,POST,DELETE"))

       (-> (handler request)
           (assoc-in [:headers "Access-Control-Allow-Headers"] "*")
           (assoc-in [:headers "Access-Control-Allow-Origin"] allowed-origins)
           (assoc-in [:headers "Access-Control-Allow-Methods"] "HEAD,GET,POST,DELETE"))))))
But neither did the trick for me, and I'm not sure how to proceed. Any ideas where I went wrong? I don't have very informed ideas about middleware order in case that is an issue, but I think I understand the "bottom" middleware is applied first, so I thought cors middleware should appear there as it does in the "todo" example I first linked. The departure from the "todo" example is that I'm using http-kit instead of jetty, though I understand it is ring compatible. Thanks for reading.
(def app
  (ring/ring-handler
   (ring/router
    [["/" {:name ::root
           :get (fn [_] {:status 200
                         :body {:info "Backend API"}})}]
     ["/api" {:name ::api
              :get {:parameters {:query {:url string? :start string? :end string?}}
                    :responses {200 {:body {:file string?}}}
                    :handler (fn [{{{:keys [start end url]} :query} :parameters}]
                               (get-sample start end url)
                               {:status 200
                                :body {:file (str "samples/" (hash (str start end url)) "." extension)}})}}]]
    {:data {:coercion reitit.coercion.spec/coercion
            :muuntaja m/instance
            :middleware [format-negotiate-middleware
                         format-response-middleware
                         rrc/coerce-response-middleware
                         exception-middleware
                         rrc/coerce-exceptions-middleware
                         format-request-middleware
                         rrc/coerce-request-middleware
                         parameters/parameters-middleware
                         ;;allow-cross-origin
                         [wrap-cors :access-control-allow-origin #".*"
                          :access-control-allow-methods [:get :put :post :patch :delete]]]}})))

seancorfield22:09:49

Don't try to write your own -- you'll almost certainly get it wrong -- use the "standard" Ring wrap-cors middleware -- it works. The most common thing people do wrong is put it at the wrong end of the middleware stack: it should be at the outermost layer of it all. I have no idea which way round reitit works. And it needs to be outside any routing -- otherwise your own handler code will try to handle the OPTIONS calls (incorrectly).

seancorfield22:09:07

If you're still stuck, ask in #reitit

Joe Dumont23:09:41

Thanks. I agree and am trying to use wrap-cors. My code fragment above is modeled off the same setup as the "todo" example and the same middleware position, but still giving CORS errors. I'll keep trying and ask in #reitit if I'm still stuck.

Joe Dumont23:09:44

Aha! I'm not sure how it can be working for the linked example, but moving it from the bottom of the middleware stack (what I thought was applied first) to the top did the trick.

seancorfield23:09:50

Yup, a very common mistake unfortunately. Glad you got it working!