Fork me on GitHub
#beginners
<
2021-04-19
>
Sibelius Seraphini15:04:02

how can I add a (print) in this code to inspect balances value?

walterl15:04:09

Short answer: (if-let [balances (doto (b/list-balances (:account context)) println)]

walterl16:04:34

Generally I use tap> rather than println, with Reveal's tap hooked up

Sibelius Seraphini16:04:13

reveal looks awesome tks

Sibelius Seraphini16:04:29

where can I put this `

:reveal {:extra-deps {vlaaad/reveal {:mvn/version "1.3.206"}}
         :ns-default vlaaad.reveal
         :exec-fn repl}
in my project.clj?

walterl17:04:14

I have {:reveal {:dependencies [[vlaaad/reveal "1.3.206"]]}} in my ~/.lein/profiles.clj, and use with lein with-profile +reveal repl and then in the REPL (do (require '[vlaaad.reveal :as reveal]) (add-tap (reveal/ui)))

grazfather16:04:55

is there a quick and easy way to get a relative path and generate a full path?

grazfather16:04:33

(absolute path)

mathias_dw18:04:34

(.getAbsolutePath (io/as-file “your path”))

grazfather18:04:19

Why as-file over file?

grazfather16:04:59

(.getAbsolutePath (io/file filename)) works

grazfather17:04:04

thank you. That’s exactly what I need

Sibelius Seraphini16:04:49

I’ve tried this config

Sibelius Seraphini16:04:12

I’m trying to output this log/debug on my repl

[clojure.tools.logging :as log]
any ideas of how to fix it?

Franco Gasperino16:04:09

look above, it looks like you're working inside pedestal?

Franco Gasperino17:04:16

(ns your.project.ns
  (:require [io.pedestal.log :as log]))

(log/info "This is a test" {:key-1 "my-value})

Franco Gasperino17:04:30

that will send to log4j configured w/ pedestal

Sibelius Seraphini17:04:49

this is a good trick

Franco Gasperino17:04:39

also, the tap> function is pretty useful if you want to send a message to a backend queue, and provide different models to handle it

Franco Gasperino17:04:43

The (tap>) can be consumed by adding (add-tap f), where f is a function which will be called with the arguments sent to the (tap>)

Franco Gasperino17:04:28

thus, the function passed to add-tap can be a pedestal log, another with a console print, or into some future endpoint

Franco Gasperino17:04:52

decoupling the produce/consume

Sibelius Seraphini17:04:27

I’m gonna read more about tap

Sibelius Seraphini17:04:32

I’m pretty new to clojure in general

Franco Gasperino17:04:49

Once you're aware of tap, you'll see it used often in really useful areas

Sibelius Seraphini18:04:54

I’m stuck in how to run reveal from this lein project

Franco Gasperino18:04:37

what is your project.clj?

Franco Gasperino18:04:51

and also what editor are you using to invoke lein?

Franco Gasperino18:04:22

Here is how i'm using tap> with pedestal logging, while keeping the door open to additional tap consumers going forward:

Franco Gasperino18:04:23

(ns my.logging
  (:require [io.pedestal.log :as log]))

(defn tap->logback [content]
  (let [{:keys [level message context]} content]
    (cond
      (= level :info) (log/info message context)
      (= level :debug) (log/debug message context))))

;; Configure this handler to consume from the tap.
(add-tap tap->logback)

(ns my.handler)

(def my-handler {
  :name :my-handler
  :enter
    (fn [context]
      (let [request (:request context)]
        (tap> {:level :debug :message "" :context request})))})

Franco Gasperino18:04:35

thats a simple example

Franco Gasperino18:04:34

when you want to disable that pedestal logging, you may remove-tap.

Franco Gasperino18:04:52

and you may add/remove it at runtime

Sibelius Seraphini18:04:33

I’m gonna try this inspect

Franco Gasperino18:04:11

inspect is part of shadow-cljs, and is for clojurescript

Franco Gasperino18:04:05

uses the same tap mechanism though, but the implementation is something akin to javascript websocket -> browser display

Pablo17:04:54

Hello everybody I am using some vars like:

(def status.scheduled 1)
(def status.canceled 2)
But when I’m trying to use them into a function I’m getting a syntax error (`Syntax error (ClassNotFoundException) compiling at (src/clj/awp/services/appointment.clj:3:8).` status.scheduled).
(def scheduled-or-scheduled
  [{:keys [status] :as appointment}]
  (some #{status} [status.scheduled status.canceled]))
I found that I need to use the fullnamespaced name awp.services.appointment/status.scheduled in order to get it work but, is there a nicely way to call this vars?

manutter5117:04:50

I thought maybe you were just using a bad variable name, but now I’m not sure I understand what you’re doing. Is status.scheduled something defined by a Java lib you are using?

manutter5117:04:50

If not, you might be better off using a namespaced keyword like :status/scheduled instead of using a symbol with a dot in the name.

Pablo17:04:31

No, they are constants from my DB. I want to group them by some way becase I also have

(def type.interview 1)
(def type.ordinary-session 2)
(def type.extraordinary-session 3)

manutter5117:04:38

Ok, you may be running into problems because the '.' in the middle of a symbol has special meaning to the Clojure compiler.

manutter5117:04:59

We normally used namespaced keywords for that sort of thing, so it’s not usually a problem.

Pablo17:04:15

Ok, I see the problem. Thanks a lot!

👍 2
grazfather18:04:22

would anyone be willing to take a look at a babashka script I wrote? Just looking for ideas where I could have done things better https://pastebin.com/Qa5JSeGy

jkrasnay21:04:15

I think your extract-host-project function could be a little simpler with the power of destructuring

piyer19:04:34

Cider question: Is there a way to jump to repl from the buffer?

dpsutton19:04:57

m-x cider-switch-to-repl-buffer. i think it already has a default key binding

piyer19:04:21

ah nice. Thank you

Darrell19:04:05

I’m trying to create a set based on lines from a file. Is this a valid approach?

(def co_set #{})
(with-open [rdr (reader "customer_numbers.txt")] (doseq [line (line-seq rdr)] (def co_set  (conj co_set line))))

Darin Douglass19:04:23

i'd reach for into here: (into #{} (line-seq rdr))

noisesmith20:04:43

using def inside other code is very rarely a good idea

👍 2
piyer19:04:26

you are def co_set on every seq.

Darrell19:04:39

I’m assuming that wouldn’t replace co_set?

Darin Douglass19:04:35

it would, but in my experience non-top-level defs are frowned-upon

Prabu Rajan19:04:03

Hi, I have the following spec for the below function. I want to validate that the passed in menu contains the item requested in the order item. My goal is to remove the nil checks and throw exception in my function code. My state looks like the following. If I pass in invalid input, lets say I pass in an order-item with a name that does not exist in my menu, the fdef does not validate that. Any idea what I am doing wrong in the fdef?

{:orders {:e4d55743-c964-48e8-9cd1-cb1cf9075617 [#:helloworld.restaurant{:name "chilly parotta",
                                                                                                :quantity 3}
                                                                        #:helloworld.restaurant{:name "masal dosa",
                                                                                                :quantity 2}]},
                        :menu {:kothu-parotta #:helloworld.restaurant{:name "Kothu Parotta", :price 9.5, :quantity 50},
                               :chicken-biriyani #:helloworld.restaurant{:name "Chicken Biriyani",
                                                                         :price 10.5,
                                                                         :quantity 50},
                               :plain-dosa #:helloworld.restaurant{:name "Plain Dosa", :price 9.5, :quantity 50},
                               :butter-naan #:helloworld.restaurant{:name "Butter Naan", :price 3, :quantity 50},
                               :paneer-butter-masala #:helloworld.restaurant{:name "Paneer Butter Masala",
                                                                             :price 9.5,
                                                                             :quantity 50},
                               :mutter-paneer #:helloworld.restaurant{:name "Mutter paneer", :price 10.5, :quantity 50},
                               :masal-dosa #:helloworld.restaurant{:name "Masal Dosa", :price 8.5, :quantity 48},
                               :chilly-parotta #:helloworld.restaurant{:name "Chilly Parotta", :price 9.5, :quantity 47},
                               :mutton-biriyani #:helloworld.restaurant{:name "Mutton Biriyani",
                                                                        :price 10.5,
                                                                        :quantity 50}}}
(defn update-menu-item-for-order-item [menu order-item]
  (let [name (::name order-item)
        item-id (keyword (slugify (::name order-item)))
        order-quantity (::quantity order-item)]
    (update-in menu
               [item-id]
               (fn [item]
                 (if (nil? item)
                   (throw (IllegalArgumentException. (str "Sorry, we don't have " name " in our menu")))
                   (if (< (::quantity item) order-quantity)
                     (throw (IllegalArgumentException.
                              (str "Not enough " (::name item) ", we are short by "
                                   (- order-quantity (::quantity item))
                                   " . Please correct the order and re-submit")))
                     (assoc item ::quantity (- (::quantity item) order-quantity))))))))

(s/def ::name string?)
(s/def ::price number?)
(s/def ::quantity number?)
(s/def ::order-item (s/keys :req [::name ::quantity]))
(s/def ::order-items (s/coll-of ::order-item))
(s/def ::menu-item (s/keys :req [::name ::price ::quantity]))
(s/def ::menu-item-id keyword?)
(s/def ::menu (s/map-of ::menu-item-id ::menu-item))

(s/fdef restaurant-save-menu :args (s/cat :menu-items (s/coll-of ::menu-item)))

(s/fdef update-menu-item-for-order-item
        :args (s/and (s/cat :menu ::menu :order-item ::order-item)
                     #(contains? (:menu %) (keyword (slugify (::name (:order-item %))))))
        :ret ::menu)

(stest/instrument `update-menu-item-for-order-item)

seancorfield20:04:00

@U01UY47RD5E This works as expected for me. Did you perhaps redefine the function and then forget to re-run the instrument code?

seancorfield20:04:10

Here’s what I get:

{:path []
                                                                :pred (clojure.core/fn
                                                                       [%]
                                                                       (clojure.core/contains?
                                                                        (:menu
                                                                         %)
                                                                        (clojure.core/keyword
                                                                         (helloworld.restaurant/slugify
                                                                          (:helloworld.restaurant/name
                                                                           (:order-item
                                                                            %))))))
                                                                :val {:menu {:kothu-parotta {:helloworld.restaurant/name "Kothu Parotta"
                                                                                             :helloworld.restaurant/price 9.5
                                                                                             :helloworld.restaurant/quantity 50}
                                                                             :chicken-biriyani ...

seancorfield20:04:39

(when I request something that is not on the menu)

Prabu Rajan21:04:17

Thanks @U04V70XH6, for some reason the same code works fine for me now. I added the other validation too now in the fdef (to validate the order quantity should be lesser than the quantity in the inventory) and it works fine. I am experiencing the awesomeness of clojure and spec!

(s/fdef update-menu-item-for-order-item
        :args (s/and (s/cat :menu ::menu :order-item ::order-item)
                     #(contains? (:menu %) (keyword (slugify (::name (:order-item %)))))
                     #(< (::quantity (:order-item %))
                         (->> (:menu %)
                              ((keyword (slugify (::name (:order-item %)))))
                              (::quantity))))
        :ret ::menu)

Prabu Rajan21:04:43

With the function spec in place, I simplified my function’s logic to remove all nil and inventory checks and throwing exceptions and all that stuff like below

(defn update-menu-item-for-order-item [menu order-item]
  (let [item-id (keyword (slugify (::name order-item)))
        order-quantity (::quantity order-item)]
    (update-in menu
               [item-id]
               #(assoc % ::quantity (- (::quantity %) order-quantity)))))

seancorfield21:04:12

Just bear in mind that you would normally only use instrument in dev/test, not in production so you need to think about what should happen if your code violates that contract in production with no instrumentation in place.

Prabu Rajan22:04:45

So, should I still have the original checks in my code so that it gets validated in production? What is the recommended approach? Maybe is it ok that only external facing API functions have such checks in production and all internal functions use spec instrumentations at dev/test?

seancorfield22:04:30

If this is something that you would previously — before Clojure, before Spec — written as explicit checks in production with either error handling or exceptions, then that’s how you should write it now, in Clojure — with Spec if you want to leverage its ability to validate data.

seancorfield22:04:02

If you only want these checks in place while you develop & test your code, instrument is a reasonable way to approach it.

seancorfield22:04:33

I can’t remember if I’ve linked you to this before? https://corfield.org/blog/2019/09/13/using-spec/

seancorfield22:04:55

If you watch some of the talks about Spec, you’ll see there’s often a recommendation to use Spec around system boundaries and not for internal functions, on the grounds that it is not a type system.

seancorfield22:04:34

But while you are developing functions that manipulate complex data (or have complex relationships encoded in the data) using Spec can be very valuable to help you get the functions correct, both via instrumentation and via generative testing (such as stest/check).

Prabu Rajan06:04:27

Thanks! I read your blog, very useful. I shall explore more. It would be great if you can send some code examples for 1. Using s/conform and s/explain-data to return user friendly error messages 2. Some tutorial on generative test generation using test.check library

Prabu Rajan07:04:15

“If this is something that you would previously — before Clojure, before Spec — written as explicit checks in production with either error handling or exceptions, then that’s how you should write it now, in Clojure — with Spec if you want to leverage its ability to validate data.” > Lets say I want explicit validation of function arguments in some edge function in production. Is it recommended to still define specs and then use s/conform or s/valid? s/explain-data to write the validation logic instead of regular clojure code?

seancorfield15:04:42

If you have a non-trivial data structure and you use it in multiple functions, yes, I’d say that’s a good case for Spec.

seancorfield15:04:15

We use Spec to validate API input arguments, so that’s a case where each function might have a different set of :params from a Ring request.

seancorfield15:04:23

(we used to use coercions in our Specs but that is bad practice so we’ve shifted to using exoscale/coax to do the coercions — it deduces them from a Spec — and then use a plain old Spec to do the actual conforming/validating part)

seancorfield15:04:14

Re: 1. above — we don’t have anything we can share but this is the bulk of how it works:

(defn invalid-argument
  ([req spec fail-code-map]
   (forbidden req "Invalid Argument"
              (when fail-code-map
                (->> (:params req)
                     (s/explain-data spec)
                     (problems->fail-codes fail-code-map)))))
  ([req fail-code]
   (forbidden req "Invalid Argument" (if (vector? fail-code) fail-code [fail-code])))
  ([req]
   (forbidden req "Invalid Argument")))

(defmacro with-validated-params
  [[spec fail-code-map] & body]
  `(let [~'params (s/conform ~spec (:params ~'req))]
     (if (s/invalid? ~'params)
       (invalid-argument ~'req ~spec ~fail-code-map)
       (do ~@body))))
forbidden just constructs a 403 HTTP response, problems->fail-codes is a set of proprietary heuristics that maps Spec failures to entries in the fail-code-map.

seancorfield15:04:49

Re: 2 — there are plenty of resources out there about test.check.

Prabu Rajan17:04:45

Thanks @U04V70XH6 you would need to use coercions to coerce strings in API requests (from json) to clojure types, correct? Thanks for the code on usage of s/explain-data. Will try it out

seancorfield17:04:04

Depends on whether you’re dealing with JSON or plain URL/form parameters but, yes, mostly you need something to coerce strings to numbers, booleans, dates, etc — and then conform with Spec.

Prabu Rajan17:04:43

sure, makes sense

Darrell19:04:57

So, next question: I have two files-- File A has 24435 unique sorted lines and File B has unique sorted 22391 lines. I’m reading the lines of each file into a sequence using the method I asked about previously so that I can get a difference between the two with (count (set/difference A_set B_set)). However, when I do that, I get “22391” which I’m certain isn’t correct. Is there a better way to get the difference between the two files?

Darin Douglass19:04:57

since they're sorted it'd probably be worth to take, say, the first 10 lines of each file to get a better understanding of your problem. this problem feels like a "fix one small thing and the rest will settle out"

Darrell20:04:04

@ddouglass I guess I’m going to have to. Something is really throwing this off. I just tried @alexmiller’s suggestion and that gives me a count of “3”, which isn’t right either. I’m expecting a few hundred.

Darrell22:04:33

Turns out it was a Byte Order Mark hiding out at the beginning of one of the files.