Fork me on GitHub
#beginners
<
2021-06-13
>
caumond15:06:02

Hi guys, I built a defprotocol implemented in many defrecord. Here an oversimplified version:

(defprotocol A
  (foo [_ b])
  (bar [_ b]))

(defrecord R1 []
  A
  (foo [_ b]
    (- b 10))
  (bar [_ b]
    (+ b 10)))
(defrecord R2 []
  A
  (foo [_ b]
    (* b 10))
  (bar [_ b]
    (/ b 10)))
I don't find a smart way to carry the same tests (= a (-> a (.foo) (.bar))) on all implementations without copying some boilerplate for R1, R2, R3, ... Is there any way to factorize?

caumond15:06:13

Right now, I have to copy my tests on all deftest for every defrecord...

indy15:06:48

Wrap the tests in a function that takes the record as arg?

caumond16:06:36

Hmm, fair enough. Some "utility test functions" including testing sentences.

indy16:06:06

Also checkout https://clojuredocs.org/clojure.test/are

(are [a] (= a (-> a foo bar))
     r1
     r2)

indy16:06:12

Actually this is what are is for, my bad for not recommending it at the start

alexmiller17:06:28

FYI you should not use Java interop to invoke protocol methods on a record, you should invoke the protocol method

eric wolf19:06:08

Idiomatic question, is this proper clojure idiom to use for updating a data structure based on an expensive calculation? It's convenient to use let to store a series of intermediate results that depend on previous let values. I'm not sure if that is expected practice. (defn buncha-calcs "returns processed-data with a-data and b-data data structure fields updated" [processed-data input-data] (let [stage-1 (expensive-calc input-data) a-results (calc-a (:a-data processed-data) stage-1 ) b-results (calc-a (:b-data processed-data) stage-1 ) ] (assoc (assoc processed-data :a-data a-results ) :b-data b-results)))

jaihindhreddy21:06:01

> It's convenient to use let to store a series of intermediate results that depend on previous let values. I'm not sure if that is expected practice. That's exactly what let is meant for. assoc can take multiple key-values, which means, you can do this with one call to assoc, like this:

(defn buncha-calcs
  "returns processed-data with a-data and b-data data structure fields updated"
  [processed input]
  (let [stage-1   (expensive-calc input)
        a-results (calc-a (:a-data processed) stage-1)
        b-results (calc-a (:b-data processed) stage-1)]
    (assoc processed :a-data a-results :b-data b-results)))
I also removed the -data suffix from the arg-names. Because Clojure is a very data-centric language, IMO it's reasonable to assume something is data unless mentioned otherwise. This is a personal opinion though. Now, you are effectively applying the function calc-a to the values of two keys, along with an extra argument. update does this more directly as opposed to manually getting the value, applying the function, and associating it back-in. Looks like this:
(defn buncha-calcs
  "returns processed-data with a-data and b-data data structure fields updated"
  [processed input]
  (let [stage-1 (expensive-calc input)]
    (update
      (update processed :a-data calc-a stage-1)
      :b-data calc-b stage-1)))
Finally, operations like assoc, dissoc, conj and update are collection-level functions (take a collection as their first arg and return a collection), and compose well with the thread-first macro (`->`), which when used the code becomes more linear to read:
(defn buncha-calcs
  "returns processed-data with a-data and b-data data structure fields updated"
  [processed input]
  (let [stage-1 (expensive-calc input)]
    (-> processed
        (update :a-data calc-a stage-1)
        (update :b-data calc-a stage-1))))
^ This is the most idiomatic way to write this IMHO. And if you haven't encountered threading macros before, I highly recommend checking out http://clojure.org/guides/threading_macros. PS: FYI you can use triple back-ticks to format multiline code-snippets.

🎯 2
💡 2
eric wolf23:06:57

Thanks, that is super helpful

omendozar22:06:03

Hi, I’m having this error when publishing to heroku.

Could not transfer artifact net.sf.saxon:saxon:pom:9 from/to jboss (): Connection reset

       This could be due to a typo in :dependencies, file system permissions, or network issues.

       If you are behind a proxy, try setting the 'http_proxy' environment variable.

       Uberjar aborting because jar failed: Could not resolve dependencies
My dependencies:
:repositories [["enonic" ""]]
  :dependencies [[camel-snake-kebab "0.4.2"]
                 [clj-http "3.10.1"]
                 [environ "1.2.0"]
                 [integrant "0.8.0"]
                 [integrant/repl "0.3.1"]
                 [io.xapix/paos "0.2.4"]
                 [metosin/reitit "0.5.10"]
                 [org.clojure/clojure "1.10.1"]
                 [org.postgresql/postgresql "42.2.14"]
                 [ovotech/ring-jwt "1.3.0"]
                 [ring "1.8.2"]
                 [seancorfield/next.jdbc "1.1.613"]
                 [buddy "2.0.0"]
                 [clojure.java-time "0.3.2"]
                 [ring/ring-mock "0.4.0"]
                 [ring-cors "0.1.13"]]
Does anyone knows how to solve this?