Fork me on GitHub
#beginners
<
2020-10-07
>
Darin Douglass00:10:02

You can always create a single test resource with input and expected output per task, then run a test for each task. Something like (typos are expected, I did this on my phone):

deftest test-tasks
  (doseq [[task {:keys [input expected]}] my-test-resource]
    (testing task
      (is (= expected (run-task task input))))))

seancorfield00:10:31

@artem00298 ^ that would be my recommendation too: put all the data together and have a single test that runs each set of data through the (is (= ...)) test.

Matias Francisco Hernandez Arellano04:10:09

Not sure why but this piece of code is failing by not printing anything to the console

(defn parse-item [data]
  (let [
        title (get-in data [:attributes :title])
        ]
    (println (format "Titulo: %s" title))
    )
  
  )

(defn success [response]
  "Performs the items parsing if the request was successfully"
  (let [items (get-items response)]
    (map parse-item items))
  )
where items used in (map parse-item items) is an structure like this (but bigger)
`
[{:id 'Some id', :attributes {:title 'Some title', :tags [] } } {:id 'Some id 2', :attributes {:title 'Some title', :tags [] } } ]```

seancorfield04:10:31

Because map is lazy and you're not realizing the sequence I suspect @matiasfh

seancorfield04:10:16

Rule of thumb: do not use map for anything with side-effects (like println).

seancorfield04:10:39

(run! parse-item items) would be eager and run parse-items for each item, so it would print lines. But that won't help you when you change parse-item to actually produce transformed data.

Matias Francisco Hernandez Arellano04:10:41

make sense.. I'm printing there to a way of "debugging" not try to see what is happening.. what could be a clojure way of doing this (very bad) debugging?

seancorfield04:10:13

Separate the transform from the print.

Matias Francisco Hernandez Arellano04:10:17

the idea is to just return the (format) form

seancorfield04:10:51

if success really is just map over items, then (run! println (success ...)) would be the way to print results.

dpsutton04:10:57

(def items my-items) (parse-item (first items)) should be enough

dpsutton04:10:16

just call your function on one of the items. when it works, it should work on all of the items

seancorfield04:10:07

Yeah, if you're working tightly in the REPL, you would test parse-item by calling it on a single item.

seancorfield04:10:23

(defn success [response]
  (map #(format "Titulo: %s" %) (get-items response)))
would probably be simpler/more idiomatic.

seancorfield04:10:43

If you want a sequence of strings?

Matias Francisco Hernandez Arellano04:10:02

make sense.. now since you are online xD.. I just tried another thing (connected with this) . This code is for a discord bot, so the bot connect to discord and receive a command. in this case the command triggers a http request using clj-http and cheshire. Then parse the response with that function that we were talking. but this error is fired up

SEVERE: Exception in dispatch-http
java.lang.Exception: Don't know how to write JSON of class org.apache.http.impl.nio.client.FutureWrapper
        at clojure.data.json$write_generic.invokeStatic(json.clj:389)
        at clojure.data.json$write_generic.invoke(json.clj:386)
        at clojure.data.json$eval9381$fn__9382$G__9372__9389.invoke(json.clj:290)
        at clojure.data.json$write_object.invokeStatic(json.clj:339)
        at clojure.data.json$write_object.invoke(json.clj:323)
        at clojure.data.json$eval9381$fn__9382$G__9372__9389.invoke(json.clj:290)
        at clojure.data.json$write.invokeStatic(json.clj:479)
        at clojure.data.json$write.doInvoke(json.clj:428)
        at clojure.lang.RestFn.invoke(RestFn.java:425)
        at clojure.lang.AFn.applyToHelper(AFn.java:156)
        at clojure.lang.RestFn.applyTo(RestFn.java:132)
        at clojure.core$apply.invokeStatic(core.clj:669)
        at clojure.core$apply.invoke(core.clj:660)
        at clojure.data.json$write_str.invokeStatic(json.clj:486)
        at clojure.data.json$write_str.doInvoke(json.clj:481)
        at clojure.lang.RestFn.invoke(RestFn.java:410)
        at discljord.messaging.impl$eval11931$fn__11933.invoke(impl.clj:132)
        at clojure.lang.MultiFn.invoke(MultiFn.java:239)
        at discljord.messaging.impl$make_request_BANG_$make_request__13636.invoke(impl.clj:788)
        at discljord.messaging.impl$make_request_BANG_.invokeStatic(impl.clj:793)
        at discljord.messaging.impl$make_request_BANG_.invoke(impl.clj:767)
        at discljord.messaging.impl$step_agent$fn__13660.invoke(impl.clj:838)
        at clojure.core$binding_conveyor_fn$fn__5754.invoke(core.clj:2033)
        at clojure.lang.AFn.applyToHelper(AFn.java:154)
        at clojure.lang.RestFn.applyTo(RestFn.java:132)
        at clojure.lang.Agent$Action.doRun(Agent.java:114)
        at clojure.lang.Agent$Action.run(Agent.java:163)
        at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130)
        at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630)
        at java.base/java.lang.Thread.run(Thread.java:832)
based on my basic knowledge of Java this is clearly a problem with the async function

Matias Francisco Hernandez Arellano04:10:33

(defn get-on-brd-message [query]
  (let [url (get-url query)]
    (client/get url {:accept :json :async? true }
      (fn [response] (success response))
                  ;; raise callback
      (fn [exception] (println (.getMessage exception)))
    )))

dpsutton04:10:44

this is an error in writing json, not reading json

dpsutton04:10:02

but you're trying to serialize some asynchronous value which can't work

Matias Francisco Hernandez Arellano04:10:53

Just by asking I figured that should try with a sync version of the request

duckie 3
Ricardo Cabral06:10:06

Hello all, I am now in the Clojure world, and I am really enjoying learning it. I have a very dumb question but I am not able to find it by my self. Why in the sample code I cannot have two (assoc-in…) ? I am trying to print both values but nothing happens. Thank you very much in advance.

;; interceptor for authorization
(def auth
  {:name :auth
   :enter
         (fn [context]
           (let [token (-> context :request :headers (get "token"))]
             (if-let [uid (and (not (nil? token)) (get-uid token))]
               (assoc-in context [:request :tx-data :user] uid)
               (assoc-in context [:request :my-key :my-value] "Hello Again")
               (chain/terminate
                 (assoc context
                   :response {:status 401 :body "Auth token not found"})))))
   :error
         (fn [context ex-info]
           (assoc context
             :response {:status 500 :body (.getMessage ex-info)}))
   })

seancorfield06:10:32

@ricardozcabral Welcome! Clojure is an expression-based language with immutable data. In the code above you seem to have three expressions inside your if which I don't think should even compile. Each expression needs to have a single return value.

Nemo06:10:40

(def auth
  {:name :auth
   :enter
         (fn [context]
           (let [token (-> context :request :headers (get "token"))]
             (if-let [uid (and (not (nil? token)) (get-uid token))]
               (->
                 (assoc-in context [:request :tx-data :user] uid)
                 (assoc-in [:request :my-key :my-value] "Hello Again"))
               (chain/terminate
                 (assoc context
                   :response {:status 401 :body "Auth token not found"})))))
   :error
         (fn [context ex-info]
           (assoc context
             :response {:status 500 :body (.getMessage ex-info)}))
   })
This might help..

🙌 6
3
seancorfield06:10:54

Can you explain what you are trying to do in that if?

seancorfield06:10:21

@prayaganeethu Could you indent that code to show the grouping better?

👍 3
Ricardo Cabral06:10:40

I was just trying to understand how assoc-in works, if I could include more than one value on it. I have also tried to put that before the if and it did not work. But I believe you are correct @seancorfield I missed the if part. Thank you very much. and thank @prayaganeethu to show me how to fix it

👍 3
seancorfield06:10:43

Perhaps:

(def auth
  {:name :auth
   :enter
         (fn [context]
           (let [token (-> context :request :headers (get "token"))]
             (if-let [uid (and (not (nil? token)) (get-uid token))]
               (-> context
                 (assoc-in [:request :tx-data :user] uid)
                 (assoc-in [:request :my-key :my-value] "Hello Again"))
               (chain/terminate
                 (assoc context
                   :response {:status 401 :body "Auth token not found"})))))
   :error
         (fn [context ex-info]
           (assoc context
             :response {:status 500 :body (.getMessage ex-info)}))
   })

seancorfield06:10:03

(thanks for the edit to show the grouping)

👍 3
seancorfield06:10:54

It definitely takes a bit of getting used to if your background is in statement-based languages that rely on mutation 😐

Ricardo Cabral06:10:22

My background is 99% Java, so I am struggling a bit with it 😄

seancorfield06:10:44

Hey, at least you're used to the JVM and stacktraces! 🙂

🙂 3
Ricardo Cabral06:10:52

This is one of the reasons I am trying to learn Clojure, it helps a lot to be used to the JVM. Just so you know the sample above still doesn’t work I am getting clojure.lang.ArityException in Interceptor :auth - Wrong number of args (4) passed to: clojure.core/assoc-in%

Nemo07:10:59

(->
  context
  (assoc-in [:request :tx-data :user] uid)
  (assoc-in [:request :my-key :my-value] "Hello Again"))
Please do recheck the arguments in both the assoc-in - might have left context in there still as an arg, hence

❤️ 3
Ricardo Cabral07:10:37

You are a life saver. Not it woks Thank you very much again 🙂

😊 3
Jim Newton12:10:24

I have a certain reduction function which I need to write. I don't know what to call it or whether something similar exists. Does this sound like any kind of standard algorithm or standard iteration function? The function takes an object representing an expression, such as a mathematical expression, or boolean expression etc. And the function takes a set of reduction operations. Each reduction operation is a function which is given the opportunity to simplify the expression, thus each option takes the expression as argument and either returns the same expression or a simplified form. The algorithm keeps calling reduction functions in-turn until one successfully reduces it, i.e. until one fails to return the same object it received.

Jim Newton12:10:47

it's sort of a cousin of fixed-point

jsn12:10:10

so basically (->> your-vector-of-operations (cons identity) (map #(% your-expression)) distinct second) ?

Jim Newton12:10:43

sorry, I don't understand that logic. Does that expression halt the computation as soon as one operation returns something different than it is given?

Jim Newton12:10:45

Here's what I have, untested:

(defn find-simplifier [obj simplifiers] ;; BAD name, need better name
  (if (empty? simplifiers)
    obj
    (loop [[f & fs] simplifiers]
      (let [new-obj (f obj)]
        (cond
          (not= new-obj obj)
          new-obj

          (empty? fs)
          obj

          :else
          (recur fs))))))

Alex Miller (Clojure team)12:10:36

I think you need a termination check on f first (and then you can remove the fs check), but seems like the right path to me

Jim Newton12:10:20

@U064X3EF3 don't I have to assure that the sequence is non-empty before restructuring it into [f & fs] ?

Alex Miller (Clojure team)13:10:32

you'll just nil for both if it's nil

Jim Newton13:10:16

but you won't be able to distinguish that from a real sequence [nil] , ok in this case that can't happen as I have a sequence of unary functions, which I suppose nil is not.

Jim Newton13:10:10

ouch, that's another difference apparently between function application and restructuring ((fn [a & as] 12)) throws an error but (let [[a & as] nil] ...) binds a to nil.

😡 3
Jim Newton13:10:05

Because this is true about functions, I supposed it would be true about functions. 😞

kennytilton14:10:33

Since clojure is lazy, you can map over all the simplifiers then pick out the first result not= using some (so you stop at the first):

(let [x '(+ 2 2)
      simplifiers [identity reverse identity]]
  (some #(when (not= % x) %)
    (map (fn [s]
           (s x)) simplifiers)))
…or roll it all up into one:
(let [x '(+ 2 2)
      simplifiers [identity reverse identity]]
  (some #(let [y (% x)]
           (when (not= y x) y)) simplifiers))
Me, I would have simplifiers return nil if they choose not to transform, but it’s your app! 🙂

jsn12:10:13

my map is subject to chunking, though; but perhaps it's fixable

Jim Newton12:10:31

what is chunking?

Jim Newton12:10:58

oh you mean it might calculate too many elements only to throw away unused ones?

Jim Newton12:10:42

Yes, that's something I want to avoid. as any of the function might have high complexity. I'd like to avoid calling a tree reduction if a previous simplification gave me a good result.

jsn12:10:39

(some #(let [expr' (% expr)] (and (not= expr' expr) expr')) your-funcs) should do, if neither expr nor expr' can legally be falsey

vlaaad12:10:57

argh, I was about to post it 😄

vlaaad12:10:05

(defn simplify [x fs] 
  (or (some #(let [x' (% x)] (when-not (= x' x) x')) fs) 
      x))

Jim Newton12:10:20

@U47G49KHQ, does that work if one of the functions reduces the expression to nil or false ?

Jim Newton12:10:14

it is very likely, and desired sometimes that an expression simply to nil

vlaaad12:10:30

it does not 🙂

vlaaad13:10:42

you can augment the code to return (reduced x') instead of x'

vlaaad13:10:44

and then check if result of (some ...) is reduced and then deref it if it is reduced and return x if it is nil

jsn13:10:24

at this point just using reduce instead would be simpler (i think)

vlaaad13:10:58

more complex, actually, because you will be dealing with 2 moving parts instead of 1 (item and accumulator instead of item)

jsn13:10:58

well, the first part is not actually moving all that much 🙂

vlaaad13:10:03

but probably easier since it unpacks reduced for you

jsn13:10:27

yeah, that was the idea

Matias Francisco Hernandez Arellano12:10:55

Is there any video/tutorial about how to use doom emacs with clojure, kind of a workflow how to?. I'm completely ignorant about how to work with the code and the repl in a "dynamic" way

practicalli-johnny13:10:40

I dont have exactly what you ask, but this book may help https://practicalli.github.io/spacemacs/clojure-projects/ There may be differences in keybindings (I found doom a bit lacking in that area) The workflow I take is essentially this: • create a project on the command line (or eshell) • Open the project in emacs • cider-jack-in-clj to start a repl for the project • edit and evaluate code in the src buffers (I rarely use the REPL buffer directly) • cider-undef and cider-ns-refresh or just stop start the repl if I have stale vars (usually renaming functions or deftest functions without undef'ing the old name • Put experimental code in rich code blocks http://practicalli.github.io/clojure/repl-driven-devlopment.html#rich-comment-blocks---living-documentation • write test to start solidifying the design and run all tests with the cider runner https://practicalli.github.io/spacemacs/testing/unit-testing/running-tests.html • run a command line test runner, eg. kaocha, before committing to run the tests from scratch

👀 3
practicalli-johnny13:10:26

Also take a look at http://www.parens-of-the-dead.com/ Its not a tutorial, but you can see repl driven development in action

practicalli-johnny13:10:05

There is a general #emacs channel here. Doom has a discord community, you may find some help there also.

💪 3
rdgd12:10:17

This is not related to refactoring your code, however since I'm assuming you want to test for an empty rest of your simplifiers collection where you call (empty fs), what you are looking for in that case, just an FYI, is the predicate empty? which would read (empty? fs) instead

3
Jim Newton12:10:19

Thanks, yes you're right. As I said I haven't tested it yet.

Jim Newton12:10:17

I fixed the code. BTW.

rdgd12:10:30

actually, a bit of clarification there (seq fs) would be more idiomatic

Jim Newton12:10:41

Yes, I think that discussion has been had many times. I organised my code to avoid calling (not (empty? ...)) to avoid the dispute yet again.

Matias Francisco Hernandez Arellano13:10:55

I did a thing: https://github.com/matiasfha/JenkinsBot A discord bot written in Clojure (first app) If you can take a look it will be very helpful

tugh13:10:56

I’m curious about why there is no def- ? Is using metadata dictionary the only way to declare private vars? :thinking_face:

practicalli-johnny14:10:53

Consider why you wish to make things private. As Clojure uses immutable data, there is less reason to hide data. So the private concept is not as relevant in Clojure. It is mostly used as documentation rather than a constraint on accessing certain parts of code (which can still be accessed when private) Metadata forms part of the function documentation, so is more useful that creating a macro (e.g. defn- macro) to wrap each of the many ways to create a var. Hope that helps.

practicalli-johnny14:10:06

I tend to just define helper functions that contain code that would otherwise be shared by several functions. This is the main reason I would consider using private. If there are several helper functions, these can then be moved into a separate namespace. I test shared functions through the functions that use them, minimising the number of tests whilst still getting effective test coverage.

vlaaad13:10:46

in short, defn- was a mistake, metadata on a name is preferred

tugh13:10:24

but why defn- was a mistake? it is pretty useful IMHO

andy.fingerhut13:10:09

If defn- leads to requests for def- , defmacro- , ..., then it is a mistake. Adding metadata :private to any/all of them works, including on defn, and leads to fewer overall names needing defining

👍 3
Day Bobby13:10:29

I have a threading macro: (-> a b c d) I want to make c conditional, so only if something-happened is truthy the pipeline will go through c. What is the best way to achieve this?

Day Bobby13:10:25

(-> a b (cond-> something-happened c) d) oh this works

👍 6
Noah Bogart14:10:42

if i have a string ":name", what's the best way to turn it into the keyword :name?

Alex Miller (Clojure team)14:10:25

clojure.edn/read-string

👍 9
Alex Miller (Clojure team)14:10:54

or clojure.core/read-string (but the former is better from a security perspective)

Noah Bogart14:10:05

ah of course, thank you

pinkfrog14:10:11

Say I have a script xx.clj which contains a main function. How can I run that main function with clj ?

pinkfrog14:10:29

clj -M xx.clj runs the whole script. I need run the main function.

Alex Miller (Clojure team)14:10:10

(but the script will need to be on the classpath)

Alex Miller (Clojure team)14:10:11

although that will also load the clj file (which is the same as "running the script")

Alex Miller (Clojure team)14:10:24

are you trying to avoid doing something else in the file?

pinkfrog15:10:07

yes. only run the main function.

pinkfrog15:10:01

I am thinking is there something similar to python’s: if name_ == ‘ main _’ : main()

dpsutton15:10:29

do you have top level forms that "do" work? ie (println "HI") and you are trying not to run those?

pinkfrog15:10:23

Take the case of clj -M xx.clj. I don’t want to write a (main) in the top level, which will run when the namespace is first loaded.

dpsutton15:10:52

i'm not sure i follow. presumably a (defn -main [...] ...) function won't be run when loaded, but the definition will be created

pinkfrog15:10:45

Yes. That’s is why I have to explicitly write a (main) in the toplevel. Mind the parens.

Alex Miller (Clojure team)15:10:08

main is not run unless you invoke it (or use clj -M -m foo)

pinkfrog15:10:42

can I add the foo.clj to the classpath with the command line ?

Alex Miller (Clojure team)15:10:34

you can clj -Sdeps '{:paths ["path/to/foos/dir"]}' ...

Alex Miller (Clojure team)15:10:53

that's the directory containing foo.clj, not the path to foo.clj

vncz15:10:12

Oh about this, why does cli require -M when running main? Can't it "figure it out" automatically when doing -m?

vncz15:10:21

(It's just a curiosity, not really a request or something else)

Alex Miller (Clojure team)15:10:28

-M (now) means "run clojure.main" and we are moving towards a point where that will be the only way to run clojure.main (and -m may at some future point have some other meaning)

vncz15:10:13

So can I just do cli -M foo ?

Alex Miller (Clojure team)15:10:03

that will run foo as a script

vncz15:10:29

Got it, so -M is to tell the CLI to run the clojure.main, while -m is to tell it to run the main function in the namespace I'm indicating

vncz15:10:54

Ok so question — what happens when I just do cli -m namespace ? Why does it work anyway? Does it do something differently?

Alex Miller (Clojure team)15:10:55

it does the same thing (for now), but will warn you to use -M - we just deprecated that behavior so this is a transitionary period

Alex Miller (Clojure team)15:10:05

eventually clj -m namespace will not work

Alex Miller (Clojure team)15:10:30

but it may be months before we make that next step

vncz15:10:34

Ok fantastic, that explains everything. Thanks @U064X3EF3

Noah Bogart16:10:24

i'm experimenting with using edn instead of json for sending data in a map from my server to the client. some of the values in my map are functions, and the edn reader in the client is throwing this error {:message "Invalid symbol: game.cards.basic/fn--16461/fn--16462.", :data {:type :reader-exception, :ex-kind :reader-error}}. This was handled when I used json by just turning that into a string: "game.cards.basic/fn--16461/fn--16462" which is harmless

Noah Bogart16:10:36

i'm using the sente library, if that helps at all

Noah Bogart16:10:58

i tried putting a {:default (fn [tag value] (str value))} option in the edn/read-string call, but that's not helped

Alex Miller (Clojure team)16:10:01

there is no edn syntax for function instances

Alex Miller (Clojure team)16:10:36

so you either need to pass a form and eval on the far side (danger will robinson) or use a symbol that can be resolved to code that's available on the far side

Alex Miller (Clojure team)16:10:39

or there are some libs that handle function serialization

Noah Bogart16:10:10

no way to just ignore it? i don't need the function on the other side, but writing a parser to selectively remove the function values feels like a lot of work

Noah Bogart16:10:30

either way, thanks so much

Alex Miller (Clojure team)16:10:54

well it's just data, you can dissoc whatever you want out of the map :)

Alex Miller (Clojure team)16:10:32

I mean you can clean the whole tree with a clojure.walk/postwalk using a function like #(if (ifn? %) nil %)

Noah Bogart17:10:21

holy crap, okay, that's pretty easy. i'll give that a whirl, thank you

dpsutton17:10:05

beware: (ifn? :bob) is true

👍 12
Alex Miller (Clojure team)17:10:22

right, fn? might actually be better here but use whatever discriminator is appropriate

Tāhā21:10:48

Clojure noob here 👋 What should the contents of my project.clj look like if I want a project structure that looks like this

my-project-dir/
   project.clj
   main.clj     (I only want to work with this one Clojure file)
   ...          (Other non-Clojure files)   

Tāhā21:10:34

(I'm using Leiningen)

dpsutton21:10:28

by default lein uses a classpath root of src. so if you require a namespace called main, it will look for src/main.clj. that being said, its usually preferable to have some kind of disambiguating namespace. often this is your org, github username, or perhaps even some domain notion if its truly one-off code. perhaps a namespace like task.main and have it at src/task/main.clj

Tāhā21:10:49

So how do I change the default root?

Tāhā21:10:05

I couldn't seem to find a project.clj reference online

phronmophobic21:10:32

for this setup, I probably wouldn't recommend leiningen which is focused on projects. you might find https://clojure.org/guides/deps_and_cli easier

phronmophobic21:10:16

otherwise, I would really recommend adopting the standard project layout

phronmophobic21:10:51

if you're just writing scripts, try https://github.com/borkdude/babashka

👍 3
Tāhā21:10:01

Shouldn't (:import java.time.format DateTimeFormatter) successfully pull DateTimeFormatter into the current namespace? I've seen people do (:import [java.util ArrayList HashMap]) so what am I doing wrong here? I get a ClassNotFoundException

Tāhā21:10:52

This is inside the ns macro

borkdude21:10:50

@tahaahmedrasheedpk

(ns foo (:import [java.time.format DateTimeFormatter]))

Tāhā21:10:45

Oh :woman-facepalming:

Tāhā21:10:21

Syntax error (ClassNotFoundException) compiling at (main.clj:12:50).
DateTimeFormatter.RFC_1123_DATE_TIME
This is my main.clj

borkdude21:10:50

@tahaahmedrasheedpk Try:

(.format DateTimeFormatter/RFC_1123_DATE_TIME limit)

Tāhā23:10:40

From the http://Clojure.org spec guide:

(ns my.domain (:require [clojure.spec.alpha :as s]))
(def email-regex #"^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,63}$")
(s/def ::email-type (s/and string? #(re-matches email-regex %)))

(s/def ::acctid int?)
(s/def ::first-name string?)
(s/def ::last-name string?)
(s/def ::email ::email-type)

(s/def ::person (s/keys :req [::first-name ::last-name ::email]
                        :opt [::phone]))
Do I have to define map keys as specs to use them in s/keys ? Isn't there something like (s/keys :req [:first-name string? :last-name string? :email ::email-type]) ?

Alex Miller (Clojure team)23:10:58

you have to define them as specs

Tāhā23:10:39

Why is that :thinking_face:

Alex Miller (Clojure team)23:10:17

one of the main philosophical ideas in spec is that attributes, not maps (concretions of attributes) should be the primary unit of specification

Alex Miller (Clojure team)23:10:33

https://clojure.org/about/spec is probably the best written page about this and other ideas, but it's discussed in several of Rich's talks as well