Fork me on GitHub
#beginners
<
2020-10-07
>
ddouglass00: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.

matiasfh04: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.

matiasfh04: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.

matiasfh04: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?

matiasfh04: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

matiasfh04: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

matiasfh04:10:53

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

matiasfh04:10:19

Thanks folks!

ricardozcabral06: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.

prayaganeethu06: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..

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?

ricardozcabral06: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

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)

seancorfield06:10:54

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

ricardozcabral06: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! πŸ™‚

ricardozcabral06: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%

prayaganeethu07: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

ricardozcabral07:10:37

You are a life saver. Not it woks Thank you very much again πŸ™‚

jimka.issy12: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.

jimka.issy12:10:47

it's sort of a cousin of fixed-point

jason35812:10:10

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

jimka.issy12: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?

jimka.issy12: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))))))

alexmiller12: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

jimka.issy12:10:20

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

alexmiller13:10:32

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

jimka.issy13: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.

jimka.issy13: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.

jimka.issy13:10:05

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

hiskennyness14: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! πŸ™‚

jason35812:10:13

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

jimka.issy12:10:31

what is chunking?

jimka.issy12:10:58

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

jimka.issy12: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.

jason35812: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))

jimka.issy12:10:20

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

jimka.issy12: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

jason35813: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)

jason35813: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

jason35813:10:27

yeah, that was the idea

matiasfh12: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

jr0cket13: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

jr0cket13: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

jr0cket13:10:05

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

ryan.is.gray12: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

jimka.issy12:10:19

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

jimka.issy12:10:17

I fixed the code. BTW.

ryan.is.gray12:10:30

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

jimka.issy12: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.

matiasfh13: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? πŸ€”

jr0cket14: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.

jr0cket14: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

bday209913: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?

bday209913:10:25

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

nbtheduke14:10:42

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

alexmiller14:10:25

clojure.edn/read-string

alexmiller14:10:54

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

nbtheduke14:10:05

ah of course, thank you

i14:10:11

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

i14:10:29

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

alexmiller14:10:46

clj -M -m xx

alexmiller14:10:10

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

alexmiller14:10:11

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

alexmiller14:10:24

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

i15:10:07

yes. only run the main function.

i15: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?

i15: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

i15:10:45

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

alexmiller15:10:08

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

i15:10:42

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

alexmiller15:10:34

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

alexmiller15:10:53

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

vincenz.chianese15:10:12

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

vincenz.chianese15:10:21

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

alexmiller15: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)

vincenz.chianese15:10:13

So can I just do cli -M foo ?

alexmiller15:10:03

that will run foo as a script

vincenz.chianese15: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

vincenz.chianese15:10:54

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

alexmiller15: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

alexmiller15:10:05

eventually clj -m namespace will not work

alexmiller15:10:30

but it may be months before we make that next step

vincenz.chianese15:10:34

Ok fantastic, that explains everything. Thanks @

nbtheduke16: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

nbtheduke16:10:36

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

nbtheduke16:10:58

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

alexmiller16:10:01

there is no edn syntax for function instances

alexmiller16: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

alexmiller16:10:39

or there are some libs that handle function serialization

nbtheduke16: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

nbtheduke16:10:30

either way, thanks so much

alexmiller16:10:54

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

alexmiller16:10:32

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

nbtheduke17: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

alexmiller17:10:22

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

tahaahmedrasheedpk21: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)   

tahaahmedrasheedpk21: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

tahaahmedrasheedpk21:10:49

So how do I change the default root?

tahaahmedrasheedpk21:10:05

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

smith.adriane21: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

smith.adriane21:10:16

otherwise, I would really recommend adopting the standard project layout

tahaahmedrasheedpk21: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

tahaahmedrasheedpk21:10:52

This is inside the ns macro

borkdude21:10:50

@tahaahmedrasheedpk

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

tahaahmedrasheedpk21:10:45

Oh :woman-facepalming:

tahaahmedrasheedpk21: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)

tahaahmedrasheedpk23: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._%+-][email protected][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]) ?

alexmiller23:10:58

you have to define them as specs

tahaahmedrasheedpk23:10:39

Why is that πŸ€”

alexmiller23: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

alexmiller23: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