beginners

Getting started with Clojure/ClojureScript? Welcome! Also try: https://ask.clojure.org. Check out resources at https://gist.github.com/yogthos/be323be0361c589570a6da4ccc85f58f.
FHE 2021-05-06T00:38:57.232900Z

Even though there's a shadow-cljs.edn file? OK. Thanks. I'll start with just that and maybe try moving to tools.deps (or is that deps.edn??) later.

2021-05-06T03:46:43.234900Z

Is it considered bad form to nest -> inside cond-> like this? Suppose I want to both assoc some keys and dissoc some keys if the map contains :a (and perhaps even do more things) Is there a better way of expressing this?

(let [my-map {:a 1 :b 2 :c 3}]
  (cond-> my-map
    (:a my-map) (->
                 (assoc :x 8 :y 9 :z 10)
                 (dissoc :b))))

2021-05-06T14:35:59.265500Z

thanks, all

2021-05-17T16:49:15.330500Z

@ben.sless the threading macros are designed to nest inside -> , in this case I think if is more readable / idiomatic

(let [my-map {:a 1 :b 2 :c 3}]
  (if-not (:a my-map)
     my-map
     (->> my-map
          (assoc :x 8 :y 9 :z 10)
          (dissoc :b))))

2021-05-17T16:49:32.330800Z

but the first example is OK

2021-05-17T17:27:08.331400Z

yeah I was thinking more where there were potentially multiple “rules” that should apply. the nice thing about cond-> is that all matching branches will happen, unlike cond

2021-05-17T17:27:47.331600Z

though I suppose at a certain point it could be argued that needs to be broken up into different fns

2021-05-17T17:32:00.331800Z

oh yeah, definitely use cond-> if there are more than one conditional

lsenjov 2021-05-06T04:02:15.235100Z

I do it often. The other option is to repeat the same condition which is harder to read imo.

👍 2
alexmiller 2021-05-06T04:17:39.235600Z

seems ok to me

Mark Wardle 2021-05-06T06:05:35.238400Z

Thanks both. That’s really helpful. I will remove the protocol and make it as simple as possible for what I need now. I was getting itchy when i had to keep adding more methods to the protocol!

Ben Sless 2021-05-06T06:47:31.238900Z

Aren't threading macros designed to be composed this way? Or was it just a happy accident?

👍 1
Ben Sless 2021-05-06T06:53:15.239200Z

Another approach, especially when you need a single instance with no versions is to use reify. You can always reify into more protocols in the future.

👍 1
zackteo 2021-05-06T07:05:32.241100Z

Hello, after doing partitioning how do you merge parts of the resulting sequence based on a condition like from [["content"] ["*"] ["content"] ["*"] ["content"]] to [["content"] ["*" "content" "*"] ["content"]]

zackteo 2021-05-06T07:06:55.242400Z

Am not sure what kind of function should I be using. Do I use reduce or do I have to loop through the sequence ?

zackteo 2021-05-06T07:22:45.244800Z

Okay I think I understand that reduce should work. But for more complicated cases like this it can be quite difficult to reason about :thinking_face:

2021-05-17T16:52:44.331Z

the thing that made reduce / fold click for me was realizing that it is a direct translation of a for loop across a collection, with the nuance that you pass an immutable value forward to each iteration (with reduced allowing for early exit). note that if you need multiple values to be carried across elements ("updated for each cycle of the loop") you can easily use a hash-map as the accumulator and pull out the interesting values on exit

2021-05-06T07:27:52.244900Z

Maybe with split-with ?

zackteo 2021-05-06T07:44:29.246Z

I just tried using someone's implementation of split-by to do the partitioning of sorts. But that ended up with giving me [["content"] ["*" "content"] ["*" "content"]]

raspasov 2021-05-06T08:14:50.247500Z

I don’t understand what’s the condition for merging.

raspasov 2021-05-06T08:15:32.247700Z

Is it every time you encounter "*" ?

zackteo 2021-05-06T08:16:11.247900Z

Yeah. Something like that. I just made it general

yuhan 2021-05-06T08:17:57.248100Z

is * something like a delimiter?

zackteo 2021-05-06T08:18:14.248300Z

Am actually using some-fn to join a str/starts-with and str/end-with for the purposes of partition out multi-line comments of /* and */

raspasov 2021-05-06T08:18:37.248500Z

Ok, let me think for a second what’s idiomatic here… You often come up with cases that I don’t encounter in day-to-day stuff :) Are you working with data input that you don’t control? If yes, then that’s ok. But if you have enough control over the data input, I always try to think how to avoid having to do “magic tricks” like that.

raspasov 2021-05-06T08:19:15.248700Z

The best solution to a problem is not having the problem in the first place! 😃

zackteo 2021-05-06T08:19:26.248900Z

hmmmm am actually working with a .cpp file like

zackteo 2021-05-06T08:19:35.249100Z

#include <stdio.h>

/*
   This is a multi-line comment
   The blanks in multi-line comment is counted as Blanks

*/
int main() {
  /// This is a comment line
  printf("Hello, world! \n "); // Code+Comment line together counted as Code.
  return 0;
}

raspasov 2021-05-06T08:19:44.249300Z

Oh, man. Ok 🙂

zackteo 2021-05-06T08:19:58.249500Z

Am trying to find the number of lines which are commented

raspasov 2021-05-06T08:20:00.249700Z

I guess that very much falls into “I don’t control the input” category

zackteo 2021-05-06T08:20:12.249900Z

Right 😅

zackteo 2021-05-06T08:20:23.250100Z

so i did

zackteo 2021-05-06T08:20:29.250300Z

(partition-by (some-fn #(str/starts-with? % "/*")
                             #(str/ends-with? % "*/"))
                    (str/split (slurp "resources/test.cpp") #"\n"))

zackteo 2021-05-06T08:22:30.251100Z

To get

(("#include <stdio.h>" "")
 ("/*")
 ("   This is a multi-line comment"
  "   The blanks in multi-line comment is counted as Blanks"
  "")
 ("*/")
 ("int main() {"
  "  /// This is a comment line"
  "  printf(\"Hello, world! \\n \"); // Code+Comment line together counted as Code."
  "  return 0;"
  "}"))

zackteo 2021-05-06T08:23:07.251300Z

I thought this is a good step towards identifying the number of comments at least in the sense of handling the multi-line case

raspasov 2021-05-06T08:23:49.251500Z

Hm …. Perhaps https://clojuredocs.org/clojure.core/line-seq … And look for /* and */ in each line?

zackteo 2021-05-06T08:24:22.251700Z

And identified that the next step would be to join the (/) ( ...) ( /) in the same partition

raspasov 2021-05-06T08:25:02.251900Z

I see, you’re already basically doing what line-seq does…

yuhan 2021-05-06T08:26:48.252100Z

Sounds like a language parsing problem - might be good to use a library like Instaparse for it

yuhan 2021-05-06T08:28:17.252300Z

Or if you want to roll your own line-based parser, maybe implement a state machine that represents inside/outside comment and use that to reduce over the sequence of lines

2021-05-06T08:29:13.252500Z

If it's a string I think regex can do it.

raspasov 2021-05-06T08:29:32.252700Z

Yes… Library sounds a good idea, if a good one exists for C++ code…

yuhan 2021-05-06T08:29:48.252900Z

c comments are particularly weird because they can be nested

zackteo 2021-05-06T08:29:56.253100Z

@yuiti.usp I would think I already reduced the problem to converting [["content"] ["/*"] ["content"] ["*/"] ["content"]] to `[["content"] ["/" "content" "/"] ["content"]]`

zackteo 2021-05-06T08:30:48.253300Z

Like ignoring nesting of sorts for now

zackteo 2021-05-06T08:32:43.253500Z

Can I iterate through the sequence to do something like if / join with the element after it . if / join with the element before it

zackteo 2021-05-06T08:33:52.253700Z

Am just thinking if I can use something like map/reduce. Or do I need to use stuff like loop

yuhan 2021-05-06T08:35:54.253900Z

probably you need loop or reduce with a simple state machine in the accumulator, that says "I'm inside/outside a comment" and decides what to do with the next element based on that

yuhan 2021-05-06T08:41:12.254100Z

There's a library called "seqexp" for this which is like regexes for sequences, basically more high level than writing your own finite state automaton

raspasov 2021-05-06T08:43:43.254300Z

(let [source (slurp "/Users/raspasov/cpp")
      lines  (line-seq
              (java.io.BufferedReader.
               (java.io.StringReader. source)))]
 (sequence
  (comp
   ;find start of comment
   (drop-while
    (fn [line]
     (not (clojure.string/includes? line "/*"))))
   ;find end of comment
   (take-while
    (fn [line]
     (not (clojure.string/includes? line "*/")))))
  lines))

raspasov 2021-05-06T08:43:55.254500Z

=> ("/*" " This is a multi-line comment" " The blanks in multi-line comment is counted as Blanks")

zackteo 2021-05-06T08:45:56.254700Z

hmmmm

raspasov 2021-05-06T08:47:41.254900Z

This will actually only find the first comment block in the file…

zackteo 2021-05-06T08:48:00.255200Z

yeap 😅 yeap I realised hahaha

zackteo 2021-05-06T08:48:50.255400Z

I'm still someone convinced there should be a relatively simple way to go from [["content"] ["*"] ["content"] ["*"] ["content"]] to [["content"] ["*" "content" "*"] ["content"]]

zackteo 2021-05-06T08:49:49.255600Z

feels like i would've done some form of this in some 4clojure problem at some point 😅

yuhan 2021-05-06T08:52:47.255800Z

(let [input                   ["A" "B" "/*" "C" "D" "*/" "E"]
      [before [start & then]] (split-with #(not= "/*" %) input)
      [during [end & after]]  (split-with #(not= "*/" %) then)]
  [before [start during end] after])
;; => [("A" "B") ["/*" ("C" "D") "*/"] ("E")]

zackteo 2021-05-06T08:55:20.256Z

hmmmmm

zackteo 2021-05-06T08:57:10.256200Z

On a side note i was quite amused one someone's implementation of split-by on the split-with clojuredocs Like so.

(-> (split-by (some-fn #(str/starts-with? % "/*")
                       #(str/ends-with? % "*/"))
              (str/split (slurp "resources/test.cpp") #"\n"))
    )
Made the problem become
[["content"] ["/*" "content"] ["*/" "content"]]

2021-05-06T09:05:43.256500Z

It's dirty, but:

(let [seen (atom 0)
      skip (atom false)]
 (partition-by
  #(do
    (when (= % ["*"])
     (swap! seen inc))
    (cond
     (true? @skip)
     (do (reset! seen 0)
      (reset! skip false)
      true)  
     (= 1 @seen) 
     true
     (> @seen 2)
     (do (reset! skip true)
      false) 
     :else false))                    
  [["content"]
   ["*"]
   ["content"]
   ["*"]
   ["content"]]))
((["content"])
  (["*"] ["content"] ["*"])
  (["content"]))

zackteo 2021-05-06T09:08:31.256900Z

Wow! Yeap that definitely works! Thanks everyone for your help and advice so far 🙂

2021-05-06T09:08:35.257100Z

Then you can map flatten it as a final pass to get exactly what you wanted

zackteo 2021-05-06T09:09:03.257500Z

mmmm right right!

zackteo 2021-05-06T09:09:15.257700Z

For now, I think I'll take a break and think this through further later :thinking_face:

raspasov 2021-05-06T09:46:49.260100Z

Ok, I believe this works:

(ns ss.experimental.comment-line-count
 (:require [net.cgrand.xforms :as x]))


(defn count-comment-lines []
 (let [source (slurp "/Users/raspasov/cpp")
       lines  (line-seq
               (java.io.BufferedReader.
                (java.io.StringReader. source)))]
  (transduce
   (comp
    (partition-by
     (fn [line]
      (cond
       (clojure.string/includes? line "/*") :start
       (clojure.string/includes? line "*/") :end)))
    ;partition so we can access previous elements
    (x/partition 2 1 (x/into []))
    (map
     (fn [[[?comment-start-line] ?comment-lines :as v]]
      (if (clojure.string/includes? ?comment-start-line "/*")
       ;return number of commented lines
       (count ?comment-lines)
       ;else, return vector unchanged
       v)))
    ;only the numbers
    (filter number?))
   ;sum all the commented lines
   +
   lines)))

raspasov 2021-05-06T09:47:20.260300Z

(requires the https://github.com/cgrand/xforms lib to do one partition transducer trick …)

raspasov 2021-05-06T09:52:02.260700Z

Same code https://gist.github.com/raspasov/156da3ff7e2fb6a8b35221c0a28dc8af

2021-05-06T09:54:33.260900Z

Here's a regex approach:

(map second
 (re-seq
  #"[\s\S]*?(/\*[\s\S]*?\\\*)[\s\S]*?"
  "c++ code here
/* Some c++ comment
     id here on multoline. \\*
 c++ here too
 /* Another comment \\*"))
Returns:
("/* Some c++ comment\n     id here on multoline. \\*"
 "/* Another comment \\*")

2021-05-06T09:59:24.261200Z

Then you can just:

(map count
 (map clojure.string/split-lines
  '("/* Some c++ comment\n     id here on multoline. \\*"
    "/* Another comment \\*")))
To get the count of lines per comments.

2021-05-06T10:00:38.261400Z

(2 1)
And then the sum of it all:
(reduce + '(2 1))
3

Christian 2021-05-06T10:07:55.261800Z

Hi, I´m new to clojure and trying to post a multipart/mixed http request and have tried several http clients(clj-http, http-kit and hato) but none of them seems to support content type mulitpart/mixed. Is there any other client libraries that support mulitpart/mixed? So that we can to something like this. (The following sample returns an 415 because hato requries a multipart/form-data header. )

(defn multipart-request
  []
  (hc/request {:method "POST"
               :url my-url
               :headers {"cookie" my-auth-token 
                         "content-type" "multipart/mixed; boundary=batch_B01"}
               :multipart [{:name "request1"}
                           {:content-type "application/http"}
                           {:content "GET A_Product(Product='123456') Accept: application/json"}]
               })
)
The rest api Im using, (Odata RESTful API, that supports batch processing ) requires this in the header: "content-type" "multipart/mixed; boundary=batch_B01" And a body like this.
--batch_B01
Content-Type: application/http

GET A_Product(Product='123456') HTTP/1.1
Accept: application/json


--batch_B01--
Any suggestions on how to solve this? Thanks :-)

Christian 2021-05-07T08:15:30.308800Z

Thanks for your response drewerel. We could just build our request without using the multipart part of the client http library, since we are able to make it work by building the request body "manually". But we want to test if multipart sections of a http client library can help us. Like described here https://github.com/gnarroway/hato#multipart-requests. Since it seems like Hato only supports content-Type of multipart/form-data, we may try to find another client library. So my question is really, has anyone tried to create a multipart/mixed request like the sample I posted, by using the multipart section of a http client library? Thx

Juλian (he/him) 2021-05-06T10:56:20.263500Z

shouldn't :name, :content-type and :content all be part of the same map?

zackteo 2021-05-06T11:13:26.263700Z

I just realised why the task is so complicated. I thought if I use partition-all like so it would work

(->> (partition-by (some-fn #(str/starts-with? % "/*")
                              #(str/ends-with? % "*/"))
                     (str/split-lines (slurp "resources/test.cpp")))
       (partition-all 3 1)
       (map (fn [x]
              (if (str/starts-with? (ffirst x) "/*")
                x (first x))))
       (map flatten)
       )
But didn't consider the case of the content inside the comment + the end comment

zackteo 2021-05-06T12:35:21.264100Z

alas if we are choosing to work with state .... this way works ...

(partition-by
    (let [comment-region? (atom false)]
      (fn [line]
        (cond
          (str/starts-with? line "/*") (do
                                         (reset! comment-region? true)
                                         @comment-region?)
          (str/ends-with? line "*/") (do (reset! comment-region? false)
                                         true)
          :else @comment-region?)))
    
    (str/split-lines (slurp "resources/test.cpp")))

2021-05-06T21:37:54.271Z

I'm not familiar with multipart/mixed http requests, but in my experience your response can only have one "content-type" in the header. So when i see you put it twice, i'm confused, can you explain provide the response in plain text that you hope to create? That would help me at least.

2021-05-06T21:50:28.271200Z

oh thats interesting, it does have multiple content types.

2021-05-06T21:54:38.271400Z

this seems relevant based n the name https://ring-clojure.github.io/ring/ring.middleware.multipart-params.html

2021-05-06T22:01:11.271700Z

That would be for your server handling a multipart params request, not for posting one though.

2021-05-06T22:04:03.271900Z

your saying the server returns a 415 because > The HTTP `415 Unsupported Media Type` client error response code indicates that the server refuses to accept the request because the payload format is in an unsupported format. https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/415 So you would need to allow that content type, that's up to that server's handler code and not related to the http client unless i'm off track.

zackteo 2021-05-06T23:52:01.276100Z

Hello, I have a function that gives me something like ([1 2 3] [4 5 6] [7 8 9]) where I want to know the sum of each column. So I use (apply map +) for that. However, if I get an empty list () apply will return a function. Which my later destructing does not handle. May I ask what is an idiomatic way to handle this? I thought of something like fnil but that doesn't really works here

seancorfield 2021-05-06T23:54:18.276500Z

@zackteo So you have (apply map + cols) ?

zackteo 2021-05-06T23:54:27.276700Z

yeap

seancorfield 2021-05-06T23:55:20.277700Z

What do want the result to be?

zackteo 2021-05-06T23:55:25.277800Z

but (apply map + ())` will return a function

zackteo 2021-05-06T23:55:56.278300Z

in most cases I will have (apply map + [[1 2 3] [4 5 6] [7 8 9]]) something like that

seancorfield 2021-05-06T23:56:53.279700Z

Depending on what result you want back, maybe (apply map + (or (seq cols) [[]])) ?

zackteo 2021-05-06T23:57:01.279900Z

I guess I'm thinking of how to do error handling, where I get an empty list. Perhaps I should be handling that earlier :thinking_face:

seancorfield 2021-05-06T23:57:49.280800Z

Unless there is some obvious, neutral result for the “sums of no columns”, you should check cols before trying to apply map + I think.

zackteo 2021-05-06T23:58:45.281Z

I see I see!

zackteo 2021-05-06T23:59:41.281800Z

How might I want to check this then? with an if ?

seancorfield 2021-05-06T23:59:46.282Z

dev=> (for [cols [ () '([1 2 3] [4 5 6] [7 8 9]) ]]
 #_=>   (apply map + (or (seq cols) [[]])))
(() (12 15 18))
if () is the “obvious, neutral result”