I know π
Is there a difference between update and assoc function? It seems to me they both have the same result.
Their doc strings should indicate how they differ. Do you know how to get those?
i use (doc f) while in repl
Perfect yeah. So (doc assoc) should say it takes a key and a value. Doc update should say it takes a key and a function
i see. thanks. if you use a function in assoc as in (assoc [] 0 #(str \a)), the value will be the function itself and not the result of executing that function. in update, it will replace the current value with the result of executing the function.
@cjpangilinan There's a subtlety there: #(str \a)
is a function of no arguments that returns "a"
but update
requires a function of one or more arguments:
user=> (assoc [] 0 #(str \a))
[#object[user$eval1$fn__2 0x74a9c4b0 "user$eval1$fn__2@74a9c4b0"]]
user=> (update [] 0 #(str \a))
Execution error (ArityException) at user/eval5 (REPL:1).
Wrong number of args (1) passed to: user/eval5/fn--6
user=> (update [] 0 (fn [_] (str \a)))
["a"]
user=>
The (fn [_] (str \a))
function accepts one argument and ignores it -- _
is idiomatic naming in Clojure for an argument that a function does not use but otherwise has no special meaning.
oh yeah. i got the same error about arity. but i change it to #(str %).
Yes that is interesting. I just tried out β(def -str #(str %))β and β(update [] 0 #(-str \a))β and works.. ofc that isnt good idea
I checked the beginning of your video and I'm wondering how well Cider plays with LSP. E.g. the autocomplete looks something that can potentially conflict if you use both things at the same time. How is that handled?
@sb I don't think you tried exactly that -- it does not work, for exactly the same reason as I observed: #(-str \a)
is still a function of no arguments and is not compatible with update
:
user=> (def -str #(str %))
#'user/-str
user=> (update [] 0 #(-str \a))
Execution error (ArityException) at user/eval151 (REPL:1).
Wrong number of args (1) passed to: user/eval151/fn--152
user=>
@cjpangilinan #(str %)
is exactly the same as just str
on its own. Well, not quite exactly, since str
is variadic -- but both str
and #(str %)
can be called with a single argument.
user=> (update [] 0 #(str %))
[""]
user=> (update [] 0 str)
[""]
user=>
Hi guys, I just got set up running Calva and Clover in VS Code, just wondering about this workspace concept in VS Code, do I need to bother with that at all or can I just open my Clojure project folder and get cracking?
@alexmiller the chapter in Clojure Applied is Futures and Promises*?*
I don't think that's what it's called, but it's chapter 5
@flyingpython If you're just working with one project -- one folder -- then you can pretty much ignore the workspace stuff.
My main use for it is to have a workspace for "work" (with two main projects open) and a workspace for "open source" (with half a dozen active GitHub projects in it). With the former I use Jira and BitBucket, with the latter I use GitHub. The nice thing about the "workspace" idea is that I can switch contexts very easily and VS Code remembers all the files I had open the last time I was using each workspace.
I tried and worked. Strange. On mobile Replete REPL.
Ah OK thank you very much for the info Sean π
Many thanks π
That's ClojureScript -- it may well behave differently than Clojure.
(but Clojure on the JVM should be considered the "reference")
Yes, I see.
I would consider it a bug in cljs to allow that invocation, but I suspect it's because JS itself doesn't check function arities strictly?
It should also be noted that cljs doesn't have the character data type like Clojure does: in Clojure \a
is a single character, in cljs \a
is just a single character string "a"
.
(! 733)-> clj -Sdeps '{:deps {org.clojure/clojurescript {:mvn/version "RELEASE"}}}' -M -m cljs.main
ClojureScript 1.10.773
cljs.user=> \a
"a"
cljs.user=> "a"
"a"
cljs.user=> (= \a "a")
true
cljs.user=> ^D
Sun Feb 21 22:18:35
(sean)-(jobs:0)-(~/clojure)
(! 734)-> clj
Clojure 1.10.2
user=> \a
\a
user=> "a"
"a"
user=> (= \a "a")
false
user=>
Interesting, I dont know that. Thanks the info. I check these things deeply later today
Unfortunately, there are lots of small differences between Clojure and cljs (but nowhere near as many now as there used to be!).
Hey guys, it might be a noob question! I want to know the difference between defn
& defn-
also suggest where to use, where not to use!
Thanks!
I wouldn't get too invested in making functions private
a lot of experienced Clojurists don't make too much use of this. You can also hide things in an internal
or impl
namespace if you really don't want people calling internal functions
this is only a convention, though
defn-
is the same as defn
, except that the function defined is private to the namespace, the same as if you had written (defn ^:private foo [x] ...)
https://clojuredocs.org/clojure.core/defn- public or non-public function, check the example
thanks
Such functions cannot be called from other namespaces, without a special trick. Some Clojure developers like to use private functions to indicate that callers should not call that function as part of the public API of your code. Others prefer to do that via documentation instead, and/or to create a namespace specifically for all public API functions, and leave implementation detail functions for separate namespaces.
thanks for the info @andy.fingerhut
Ask in #lsp, Iβm by no means an expert
Using core.async channels. Do you know if there are any limitations on the data size per message?
It as all just pointer sized
ok thanks
Is that possible to create faster transducers? I compared few versions, but Im not sure I understand fully the topic. Maybe I did these versions in wrong way https://gist.github.com/damesek/e7023c0bc434a42952128b2f33c702dc? somebody could advise how could I create to create a faster/ better transducer? Thanks
Your (map inc data) benchmark is not doing the work due to laziness (wrap a doall around that or something)
Ok thanks! I check it
Same for r/map I think - you have to reduce the reducible collection to actually do the work
Yes, maybe the map inc
isnβt a best test. I re-write later to different task.
My point is that these transducers are fast, youβre just not getting the correct slow times for sequences
Ok, I test it. Thank you!
Hello! Can anyone help me find a implementation for a function that should replace a text with a new text at specific range?
(update-text "some \ncool\n\n text" "boring" {:start {:line 2
:column 1}
:end {:line 2
:column 4}})
=> "some \nboring\n\n text"
The issue seems to be the line/col thing.
Not sure it's a beginner question but I could not figure it out how to achive that and I thought this channel could help π
I don't have a function handy to give you that I know already does this, but if you can take a string s
, and any number of {:line <x> :column <y>}
maps, and calculate for each one the 0-based index in s
where that line and column is, and those indices are start-idx
and end-idx
, then I think all you need to do after that is (str (subs s 0 start-idx) replacement-string (subs s end-idx))
(perhaps with some off-by-one mistakes there).
Thank you for the response!
Hum, I think I got your point, it makes sense, I just need to find the start-idx
/ end-idx
like you said. Probably can get that with some for loops
Sorry, I gave you the answer for the easy part, not the harder part π
Np, any help is welcome π I'm having a hard time with that function haha
@ericdallo what about something like:
String myName = "domanokz";
String newName = myName.substring(0,4)+'x'+myName.substring(5);
but then in clojure?you can first split the original string and then update only the relevant lines
and then concat them together again with the newlines
but what about changes that add more lines? for example
(update-text "some \ncool\n text" "boring\n text" {:start {:line 2
:column 1}
:end {:line 3
:column 6}})
Are you trying to do deltas for clojure-lsp?
Yep, incremental text change hahaha
TextDocumentContentChangeEvent
working on htis one
are lines indexed by 1?
Yeah, the line and column start with 1
what if the middle contains more than one line?
how is the replacement done then?
I don't get it, it should behave like a block of code change, you remove a block of code and replace by another
ahh okay i see
but the start and end line + cols always match the replacing input length?
oh, no, it matches the old-text
so maybe an algorithm: cut out the original delta and then concat the new input in between
yes, that was @andy.fingerhut suggestion, I'm trying to find the start and end idx so that would work
right
@ericdallo try this:
(defn update-text [text
update-text
{{start-line :line start-column :column} :start
{end-line :line end-column :column} :end}]
(let [[prefix middle suffix]
(->> (clojure.string/split-lines text)
(map-indexed #(vector %1 %2))
(partition-by #(or (= (dec start-line) (first %))
(= (dec end-line) (first %))))
(map #(map second %)))
middle-pre (subs (first middle) 0 (dec start-column))
middle-suff (subs (last middle) end-column)
prefix (clojure.string/join "\n" prefix)
suffix (clojure.string/join "\n" suffix)]
(-> [prefix middle-pre update-text middle-suff suffix]
(clojure.string/join))))
(update-text "some \ncool\n text" "boring\n text"
{:start {:line 2 :column 1} :end {:line 3 :column 4}})
Damm, that was fast! Thank you!! Let me try
It might have an off-by-one error on the columns, but I'm pretty sure it works
partition-by
is really the key function here that does all the hard work
yes there is an error*
let me fix
(defn update-text [text
update-text
{{start-line :line start-column :column} :start
{end-line :line end-column :column} :end}]
(let [[prefix middle suffix]
(->> (clojure.string/split-lines text)
(map-indexed #(vector %1 %2))
(partition-by #(and (<= (dec start-line) (first %))
(>= (dec end-line) (first %))))
(map #(map second %)))
middle-pre (subs (first middle) 0 (dec start-column))
middle-suff (subs (last middle) end-column)
prefix (clojure.string/join "\n" prefix)
suffix (clojure.string/join "\n" suffix)]
(-> [prefix middle-pre update-text middle-suff suffix]
(clojure.string/join))))
(update-text "some \ncool\n text" "boring\n text"
{:start {:line 2 :column 1} :end {:line 3 :column 4}})
I'll take a look
(require '[clojure.string :as str])
(defn offsets [lines line col end-line end-col]
(loop [lines (seq lines)
offset 0
idx 0]
(if (or (not lines)
(= line idx))
[(+ offset col line)
(loop [lines lines
offset offset
idx idx]
(if (or (not lines)
(= end-line idx))
(+ offset end-col end-line)
(recur (next lines)
(+ offset (count (first lines)))
(inc idx))))]
(recur (next lines)
(+ offset (count (first lines)))
(inc idx)))))
(defn replace-str [original line col end-line end-col replacement]
(let [lines (str/split original #"\n") ;; don't use OS specific line delimiter!
[start-offset end-offset] (offsets lines line col end-line end-col)
[prefix suffix] [(subs original 0 start-offset)
(subs original end-offset (count original))]]
(str/join [prefix replacement suffix])))
(prn (replace-str "xxxxxxx\n123\n123" 1 1 2 1 "dude")) ;;=> "xxxxxxx\n1dude23"
(prn (replace-str "(+ 1 1)" 0 5 0 6 "2" )) ;;=> "(+ 1 2)"
(prn (replace-str "(+ 1 1)\n" 1 0 1 0 "\n")) ;;=> "(+ 1 1)\n\n"
Also notes that this now works:
(prn (replace-str "(+ 1 1)\n" 0 3 0 3 "1 ")) ;;=> "(+ 1 1 1)\n"
Thanks! I'll try
Updated version
Really cool!
(require '[clojure.string :as str])
(defn offset [lines line col]
(loop [lines (seq lines)
buf (java.lang.StringBuffer.)
idx 0]
(if lines
(if (= line idx)
(+ (count (str buf)) col line)
(recur (next lines)
(.append buf (first lines))
(inc idx)))
offset)))
(def string "foobar\nbar\nbaz")
(def os (offset (str/split-lines "foobar\nbar\nbaz") 1 1))
(prn (.charAt string os)) ;;=> \a
Maybe something like this
fyi always require namespace clojure.string explicitly
Then you can use these offsets, to split the original string into three parts and create the new one from the first and last part + the new one in the middle
yup just wanted to illustrate
@ericdallo you maintain emacs-LSP?
That's awesome tooling I thank you for that
You can optimize this by searching for the next end line and end col by just continuing the loop
clojure-lsp, Yes #lsp π
Even though I use CIDER π
I use both π
You should take into account Windows newline delimiters, which can be two characters
that's high level, i don't code too much anymore
Thank you! I'll test it with some weird inputs and check if it's working, thank you very much for the help π
will have to try when i have the chance
Yes, this looks a alternative indeed to https://clojurians.slack.com/archives/C053AK3F9/p1614006073168100
The above will probably a bit more performant
thanks borkdude, I'll try if the above solution don't work
yes, it seems to me too
Beware of Windows though, should require careful testing
It's working but cropping the first col
Always use the file system specific delimiter
(update-text "some \ncool\n\n text" "boring" {:start {:line 2
:column 1}
:end {:line 2
:column 4}})
=> "some boring\n text"
it should print "some \nboring\n text"
Yes, had issues with that some time ago in clojure-lsp crawler π
remove (dec start-column)
and replace with start-column
almost there: "some cboring\n text"
wait what was wrong with the dec?
it was removing the \n
"some boring\n text"
should be "some \nboring\n text"
yeah so stick a newline in between prefix and middle-pre
go back to dec
[prefix "\n" middle-pre update-text middle-suff "\n" suffix]
sorry i closed my repl
it works π
sweet
will try multiple inputs, thanks @henrischmidt73 β€οΈ
no problem!
(require '[clojure.string :as str])
(defn offset [lines line col]
(loop [lines (seq lines)
buf (java.lang.StringBuffer.)
idx 0]
(if lines
(if (= line idx)
(+ (count (str buf)) col line)
(recur (next lines)
(.append buf (first lines))
(inc idx)))
offset)))
(def string "xxxxxxx\n123\n123")
(defn replace-str [original line col end-line end-col replacement]
(let [lines (str/split-lines string)
start-offset (offset lines line col)
end-offset (offset lines end-line end-col)
[prefix suffix] [(subs original 0 start-offset)
(subs original end-offset (count original))]]
(str/join [prefix replacement suffix])))
(prn (replace-str string 1 1 2 1 "dude")) ;;=> "xxxxxxx\n1dude23"
You can optimize offset by collapsing the finding of the first and second offset into one loop
Just another possibility ;)
@ericdallo I have similar code in the clj-kondo LSP server ;) https://github.com/clj-kondo/clj-kondo.lsp/blob/d9d29a5d4a7955daef0f5c196a2054f9c75b7609/server/src/clj_kondo/lsp_server/impl/server.clj#L137
@borkdude nice solution
definitely more production than mine, i'm fond of code golfing myself
with similar I mean, it doesn't do the incremental stuff, but I had to fix some stuff related to how it shows diagnostics which also required me to split the lines, etc
Nice, it looks easier to understand indeed, I wonder about the performance, if it's relevant
Weren't you starting with this because of performance in the first place? ;)
I would say anything that is executed on each keystroke should be as performant as possible
yes, agree
if you're really after performance you should just write a loop with StringBuffer
I could have sworn I had StringBuffer available in babashka, but that is java.lang.StringBuilder I see now...
Ah, StringBuilder is more efficient I read now
but not thread-safe, well, that doesn't matter here
@henrischmidt73 what about texts that don't contain new lines?
(update-text "(+ 1 1)" "2" {:start {:line 1 :column 6}
:end {:line 1 :column 7}})
I'm getting NPE π
@ericdallo My version returns:
(prn (replace-str "(+ 1 1)" 0 5 0 6 "2" ))
;;=> "(+ 1 2)"
(it's 0-based)
let me try
Yeah it works perfectly @borkdude
thank you both, I'll do some tests yet and later a performance check
@ericdallo on Windows this line (+ (count (str buf)) col line)
should probably by incremented by one, if the newline is represented by 2 characters
no, (+ ... (* 2 line))
even
I am adding one offset for each line due to the newline char
maybe LSP already does some normalization on the newlines? don't know
probably not
wait, windows has URI issues related to double \
and etc, why this is related with replacing a text?
because we are splitting on new line and then trying to find the offset by calculating the length of each line + one for the newline
but a newline is represented by 2 characters on Windows
oh yeah, the \r char... damm
you can also just split literally on \n
which probably also works
ignoring the \r
yeah, I think that works
so you only have to change the str/split-lines
part to split on \n
But your script is already using string/split-lines
that take cares of that, right?
hum, got it
on windows it splits by \r\n
, got it
so foo\r\nbar
will be foo\r
and bar
this will then calculate the right offset on each OS
so in this case: totally ignore the file system specific newline :)
changed, I'll create a test for that case as well
it makes sense
thanks!
Here is the more optimized version:
(require '[clojure.string :as str])
(defn offsets [lines line col end-line end-col]
(loop [lines (seq lines)
buf (java.lang.StringBuilder.)
idx 0]
(when lines
(if (= line idx)
[(+ (count (str buf)) col line)
(loop [lines lines
buf buf
idx idx]
(when lines
(if (= end-line idx)
(+ (count (str buf)) end-col end-line)
(recur (next lines)
(.append buf (first lines))
(inc idx)))))]
(recur (next lines)
(.append buf (first lines))
(inc idx))))))
(defn replace-str [original line col end-line end-col replacement]
(let [lines (str/split-lines original)
[start-offset end-offset] (offsets lines line col end-line end-col)
[prefix suffix] [(subs original 0 start-offset)
(subs original end-offset (count original))]]
(str/join [prefix replacement suffix])))
(prn (replace-str "xxxxxxx\n123\n123" 1 1 2 1 "dude")) ;;=> "xxxxxxx\n1dude23"
(prn (replace-str "(+ 1 1)" 0 5 0 6 "2" )) ;;=> "(+ 1 2)"
It collapses the finding of both offsets into one loop
Using that works too π Thank you @borkdude will follow with that
@ericdallo 2 improvements: - don't use StringBuilder, it isn't necessary - don't use OS specific newline delimiter
(require '[clojure.string :as str])
(defn offsets [lines line col end-line end-col]
(loop [lines (seq lines)
offset 0
idx 0]
(when lines
(if (= line idx)
[(+ offset col line)
(loop [lines lines
offset offset
idx idx]
(when lines
(if (= end-line idx)
(+ offset end-col end-line)
(recur (next lines)
(+ offset (count (first lines)))
(inc idx)))))]
(recur (next lines)
(+ offset (count (first lines)))
(inc idx))))))
(defn replace-str [original line col end-line end-col replacement]
(let [lines (str/split original #"\n") ;; don't use OS specific line delimiter!
[start-offset end-offset] (offsets lines line col end-line end-col)
[prefix suffix] [(subs original 0 start-offset)
(subs original end-offset (count original))]]
(str/join [prefix replacement suffix])))
(prn (replace-str "xxxxxxx\n123\n123" 1 1 2 1 "dude")) ;;=> "xxxxxxx\n1dude23"
(prn (replace-str "(+ 1 1)" 0 5 0 6 "2" )) ;;=> "(+ 1 2)"
Nice, mine is just missing the StringBuilder removal so
Hello everyone
Iβm creating a RESTful API by using metosin/compojure-api
(specifically compojure.api.sweet/api
), but I have a little problem. I want to censor some keys in every response.
I added a middleware by using the extra middleware vector (`:middleware` key), but it is not applied on the default exceptions handlers. I saw the code of [`compojure.api.middleware/api-middleware`](https://github.com/metosin/compojure-api/blob/5c88f32fe56cdb6fcdb3cc506bb956943fcd8c17/src/compojure/api/middleware.clj#L213) and I found that middlewares are wrapped by these exceptions handlers.
Could you give me a suggestion of how can I define a middleware that applies on every response? I tried to apply it after api
, but it returns an InputStream
on the body.
Thanks!
I have a let
that includes an api call. Whatβs the βrightβ way to bind in a let that doesnβt mess up if one of the args (the api call response) is nil?
(let [inside-temp (-> (read-reg bus reg-temp)
(get 1)
bytes->val
reading->C)
outside-temp (get-manhattan-temp) ; can be nil
now (java.time.LocalDateTime/now)
min-temp (minimum-legal-temp now outside-temp)]
I could do a when
and then another let
to bind min-temp
but that seems ugly
@grazfather You can do min-temp (when outside-temp (minimum-legal-temp ...))
or use a default temp outside-temp (or (get-man...) -1)
You can also use some->
and some->>
for short-circuiting on nil
ok cool, and is when outside-temp
better thn when (not (nil? outside-temp
?
assuming that outside-temp
is a number or nil, you can just use (when outside-temp ..)
it might nice to short-circuit the whole thing with when-let
on get-manhattan-temp
so you don't do any work that's not necessary
ah, I did not know about when-let
what did i do wrong here? Can someone help? Iβm expecting ["1" "2"]
but only getting an empty array
(let [ids []]
(doseq [jdoc ({:id "1" :data "x"} {:id "2" :data "y"})]
(conj ids (:id jdoc)))
ids)
@grazfather I think you should take a look at if-let
but if that doesn't satisfy your use case, I recommend taking a look at the following macro: https://github.com/schmidt73/guidescan-web/blob/master/src/guidescan_web/utils.clj
if-let*
binds forms but short circuits to the else branch if any of the bindings are nil.
@david.daigml that happens because (conj ids (:id jdoc))
is just returning the new seq, but never updating in place your ids
binding.
Remember that things are inmutable on Clojure
It's very useful in general and I think there was some debate whether it should be included in the clojure.core
So conj
returns a new collection
you're probably looking for something along the line of:
(map :id [{:id "1" :data "x"} {:id "2" :data "y"}])
In your case you would simply replace your let
with if-let*
thanks guys, yea thatβs definitely the problem