Fork me on GitHub
#beginners
<
2021-03-09
>
fabrao03:03:21

Hello all, how do I dynamic generate param for this ?

(fnc "A" [["A" "B"][1 2]] "B" [["A" "B"][1 2]] ...)
I can generate that [[...]] but how to how to sequence "A" [[...]] "B" [[...]] ?

seancorfield03:03:42

@fabrao Can you be a bit more specific over exactly what the relationship is between various parts of those arguments?

seancorfield03:03:57

Are you asking about Spec/generators, or something else?

fabrao03:03:06

Hello Sean, I have to make params for dynamic create workbook for excel lib mjul/docjure that use

(let [wb (create-workbook "Squares"
                          [["N" "N^2"]
                           [1 1]
                           [2 4]
                           [3 9]]
                          "Cubes"
                          [["N" "N^3"]
                           [1 1]
                           [2 8]
                           [3 27]])]
   (save-workbook! "exponents.xlsx" wb))
the create-workbook is a sequence of string array/array . But I have to make many string and arrays. How to I make it dynamic generating it?

seancorfield03:03:19

You have a formula for calculating values here?

fabrao03:03:47

no, just generate sheet and values for it

seancorfield03:03:58

Then I have no idea what you're asking.

seancorfield03:03:15

If you have a formula for calculating the data then you can just generate it.

seancorfield03:03:33

If you don't have a formula or a Spec, I'm not sure how you expect to automate this?

fabrao03:03:08

Well , how can I generate `(fn "sheet1" [[...]] "sheet2" [[...]] ...)

seancorfield03:03:09

The data you've shown can be generated -- there's a clear formula involved

fabrao03:03:36

(defn create-workbook
  "Create a new XLSX workbook.  Sheet-name is a string name for the sheet. Data
  is a vector of vectors, representing the rows and the cells of the rows.
  Alternate sheet names and data to create multiple sheets.
  (create-workbook \"SheetName1\" [[\"A1\" \"A2\"][\"B1\" \"B2\"]]
                   \"SheetName2\" [[\"A1\" \"A2\"][\"B1\" \"B2\"]] "
 ([sheet-name data]
   (let [workbook (XSSFWorkbook.)
         sheet    (add-sheet! workbook sheet-name)]
     (add-rows! sheet data)
     workbook))

 ([sheet-name data & name-data-pairs]
  ;; incomplete pairs should not be allowed
  {:pre [(even? (count name-data-pairs))]}
  ;; call single arity version to create workbook
   (let [workbook (create-workbook sheet-name data)]
     ;; iterate through pairs adding sheets and rows
    (doseq [[s-name data] (partition 2 name-data-pairs)]
      (-> workbook
          (add-sheet! s-name)
          (add-rows!  data)))
    workbook)))
It seems that only 2 sequences can be passed

fabrao03:03:28

or name-data-pairs can be many as I want?

fabrao03:03:41

I solved using this ->

(-> workbook
          (add-sheet! s-name)
          (add-rows!  data))

fabrao03:03:49

thank you anyway

seancorfield03:03:19

I would never have suspected that was the answer to the question you asked 🙂

fabrao04:03:57

🙂 the question was about generating sequences of string and arrays dynamic

seancorfield04:03:32

Yeah, and you are not generating sequences here. You already had the data and you were asking about how to call a variadic function with particular pieces of that data.

yubrshen06:03:31

I started a repl by clj -A:dev on the same machine (localhost) outside emacs when I use M-x cider-connect-clj it asks the port number. How can I figure out the port number? Or if there is a way to configure so that it will be started at a known port number? Try googled, but couldn't figure out. It seems that running a "socket server" is not for my situation, as M-x cider-connect-clj failed. I have cljs with shadow-cljs configured, the repl can be connected by M-x cider-connect-cljs with the configured port number. I can even using M-x cider-connect-clj to the same port for cljs repl, but apparently, I got another cljs repl, not the clj repl that I want. I even tried adding :nrepl {:port 5555} in deps.edn following the example of shadow-cljs.edn (with a different port number), but the connection still failed. Thanks for your help!

seancorfield06:03:33

@yubrshen What is that :dev alias? Does it start an nREPL server?

seancorfield06:03:27

I don't know of any tooling that would read :nrepl from deps.edn.

seancorfield06:03:44

(what you said there doesn't sound like an alias)

yubrshen06:03:18

@seancorfield here is the :dev alias:

:dev {:extra-paths ["dev"]
                 :extra-deps  {org.clojure/clojurescript   {:mvn/version "1.10.520"}
                               org.clojure/tools.namespace {:mvn/version "0.3.1"}
                               thheller/shadow-cljs        {:mvn/version "2.8.52"}
                               binaryage/devtools          {:mvn/version "0.9.10"}}}

seancorfield06:03:57

Since that has no :main-opts, that isn't going to do anything but start a regular Clojure REPL.

yubrshen06:03:08

Yes, the nREPL as a repl shell started, but I'm not sure it's a server.

seancorfield06:03:47

I don't see anything in your alias that would start an nREPL server -- which is what cider-connect is going to need.

yubrshen06:03:14

I just want to achieve the objective of having repl functionality by connecting to a repl started in a shell outside of emacs.

seancorfield06:03:16

If you were actually starting nREPL, it would print out the port number it was using.

seancorfield06:03:55

Are you working from a specific tutorial?

yubrshen06:03:17

No, I'm not working from a tutorial.

yubrshen06:03:38

You're right. I used to be able to observed the port number, when I did M-x cider-jack-in-clj, or even clj -A:dev but not anymore.

seancorfield06:03:35

It looks like you're trying to use ClojureScript, not Clojure, and you're trying to use Shadow-cljs? The README for the latter seems to focus on starting REPLs using npx.

seancorfield06:03:12

I don't know how you would use Shadow-cljs with the Clojure CLI. I have only used Figwheel Main with the Clojure CLI.

yubrshen06:03:41

I'm doing both clj and cljs following examples of Fulcro-3 tutorial. Indeed, I'm actually running the code of that.

seancorfield06:03:06

Then maybe ask in #fulcro about using the Clojure CLI with it?

yubrshen06:03:22

OK. Thanks!

jb recluse15:03:47

i didnt see a question in #fulcro - did you get this resolved or do you still need assistance?

yubrshen19:03:39

No, it's not yet resolved. I need to find time to investigate. Thanks! I doubt it related to Fulcro. I'll try to minimize the scope of my problem first.

pez07:03:27

I think CIDER has a command for jacking in to both clj and cljs with the same command.

yubrshen19:03:15

I haven't tried it yet. Thanks!

Jan Ignatius08:03:58

Good morning. I was stuck last night with figuring out how to pass attributes from the command line with lein run, the main issue being needing to convert the attribute to integer. I've made some progress but now I'm stuck at a different point: The code below runs in REPL, using the (now commented out) (-main "100") call, and I can see with the debug messages that the attribute is correctly extracted and converted to a integer. But when I try lein run 101 all the debugging messages come ok until the for statement - somehow lein just quietly stops there and the fizz-buzz-full function is never called.

(ns test-world2.core
  (:gen-class))

(defn fizz-buzz-full [n]
  (println "fizz-buzz-full: arg received is " n)
  (cond
    (and (zero? (mod n 3)) (zero? (mod n 5))) (println "fizzbuzz")
    (zero? (mod n 3)) (println "fizz")
    (zero? (mod n 5)) (println "buzz")
    :else (println n)))

(defn arg-conversion [arg]
  ;; debugging
  (println "arg-conversion: received arg value is:" arg)
  
  (def i-arg (Integer/parseInt arg))
  
  ;; debugging
  (println "arg-conversion: arg converted to integer in i-arg:" i-arg)
  (println "arg-conversion: is i-arg is an integer? " (integer? i-arg))
  (println "arg-conversion: is i-arg is an string? " (string? i-arg))
  
  (for [x (range 1 i-arg)]
    (fizz-buzz-full x)
    ; debug
    ;(println "arg-conversion: (inside FOR) value of x is" x "and i-arg is " i-arg)
    )
  )


(defn -main [& args]
  (if (seq args)
  ; Foreach arg...
    (def first-arg (first args))
    (throw (Exception. "Must have one argument!")))
  
  ;; debugging
  (println "-main: arguments received from the command line: " args)
  (println "-main: the first-arg is: " first-arg)
  (println "-main: is the first-arg an integer? " (integer? first-arg))
  (println "-main: is the first-arg a string? " (string? first-arg))

  (arg-conversion first-arg))

;(-main "100")

Jan Ignatius08:03:25

The output is as follows:

:~/test-world2$ lein run 101 102
-main: arguments received from the command line:  (101 102)
-main: the first-arg is:  101
-main: is the first-arg an integer?  false
-main: is the first-arg a string?  true
arg-conversion: received arg value is: 101
arg-conversion: arg converted to integer in i-arg: 101
arg-conversion: is i-arg is an integer?  true
arg-conversion: is i-arg is an string?  false

gon08:03:45

for is not realized, it is lazy. you need to force it to "realize" its content, wrap it in a doall sexp like (doall (for [x (range 1 i-arg)] (fizz-buzz-full x) ....

Jan Ignatius08:03:28

Ok, I need to read up on these concepts of lazy and realized.

👀 3
Jan Ignatius09:03:33

And a thank you @gon - I've been at this for hours and I don't think I would have realized where the problem was on my own.

simongray09:03:32

The laziness of sequences is one of the major gotchas for all beginners of Clojure.

Jan Ignatius09:03:06

Is the range that makes the for lazy?

solf09:03:56

It's for itself https://clojuredocs.org/clojure.core/for > ...and yields a lazy sequence of evaluations of expr...

Jan Ignatius09:03:03

Interesting. And when evaluating through REPL, these lazy sequences are always realized, even without the doall?

solf09:03:27

eh, yes, because when you eval through the repl... the repl prints something

solf09:03:36

to print it, it needs to realize it

solf09:03:14

I've wasted a few hours myself on that specific issue, having something seemingly work on the repl, but not without it 😅

Jan Ignatius09:03:33

I guess there is no linter that could catch that

solf09:03:05

Maybe in some specific circumstances, where lazy sequences wouldn't make sense... but not that I know of

raspasov09:03:36

If you pass the lazy sequence around and use it eventually, it will get realized. Only case where you run into confusion is if you write something like:

(let [_ (for [i (range 10)] (println i))]
  (do
    ;... some other stuff
    ))

raspasov09:03:56

This will not realize the sequence, because it’s never used.

raspasov09:03:18

This will via (doall …) :

(let [_ (doall (for [i (range 10)] (println i)))]
  (do
    ;... some other stuff
    ))

Jan Ignatius09:03:34

The challenge I have is that I don't quite know how to read something as lazy. For me, in my code example, the arg-conversion function is called and it has a call for "for x until y, call another function (fizz-buzz-full) and include in that call the current value of the ongoing sequence between x and y".

raspasov09:03:47

The docs are usually pretty clear about what returns lazy sequences: https://clojuredocs.org/clojure.core/map -> lazy https://clojuredocs.org/clojure.core/filter -> lazy https://clojuredocs.org/clojure.core/filterv -> returns a vector not lazy! https://clojuredocs.org/clojure.core/mapv -> again, vector, not lazy But I agree with your point, there’s no way to easily “know” at the beginning. If it’s bothering you, I’d even say just wrap all collection operations in (doall …) while exploring things at the beginning. Before long, you’ll develop the intuition what’s lazy and what’s not.

raspasov09:03:27

I personally generally avoid (for …) in my code… It’s a macro, not a function. That’s awkward sometimes. I realize the temptation, coming from another language, because the word “for” is familiar. Try using map/filter/remove/reduce instead .

raspasov09:03:40

(for …) has its uses and and it can be powerful, it can do cartesian products and stuff… https://clojuredocs.org/clojure.core/for but 98% of the time you don’t need that.

Jan Ignatius10:03:32

What would be a more elegant way for me to call the fizz-buzz-full?

raspasov10:03:48

Try something like: (mapv fizz-buzz-full (range 1 i-arg))

raspasov10:03:56

(intentionally using (mapv…) which is not lazy)

Jan Ignatius10:03:22

It's interesting how the language keep tripping me. The mapv guide says that it applies f to the collection. Which in my mind reads (for my example) "applies fizz-buzz-full to <current vector created by range>" - which sounds exactly reverse what I want; to apply the vector value to the function...

raspasov10:03:32

Perhaps it’s just the incompleteness of written/spoken language to describe abstract domain concepts… Maybe a more verbose wording is: calls f on each item of the collection and returns a vector of all (f item) return values.

Jan Ignatius10:03:58

Talking about the docs, what exactly is "ISeq"? I keep seeing it in the descriptions

andarp11:03:07

To me ”apply x” reads as “use function x (... on something)”, not “use value x with some function”. Not saying that’s the right way to read it but to me the sentence you mentioned does make sense

andarp11:03:38

Talking in a general sense here, not Clojure-specific

Jan Ignatius11:03:52

I feel like I need a new dictionary when reading these 🙂 .. e.g. what exactly is a "stateful cursor" in this context?

Aron10:03:48

Hi, can someone please help me if there is an very short way to write a list of keys to be checked if all of them have truthy values in a given map?

Ben Sless10:03:43

(apply every-pred (map (fn [k] (fn [m] (get m k))) ks))

Ben Sless10:03:09

you can also use reduce instead of apply in this context

borkdude11:03:24

This might also works

user=> (every? identity (vals (select-keys {:a 1 :b false} [:a :b])))
false
user=> (every? identity (vals (select-keys {:a 1 :b true} [:a :b])))
true

Aron11:03:33

thanks, the first was how i imagined doing it but this should be an inline predicate, creating nested lambdas in such places is confusing for me, but creating a new utility dependency to put a new predicate is also seems a lot the second is what I imagined would be the short way, didn't know how to write it best, if I could use identity for it really

Aron11:03:36

thanks again

Aron11:03:19

can't wait spec2 to be finished

3
Daniel Stephens12:03:27

This has some caveats but they might be fine. Your map can't be nil and you want missing keys to be falsey:

(every? {:a 1 :b 2 :d false} [:a :d])
=> false
(every? {:a 1 :b 2 :d false} [:a :b])
=> true
(every? {:a 1 :b 2 :d false} [:a :missing])
=> false

👍 3
Aron12:03:21

all yes 😄

🎉 6
Sebastian12:03:49

I've started working on clojure specs. I want to validate some data given in a json file. I want to validate this data before it goes into the database, so the error message happens further out than at the database level. I've created some specs and functions that validate now, however is there a way to test these like i would create tests for other functions in my test folder? Or am i misunderstanding how specs are supposed to work?

Sebastian12:03:58

I would like to test the specific specs such as (s/def ::username (s/and seq string?))

Daniel Stephens13:03:26

You can use s/valid? in your tests

(s/def ::username (s/and seq string?))
(s/valid? ::username "")
=> false
(s/valid? ::username "asasa")
=> true
(is (s/valid? spec val))

Sebastian13:03:44

Yes, but these specs are in a different namespace. I am unsure how to handle that?

Bastien Rivière13:03:38

You can specify the keyword with the namespace like this:

(s/valid? :my.namespace/username)

Bastien Rivière13:03:14

Or if you have an alias:

(s/valid? ::myalias/username)

Sebastian13:03:30

I see. Thank you

Sebastian15:03:38

I am having a hard time with optionality in spec and it seems im not alone. I am currently trying to express these predicates. Either :type1 is true or :type2 is true. If type1 is true then val also needs to be valid. If type2 is true then val can be optional or even nil. Is there a possible way to express this with specs?

(s/def ::type1 (= :type1 %))
(s/def ::type2 (= :type2 %))
(s/def ::val int?)
(s/def types (s/keys req-un [:type1]
                     opt-un [:val :type2]))
I tried modelling val into a map with type1 and type2
(s/def ::types (s/or :type1 (s/keys :req-un [::type1 ::val])
                     :type2 (s/keys :req-un [::bilag])))
However this does not include "val" in case of type2. And if i include val in type2 it will fail because it cannot be nil. Is there a way to better express this using specs, or is this out of scope of what spec is?

alexmiller15:03:33

this is what s/multi-spec is for

🙌 3
Sebastian15:03:20

You are a saviour

introom16:03:03

For clojure.java.shell/sh, is it to get the output of the subprocess? The subprocess is long running and I want it to print out to the console.

alexmiller16:03:21

Using Java’s ProcessBuilder api directly via interop is pretty easy and makes this pretty trivial (this api did not exist when sh was created)

lread20:03:31

There is also @borkdude’s https://github.com/babashka/process which I personally like very much.

borkdude20:03:10

@UGC0NEP4Y :

(babashka.process/process ["foobar"] {:inherit true})

sova-soars-the-sora20:03:12

is jubot the main clojure go-to for making a chat bot?

Savo21:03:24

For fellow beginners & Regular 4clojure problem solvers. I wrote an article on interesting problem I encountered in 4clojure problem list. https://savo.rocks/posts/my-journey-to-understanding-function-composition/

butterguns21:03:20

Hi. I enjoy using keyword args for optional function args. e.g.

(defn add 
  [a b & {:keys [say-hello?]}]
  (when say-hello?
    (prn "hello"))
  (+ a b))

(add 1 2 :say-hello? true)
I come undone when I want to pass these optional args to a similar fn, e.g.
(defn increment 
  [a & {:keys [say-hello?]}]
  (add a 1 ....what-goes-here?))
(Ignoring that I could use partial in the example above, because this is a trivial example). How do I do this elegantly? Or should I just give up and not use & ?

seancorfield21:03:09

Use a hash map instead of "named arguments" -- makes it easier to compose function calls.

seancorfield21:03:49

Named arguments -- & {:keys [...]} -- is OK for top-level functions that humans call, but it doesn't work well for functions calling functions.

alexmiller23:03:00

* yet! (stay tuned for 1.11)

😮 18
seancorfield23:03:22

I was tempted to mention that discussion 🙂

seancorfield23:03:48

Remind me: is the plan to allow a function declared with named arguments to also be able to take a hash map? So where you have & {:keys [...]} you can pass a single hash map instead of the unrolled arguments.

seancorfield23:03:51

Or will it work the other way round? Or will it work both ways? I think it's unambiguous in both directions, right?

alexmiller00:03:31

Optional trailing map

seancorfield00:03:52

So you'll declare with & {:keys [...] :as opts} and then you can call with either :named "arguments" or {:named "arguments"}? Sounds good.

alexmiller02:03:44

or a mix of some args AND a trailing map

seancorfield02:03:09

Oh, interesting. Do you think it'll start a major shift back to named arguments? Although I guess that will depend on how quickly folks adopt 1.11...

seancorfield21:03:15

Updating your code to take that approach:

(defn add
  [a b {:keys [say-hello?]}]
  (when say-hello?
    (prn "hello"))
  (+ a b))

(add 1 2 {:say-hello? true})

(defn increment
  [a opts]
  (add a 1 opts))

butterguns22:03:38

Would you do this consistently, across your code? Or mix the hash map approach / named args approach depending on what you need?

seancorfield22:03:44

I would use the hash map approach consistently, and only provide the named arguments version for a specific, human-focused use case, such as in the REPL.

seancorfield22:03:24

It used to be common to see named arguments in a lot of Clojure code but pretty much everyone has moved away from it over the years.

butterguns22:03:45

That's a shame, I love how elegant it is, but I can totally understand the reasoning behind that

seancorfield22:03:22

It's only elegant when you're typing it in as a human 🙂 When you're trying to pass it down the call chain, it's a PITA 🙂

butterguns22:03:58

Tell me about it 🙂