Fork me on GitHub
#beginners
<
2021-03-08
>
sb09:03:42

If I have two different kind of output like vector or map -> like in this topic https://gist.github.com/damesek/d6a371ce56983e77872cd7a574f548bb is that possible use comp in some way? I can solve with two different function or not so nice solution like this.. but is that possible nicer?

(reduce (-transform-to-map)
          {}
          (reduce (-vec-frequencies)
                  []
                  data))

raspasov10:03:20

(.indexOf …) is O(n) so watch out if you’re applying this to large collections, it can be quite slow.

sb10:03:05

True, I totally forget.. thanks!

👍 1
Hagenek10:03:28

Is there any way for me to use java methods as predicate functions in Clojure? I just discovered that while the first function works, the second doesnt:

(defn letters? [s]
  (not= (count (filter #(if (Character/isLetter %)
                          true
                          false) s))
        0))
(defn letters2? [s]
(some Character/isLetter s))

borkdude10:03:30

@georghagen Just wrap in a function: (some #(Character/isLetter %) s))

Hagenek10:03:01

Thanks 😃 I end up with this because nil is truthy, and more idiomatic way to write it? (defn letters? [s]   (if (some #(Character/isLetter %) s)     true     false))

David Reno10:03:24

Do you even need the “if” statement?

David Reno11:03:04

Oh, I think I see. You don’t want the truth/falsy values returned, you want it coerced.

Hagenek11:03:33

That's right because it needs to be like that for the predicate function to work in some right?

borkdude11:03:26

(def letters? (partial some #(Character/isLetter %)))

clojure-spin 1
borkdude11:03:11

if you want to be fancy ;) I usually still write the defn out in full though (better static analysis for clj-kondo for example)

Hagenek11:03:43

Ahh ok, yeah it gets to be a point where you can just write it out, Clojure is very terse to begin with also so 😃

Stuart11:03:11

if you are doing (if test true false), can you replace this with just (boolean test) Or am I misunderstanding?

borkdude11:03:49

correct

👍 1
Lisbeth Ammitzbøll Hansen12:03:33

Hi. How do I traverse this kind of data structure and return a value of total minutes for each key :

(defn time-regs
  []

  {""  [{:user_id 1, :email "", :time-registered "0 hrs, 0 min"} 
                        {:user_id 1, :email "", :time-registered "2 hrs, 20                        
                          min"}], 
   ""  [{:user_id 3, :email "", :time-registered "0 hrs, 20 
                          min"}
                         {:user_id 3, :email "", :time-registered "1 hrs, 20  
                          min"}]})
I want to traverse :time-regs and for every key ("<mailto:[email protected]|[email protected]>", "<mailto:[email protected]|[email protected]>") evaluate :time-registered with :
match (re-matches #"([0-9]+) hrs, ([0-9]+) min" time-reg)
and if there is a match, add minutes. Finally, I want to return something like :
{""   [:min-total 140]
  "" [:min-total 100"]}
I have looked at doseq - it makes it possible to access my keys and values, but doesn't return a value. I have tried with map : (map #(% 0) (time-regs)), but can't find a way to access the values for the keys

raspasov12:03:32

This is how to access the values of the keys:

(->> (time-regs)
     (map
       (fn [[k vs]]
         [k [:min-total (mapv :time-registered vs)]]))
     (into {}))
=> {“<mailto:[email protected]|[email protected]>” [:min-total [“0 hrs, 0 min” “2 hrs, 20 min”]], “<mailto:[email protected]|[email protected]>” [:min-total [“0 hrs, 20 min” “1 hrs, 20 min”]]}

raspasov12:03:55

I am not sure how to reliably convert things like “2 hrs, 20 min” to minutes; perhaps there’s a better format to return this in? If not, I’d look into date/time libraries that might support this kind of thing; ideally I’d like to avoid it and rely on something like a timestamp/date.

Lisbeth Ammitzbøll Hansen13:03:37

Thanks a lot for your answer 🙂. True about the time format (it's others code that I'm trying to test)

👍 1
simongray13:03:29

You can use this function to convert the string-based :time-registered format to minutes

(defn time-registered->min-total
  [time-registered]
  (when-let [[_ hrs min] (re-matches #"(.+) hrs, (.+) min" time-registered)]
    (+ (* (Integer/parseInt hrs) 60)
       (Integer/parseInt min))))

(time-registered->min-total "2 hrs, 39 min")
;;=> 159

👌 2
Jim Strieter13:03:03

This question is about style. I wrote a function like this: (defn my-func [some-arg] (let [ x1 (whatever ...) x2 (whatever ...) y1 (whatever ...) y2 (whatever ...)] (fn [x] ( whatever)))) The point is to solve a boundary value problem, then return a function that is the solution to that problem. Here's my question: Am I guilty of writing C in Clojure?

pavlosmelissinos13:03:59

Sorry, it's not clear to me what you're doing. Where are you using those bindings? What about some-arg? What do you think is C-like about your example? Apart from your question, I think it's more common to start the bindings on the let line:

(defn my-func [some-arg]
  (let [x1 (whatever ...)
        x2 (whatever ...)
        y1 (whatever ...)
        y2 (whatever ...)]
    (fn [x] (whatever))))

teodorlu13:03:27

I've seen @U04V15CAJ use _ as let symbols to just "collect side effcts". Something like

(defn my-func [some-arg]
  (let [x1 (whatever ...)
        _ (whatever ...)
        y1 (whatever ...)
        _ (whatever ...)]
    (fn [x] (whatever))))
if x2 and y2 are never used. You could see if you could refactor your functions for use in a pipeline (`->>` or ->)

teodorlu13:03:11

> Here's my question: Am I guilty of writing C in Clojure? Sure, that looks a lot like C. But Clojure is flexible for a reason. Writing imperative code in Clojure is fine, if you need imperative code.

1
Jim Strieter13:03:29

What the function does is take 2 tuples (x1, y1), (x2, y2), and solve y=mx+b. Then return a function representing the y=mx+b.

Jim Strieter13:03:44

Yeah I can tidy up the indents a bit

Jim Strieter13:03:53

@U3X7174KS yeah Haskell you hear that???

❤️ 1
manutter5114:03:15

I like to use a style like that to write “self-documenting” code--breaking the intermediate values out onto separate lines in a let allows you to assign them meaningful and informative names that will help future devs in understanding what exactly the function is doing.

5
manutter5114:03:35

Sometimes I’ll write an expression that uses the -> threading macro, and then later I’ll come back and I have a hard time following the flow, so I’ll convert it back to a let statement with meaningful names for the intermediate values.

👍 1
Jim Strieter14:03:08

@U06CM8C3V yeah, that makes sense

andarp17:03:55

This pragmatic approach is great and I’m happy to see it championed among clojurists. A lot of functional heavy languages also tend to cultivate a culture where terse compositions / pipelines are preferred and while I can see the appeal of elegance I think it can easily create hard to maintain code.

robertfw18:03:04

Adding another datapoint, I started off with terse code and after my first time working with others on a Clojure project, shifted to writing more verbose let binding blocks to improve readability. The trick for using _ for side-effecting steps within a let is also something I'll lean on, I think the code is much cleaner that way than using multiple lets, though if there are lots of them I consider it a code smell that has me looking to see if there is a better structure

Endre Bakken Stovner15:03:11

I want to implement the following function in Clojure:

(facts "about `to-namedlist`"
       (to-namedlist {:plot "quals.svg" :data "quals.tsv"}) => {:plot "quals.svg" :data "quals.tsv" 0 "quals.svg" 1 "quals.tsv"}

       (to-namedlist ["bwa-map.bam" "bwa-map2.bam"]) => {0 "bwa-map.bam" 1 "bwa-map2.bam"}

       (to-namedlist "hg19.fasta") => {0 "hg19.fasta"})
It seems like I need to switch on the type of the input arg to do it. What is the most idiomatic way to achieve this?

Endre Bakken Stovner16:03:06

Thanks. I guess (case (type arg) ... would also be acceptable here then?

andy.fingerhut19:03:57

There can be multiple different concrete types implementing each of these kinds of collections, e.g. PersistentHashMap and PersistentArrayMap for maps, plus a handful of others that are in less common use.

andy.fingerhut19:03:32

If all you care about is that it implements the Clojure map interface, then (map? x) is better than listing multiple concrete types, probably, for long term code maintenance.

👍 2
☝️ 1
noisesmith21:03:23

also, you can use defmulti and use type as your dispatch function, that way you can add support for more types in a more elegant way, and defmulti automatically does proper subclass dispatch

(cmd)user=> (defmulti foo type)
nil
(cmd)user=> (defmethod foo java.util.Map [_] :map)
#object[clojure.lang.MultiFn 0x2e52fb3e "[email protected]"]
(cmd)user=> (foo {})
:map
(ins)user=> (foo (java.util.HashMap.))
:map

Endre Bakken Stovner16:03:27

I know the below is abominable code... But even worse, it does not work as expected:

(defn switch-on-type [arg]
  (case (str (type arg))
    "java.lang.String" "String!!!"
    "clojure.lang.PersitentArrayMap" "Map!"
    "Dunno"))

(switch-on-type "hi") ; "Dunno" ;; I wanted "String!!!"

manutter5116:03:09

Try making the last line (str "Dunno what " (type arg) " is."), it’ll help you pin down the problem.

Endre Bakken Stovner16:03:26

Thanks for the suggestion. I am still unable to see my error. Btw, you see that I use (str (type arg)) in my case, right?

manutter5116:03:47

Yes, but including it in the last line should show you what you're getting that you're not expecting. What's it returning if you add that?

Endre Bakken Stovner16:03:04

I still do not see my error... 😖 :duncecap:

Darin Douglass16:03:32

why do you need to check the type explicitly? something like this is much clearer and likely meets your needs?:

(cond
  (string? arg) "String!!"
  (map? arg) "Map!"
  :else "dunno")

Endre Bakken Stovner16:03:16

For some reason I was looking for a hash-map? but did not find it. It's been too long since I've used Clojure 😕

Darin Douglass16:03:45

hash-map is a fn, so i can see the confusion 🙂 glad to help

Endre Bakken Stovner16:03:02

Yes, thank you 🙂

Aviv Kotek16:03:49

using next.jdbc, how would it be possible to do batch-upsert (with column insertion), something like this: (this would work using java.jdbc)

(jdbc/execute! db ["INSERT INTO T(col1, col2) 
                    VALUES (?, ?)
                    ON DUPLICATE UPDATE
                    col2=values(col2)"
                   [["c1" "c2"]
                    ["c3" "c4"]]....more here] {:multi? true})

Aviv Kotek17:03:17

yep, wanted to escape the "prepared-statement", but I guess i'd go with that now -- thanks!

acim117:03:18

How does one typically document that a function throws an exception and which (if applicable)?

walterl17:03:00

Most common in the standard lib is simply "Throws <exception> when <condition>" in the docstring.

walterl17:03:10

Trying to think of an example, but spacing

acim117:03:04

Gotcha. Pretty barebones but to the point, makes sense.

Jim Strieter17:03:30

Is currying considered idiomatic Clojure? If so, is there a rule of thumb for how to order the inputs in a function?

andarp17:03:02

There is partial but I rarely see it used, compared to eg ML languages. Since it’s so easy to create a lambda and that does away with the issue of input ordering I personally haven’t really felt that I’m missing it as an idiomatic approach.

seancorfield17:03:59

I seem to recall Rich has said that he considers short, anonymous functions to be more idiomatic than partial (a long time ago, on the Clojure Google Groups mailing list).

1
Jim Strieter22:03:21

Thank you gents!

pez00:03:16

Sometimes partial just makes more sense to me than a lambda.

Jan Ignatius19:03:57

When using (print), is it possible to suppress the "nil" that follows the printout?

hiredman19:03:08

the nil that follows is the repl printing out the result of the call to print

Jan Ignatius19:03:24

The compiled app would not print it?

hiredman19:03:00

code run outside of a repl won't

Jan Ignatius19:03:11

Ok. Then my fizzbuzz works 🙂

Jan Ignatius19:03:22

I was trying to figure out how to remove that nil for a while now 😄

Jan Ignatius20:03:48

Hmmh, it works in REPL but I can't seem to get the complied version to work. What is the correct syntax to pass on the CLI attributes to the -main function? This is what I have currently:

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

(defn -main [& args]

  (defn fizz-buzz-full [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)))
  
  (for [x (range 1 args)]
    (fizz-buzz-full x)
    )
  )

(-main)

Jan Ignatius20:03:58

lein run 101 just fails with " Exception in thread "main" Syntax error compiling at (test_world2/core.clj:19:1)." 19 being the line for (-main).

dpsutton20:03:07

couple things. don't nest defn. maybe you have experience with scheme? Just put (defn fizz-buzz [n] ...) outside of your main function. arguments passed from the command line are going to be strings. so you'll have "101" not 101 to contend with. If you're invoking (-main) with no args in your namespace it will call that with no args which will hit the code (range 1 nil) which will probably blow up. Can you post the error you've gotten here in a snippet?

Jan Ignatius20:03:07

Ok, I've broken the other function out from -main and added "(int args)" to avoid the string-issue:

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

(defn fizz-buzz-full [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 -main [& args]
   
  (for [x (range 1 (int args))]
    (fizz-buzz-full x)
    )
  )
;(-main)

Jan Ignatius20:03:31

This resulted in a new error (yay!) on "Unable to resolve symbol: fizz-buzz-full in this context"

Jan Ignatius20:03:11

The full error message is very long

dpsutton20:03:59

Yes. The function needs to be defined before it’s call. You can use triple backticks to have code blocks

Jan Ignatius20:03:13

If I comment out the (-main) - as my understanding is that lein run will run (-main) automatically? I get an error on " class clojure.lang.ArraySeq cannot be cast to class java.lang.Character (clojure.lang.ArraySeq is in unnamed module of loader 'app'; java.lang.Character is in module java.base of loader 'bootstrap')"

Jan Ignatius20:03:03

I guess I'm handling the cli arguments in the wrong way.

Jan Ignatius20:03:46

I updated the code block above to match the current version

dpsutton20:03:07

Yeas the &args there should be some kind of sequence of strings. Most likely (“101”).

dpsutton20:03:23

Do you have a repl running?

Jan Ignatius20:03:58

Yeah, in VS code

Jan Ignatius20:03:38

I get the same error if I uncomment (-main) and change it to (-main 101) and try to evaluate it.

dpsutton20:03:42

Do you know how to send forms to the repl from your text file?

Jan Ignatius20:03:30

Yes, the code worked flawlessly until I added the option of defining the range

dpsutton20:03:50

Ok. So again & args will take all of the arguments in as a sequence of args. And you can’t call int on a collection

dpsutton21:03:59

And it might be better to call main with “101” because that’s what it will look like when you call it from the command line

Jan Ignatius21:03:51

So this works in REPL:

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

(defn fizz-buzz-full [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 -main [args]
   
  (for [x (range 1 (int args))]
    (fizz-buzz-full x)
    )
  )

(-main 101)

Jan Ignatius21:03:07

I dropped the & from the [args]

Jan Ignatius21:03:53

But if I comment out (-main 101) and run lein run 101 I still get the error.

Jan Ignatius21:03:22

Do I need to touch some of the other project files to somehow enable the arguments getting passed?

Jan Ignatius21:03:38

I'm very, very new to clojure and lein

pavlosmelissinos21:03:50

your problem is with the way you parse args in your main function

dpsutton21:03:54

but that's not a valid signature for a main function

☝️ 1
Jan Ignatius21:03:00

Ok, what does signature mean in this context? Is the -main function special?

dpsutton21:03:03

now a common thing to do is make a main that has the normal & args function, take the first arg, turn it into a numeric type rather than a string, and then call your function. and this way your function can just deal with a single numeric and do what it wants and ignore strings, and collections, and other ugliness

dpsutton21:03:15

yes. its an entrypoint and takes a collection of strings. that's how it can be invoked from lein run. it kinda has to have that shape. but let's ignore that for now. write a function that takes a single long and does your fizz buzz stuff on it. practice it at the repl (fizzbuzz 7) does what you want it to do and then we can hook up the main function to that

Jan Ignatius21:03:43

Will do. Though tomorrow - it's past 2300 here. Thank you for your help and patience.