Fork me on GitHub
#beginners
<
2024-03-13
>
Vladimir Pouzanov17:03:55

what's the general approach for guard functions? e.g. if the argument X is nil, return B, otherwise if (:val X) is not a map return B again, ... ? It can obviously be written with any kind of control flow (if or condp seems fitting), but it seems like the nesting just keeps growing. I'm slightly concerned about my line ends starting to look like )))))))

Samuel Ludwig17:03:26

multimethods are going to be what youre looking for i believe, its one of the main ways to attain polymorphism in clojure https://clojure.org/about/runtime_polymorphism

1
Vladimir Pouzanov17:03:29

that's fair, thanks!

practicalli-johnny18:03:10

cond is a nice simple approach and there are several variants of this function Function definitions can handle different arities already (basic polymorphism) Or add :pre and :post conditions to a function definition. Multi-methods if there are specific types / values to dispatch on. Only the editor needs to worry about closing parents... although nesting multiple if , when, let forms several times is not the recommended way to write Clojure

Ben Sless19:03:22

This also sounds a bit like you want some->

Vladimir Pouzanov19:03:52

it's kind of like some-> but with a default value, I suppose. Imperatively, that'd be an early return statement (or rust's ?)

Vladimir Pouzanov19:03:51

I tried multimethods, and they seem to do abut what I want, but there's some oddity with redefining them it seems. Sometimes cljs doesn't care and I have to re-start the node to get the repl into the proper state 😐

Samuel Ludwig19:03:12

ah, yea so their interaction is a little weird because they end up creating a little, persistent, table of sorts. you can redefine them by un-mapping them first, via (ns-unmap *ns* 'my-multimethod)

Vladimir Pouzanov19:03:31

ah! That's much better than restarting node, thanks.

Samuel Ludwig19:03:36

ofc! the reason for this weirdness, is that you can actually create additional methods (via defmethod) in namespaces other than where the original defmulti is (this can be useful as a sort of extension mechanism, especially for code you don't directly own/control (like code in a 3rd party library)). it does then require some kind of tracking that isnt directly connected to a single namespace

Vladimir Pouzanov19:03:42

that's interesting; it opens a whole new can of worms for me though.

Dallas Surewood21:03:21

I am confused about a line in the clojure framework biff that uses doseq.

(defn alert-new-user [{:keys [biff.xtdb/node]} tx]
    (doseq [_ [nil]
            :let [db-before (xt/db node {::xt/tx-id (dec (::xt/tx-id tx))})]
            [op & args] (::xt/tx-ops tx)
            :when (= op ::xt/put)
            :let [[doc] args]
            :when (and (contains? doc :user/email)
                       (nil? (xt/entity db-before (:xt/id doc))))]
      ;; You could send this as an email instead of printing.
      (log/info "WOAH there's a new user")))
How does the [op & args] (::xt/tx-ops tx) line work? It's outside of the let form. I tried to do this myself and couldn't get it working.
(doseq [x [nil 3]
          [op & args] [1 2 3]
          :when (= 3 x)]
    (println 3))

Bob B21:03:14

it's a 'normal' destructuring of elements of a sequential thing, but it's destructuring the elements (because it's in a doseq)

(doseq [x [nil 3]
        [op & args] [[1 2 3]]
        :when (= 3 x)]
  (println x op args))
=> 3 1 (2 3)

Bob B22:03:53

to put it differently:

(doseq [x [1 2 3]]
  (println x))
in this case, x is 1 and then 2 and then 3. If x is replaced with a destructuring form, an attempt will be made to destructure 1 as a seq, which leads to the error about creating an ISeq from a Long.

huy22:03:32

Hi @seancorfield, you mentioned https://www.reddit.com/r/Clojure/comments/rq51mg/how_can_i_test_my_clojure_code_without_rejacking/hqarg7r/ that you don't use the Stuart's "reloaded" workflow. Could you elaborate a little more about your typical workflow? Thanks.

seancorfield00:03:14

I use Component but I don't use any reload/refresh machinery while I'm working. I just always eval every change I make in my editor so my running REPL is always up-to-date, and I write REPL-friendly code where possible -- see https://clojure.org/guides/repl/enhancing_your_repl_workflow#writing-repl-friendly-programs

seancorfield00:03:47

If I need to rename/remove a def(n), Clojure has standard functions for unmapping stuff from namespaces. I have a hotkey bound in Calva to "zap" the contents of the current ns and then I can eval that file again. I very rarely need that.

huy17:03:56

Thank you for your answer. You don't use it because you don't like the idea of resetting the state or because you don't find the need to? What's the purpose of using Component if you're not using the reloaded workflow? (sorry I'm new to all of this)

seancorfield17:03:44

Component manages the lifecycle of dependencies. The reloaded workflow is purely about dev/repl. I've never needed any reload/refresh machinery -- and I see people run into problems with it quite often here on Slack. I think developing good REPL-based habits is better than relying on some sort of reload/refresh "crutch".

seancorfield17:03:43

A fairly simple example of using Component: https://github.com/seancorfield/usermanager-example/blob/develop/src/usermanager/main.clj The Application depends on the Database. The Web Server depends on the Application. Component ensures the Database is started first, then the Application, then the Web Server. If you stop things (in the REPL), it will stop them in reverse order.

👍 1
huy17:03:37

Ah, now it makes sense to me. Thanks!

huy17:03:46

Do you know in the reloaded workflow, Clojure typically recompiles everything or only the new changes?

seancorfield17:03:12

My understanding is that it deletes namespaces and then completely reloads them, which would force a recompile from disk of everything. That would be insanely slow for a codebase of our size. My workflow -- eval'ing every change I make -- doesn't require files to be saved to disk (I can save files whenever you want -- it doesn't impact what code is eval'd or run/tested). I also do not use any "watch" processes (like run tests on file changes) -- I run tests via hotkeys in my editor so I don't have to change context or worry about tests running when I'm only partway through making a change.

huy19:03:48

If it needs to recompile everything, doesn't that defeat one of the main selling points of Clojure, which is partial/incremental compile? I've been thinking a lot about that since I first saw the reloaded workflow. Thanks for confirming my doubt!