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.
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))))
thanks, all
@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))))
but the first example is OK
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
though I suppose at a certain point it could be argued that needs to be broken up into different fns
oh yeah, definitely use cond-> if there are more than one conditional
I do it often. The other option is to repeat the same condition which is harder to read imo.
seems ok to me
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!
Aren't threading macros designed to be composed this way? Or was it just a happy accident?
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.
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"]]
Am not sure what kind of function should I be using. Do I use reduce
or do I have to loop through the sequence ?
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:
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
Maybe with split-with ?
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"]]
I don’t understand what’s the condition for merging.
Is it every time you encounter "*"
?
Yeah. Something like that. I just made it general
is * something like a delimiter?
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 */
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.
The best solution to a problem is not having the problem in the first place! 😃
hmmmm am actually working with a .cpp
file like
#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;
}
Oh, man. Ok 🙂
Am trying to find the number of lines which are commented
I guess that very much falls into “I don’t control the input” category
Right 😅
so i did
(partition-by (some-fn #(str/starts-with? % "/*")
#(str/ends-with? % "*/"))
(str/split (slurp "resources/test.cpp") #"\n"))
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;"
"}"))
I thought this is a good step towards identifying the number of comments at least in the sense of handling the multi-line case
Hm …. Perhaps https://clojuredocs.org/clojure.core/line-seq
… And look for /*
and */
in each line?
And identified that the next step would be to join the (/) ( ...) ( /) in the same partition
I see, you’re already basically doing what line-seq does…
Sounds like a language parsing problem - might be good to use a library like Instaparse for it
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
If it's a string I think regex can do it.
Yes… Library sounds a good idea, if a good one exists for C++ code…
c comments are particularly weird because they can be nested
@yuiti.usp I would think I already reduced the problem to converting
[["content"] ["/*"] ["content"] ["*/"] ["content"]]
to `[["content"] ["/" "content" "/"] ["content"]]`
Like ignoring nesting of sorts for now
Can I iterate through the sequence to do something like if / join with the element after it . if / join with the element before it
Am just thinking if I can use something like map/reduce. Or do I need to use stuff like loop
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
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
(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))
=>
("/*" " This is a multi-line comment" " The blanks in multi-line comment is counted as Blanks")
hmmmm
This will actually only find the first comment block in the file…
yeap 😅 yeap I realised hahaha
I'm still someone convinced there should be a relatively simple way to go from [["content"] ["*"] ["content"] ["*"] ["content"]]
to [["content"] ["*" "content" "*"] ["content"]]
feels like i would've done some form of this in some 4clojure problem at some point 😅
(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")]
hmmmmm
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"]]
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"]))
Wow! Yeap that definitely works! Thanks everyone for your help and advice so far 🙂
Then you can map flatten
it as a final pass to get exactly what you wanted
mmmm right right!
For now, I think I'll take a break and think this through further later :thinking_face:
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)))
(requires the https://github.com/cgrand/xforms lib to do one partition transducer trick …)
Same code https://gist.github.com/raspasov/156da3ff7e2fb6a8b35221c0a28dc8af
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 \\*")
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.(2 1)
And then the sum of it all:
(reduce + '(2 1))
3
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 :-)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
shouldn't :name
, :content-type
and :content
all be part of the same map?
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 commentalas 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")))
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.
oh thats interesting, it does have multiple content types.
this seems relevant based n the name https://ring-clojure.github.io/ring/ring.middleware.multipart-params.html
That would be for your server handling a multipart params request, not for posting one though.
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.
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
@zackteo So you have (apply map + cols)
?
yeap
What do want the result to be?
but (apply map +
())` will return a function
in most cases I will have (apply map + [[1 2 3] [4 5 6] [7 8 9]])
something like that
Depending on what result you want back, maybe (apply map + (or (seq cols) [[]]))
?
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:
Unless there is some obvious, neutral result for the “sums of no columns”, you should check cols
before trying to apply map +
I think.
I see I see!
How might I want to check this then? with an if
?
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”