beginners

Getting started with Clojure/ClojureScript? Welcome! Also try: https://ask.clojure.org. Check out resources at https://gist.github.com/yogthos/be323be0361c589570a6da4ccc85f58f.
grazfather 2021-02-22T00:51:53.110800Z

I know πŸ™‚

Cj Pangilinan 2021-02-22T02:21:54.111700Z

Is there a difference between update and assoc function? It seems to me they both have the same result.

dpsutton 2021-02-22T02:24:01.112400Z

Their doc strings should indicate how they differ. Do you know how to get those?

Cj Pangilinan 2021-02-22T02:26:02.112900Z

i use (doc f) while in repl

dpsutton 2021-02-22T02:27:16.114100Z

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

Cj Pangilinan 2021-02-22T02:36:56.115800Z

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.

seancorfield 2021-02-22T02:46:48.116800Z

@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=>

πŸ‘ 1
seancorfield 2021-02-22T02:47:50.118Z

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.

Cj Pangilinan 2021-02-22T04:21:59.118800Z

oh yeah. i got the same error about arity. but i change it to #(str %).

sb 2021-02-22T04:35:14.121200Z

Yes that is interesting. I just tried out β€˜(def -str #(str %))’ and β€˜(update [] 0 #(-str \a))’ and works.. ofc that isnt good idea

jumar 2021-02-22T04:35:46.121400Z

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?

seancorfield 2021-02-22T04:57:07.121700Z

@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=>

seancorfield 2021-02-22T04:58:55.123100Z

@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.

πŸ‘ 1
seancorfield 2021-02-22T05:00:30.123300Z

user=> (update [] 0 #(str %))
[""]
user=> (update [] 0 str)
[""]
user=>

Rowan Barnard 2021-02-22T05:43:05.125300Z

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?

bhaim123 2021-02-22T05:44:16.125900Z

@alexmiller the chapter in Clojure Applied is Futures and Promises*?*

alexmiller 2021-02-22T05:58:45.126100Z

I don't think that's what it's called, but it's chapter 5

seancorfield 2021-02-22T06:06:33.126900Z

@flyingpython If you're just working with one project -- one folder -- then you can pretty much ignore the workspace stuff.

seancorfield 2021-02-22T06:08:19.129100Z

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.

sb 2021-02-22T06:10:19.130300Z

I tried and worked. Strange. On mobile Replete REPL.

Rowan Barnard 2021-02-22T06:10:36.130800Z

Ah OK thank you very much for the info Sean πŸ˜‰

sb 2021-02-22T06:10:50.130900Z

bhaim123 2021-02-22T06:14:16.131100Z

Many thanks πŸ™‚

seancorfield 2021-02-22T06:14:52.131300Z

That's ClojureScript -- it may well behave differently than Clojure.

πŸ‘ 1
seancorfield 2021-02-22T06:15:11.131700Z

(but Clojure on the JVM should be considered the "reference")

sb 2021-02-22T06:15:16.132Z

Yes, I see.

seancorfield 2021-02-22T06:15:58.132300Z

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?

seancorfield 2021-02-22T06:18:10.132500Z

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".

seancorfield 2021-02-22T06:18:58.133Z

(! 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=> 

πŸ‘ 1
sb 2021-02-22T06:19:25.134Z

Interesting, I dont know that. Thanks the info. I check these things deeply later today

1
seancorfield 2021-02-22T06:19:58.134500Z

Unfortunately, there are lots of small differences between Clojure and cljs (but nowhere near as many now as there used to be!).

πŸ‘ 1
ajithkumar 2021-02-22T07:20:24.137700Z

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!

ghadi 2021-02-22T13:55:58.157900Z

I wouldn't get too invested in making functions private

ghadi 2021-02-22T13:56:57.158100Z

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

πŸ‘ 1
ghadi 2021-02-22T13:57:09.158300Z

this is only a convention, though

2021-02-22T07:22:19.138500Z

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] ...)

sb 2021-02-22T07:23:01.139900Z

https://clojuredocs.org/clojure.core/defn- public or non-public function, check the example

πŸ‘ 1
ajithkumar 2021-02-22T07:23:29.140800Z

thanks

2021-02-22T07:23:37.141100Z

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.

ajithkumar 2021-02-22T07:25:06.141500Z

thanks for the info @andy.fingerhut

borkdude 2021-02-22T07:59:57.142200Z

Ask in #lsp, I’m by no means an expert

Marcus 2021-02-22T08:20:05.143400Z

Using core.async channels. Do you know if there are any limitations on the data size per message?

2021-02-22T08:38:15.143900Z

It as all just pointer sized

Marcus 2021-02-22T09:34:30.144300Z

ok thanks

sb 2021-02-22T11:26:33.147900Z

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

alexmiller 2021-02-22T13:05:45.150Z

Your (map inc data) benchmark is not doing the work due to laziness (wrap a doall around that or something)

sb 2021-02-22T13:06:20.150200Z

Ok thanks! I check it

alexmiller 2021-02-22T13:09:10.151300Z

Same for r/map I think - you have to reduce the reducible collection to actually do the work

sb 2021-02-22T13:15:24.152100Z

Yes, maybe the map inc isn’t a best test. I re-write later to different task.

alexmiller 2021-02-22T13:17:39.153200Z

My point is that these transducers are fast, you’re just not getting the correct slow times for sequences

πŸ‘ 1
sb 2021-02-22T13:24:03.153400Z

Ok, I test it. Thank you!

ericdallo 2021-02-22T14:16:13.160900Z

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 πŸ˜…

2021-02-22T14:21:45.161300Z

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).

ericdallo 2021-02-22T14:26:29.161500Z

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

2021-02-22T14:29:12.161700Z

Sorry, I gave you the answer for the easy part, not the harder part πŸ™‚

ericdallo 2021-02-22T14:31:28.161900Z

Np, any help is welcome πŸ˜„ I'm having a hard time with that function haha

borkdude 2021-02-22T14:35:58.162100Z

@ericdallo what about something like:

String myName = "domanokz";
String newName = myName.substring(0,4)+'x'+myName.substring(5);
but then in clojure?

borkdude 2021-02-22T14:36:40.162300Z

you can first split the original string and then update only the relevant lines

borkdude 2021-02-22T14:36:52.162500Z

and then concat them together again with the newlines

ericdallo 2021-02-22T14:40:53.162700Z

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}})

borkdude 2021-02-22T14:41:48.163Z

Are you trying to do deltas for clojure-lsp?

ericdallo 2021-02-22T14:42:04.163300Z

Yep, incremental text change hahaha

Henri Schmidt 2021-02-22T14:45:00.163900Z

working on htis one

Henri Schmidt 2021-02-22T14:45:03.164100Z

are lines indexed by 1?

ericdallo 2021-02-22T14:46:01.164300Z

Yeah, the line and column start with 1

Henri Schmidt 2021-02-22T14:47:39.164500Z

what if the middle contains more than one line?

Henri Schmidt 2021-02-22T14:47:51.164700Z

how is the replacement done then?

ericdallo 2021-02-22T14:48:41.164900Z

I don't get it, it should behave like a block of code change, you remove a block of code and replace by another

Henri Schmidt 2021-02-22T14:49:04.165100Z

ahh okay i see

borkdude 2021-02-22T14:49:05.165300Z

but the start and end line + cols always match the replacing input length?

ericdallo 2021-02-22T14:49:37.165500Z

oh, no, it matches the old-text

borkdude 2021-02-22T14:50:30.165700Z

so maybe an algorithm: cut out the original delta and then concat the new input in between

ericdallo 2021-02-22T14:51:45.165900Z

yes, that was @andy.fingerhut suggestion, I'm trying to find the start and end idx so that would work

borkdude 2021-02-22T14:52:35.166100Z

right

Henri Schmidt 2021-02-22T14:57:36.166500Z

@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}})

ericdallo 2021-02-22T14:58:50.167Z

Damm, that was fast! Thank you!! Let me try

Henri Schmidt 2021-02-22T14:59:20.167200Z

It might have an off-by-one error on the columns, but I'm pretty sure it works

Henri Schmidt 2021-02-22T14:59:50.167400Z

partition-by is really the key function here that does all the hard work

Henri Schmidt 2021-02-22T15:00:38.167600Z

yes there is an error*

Henri Schmidt 2021-02-22T15:00:40.167800Z

let me fix

Henri Schmidt 2021-02-22T15:01:13.168100Z

(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}})

borkdude 2021-02-23T10:28:25.233700Z

I'll take a look

borkdude 2021-02-23T11:13:49.234600Z

@ericdallo

(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"

borkdude 2021-02-23T11:15:05.235Z

Also notes that this now works:

(prn (replace-str "(+ 1 1)\n" 0 3 0 3 "1 ")) ;;=> "(+ 1 1 1)\n"

ericdallo 2021-02-23T12:20:16.238300Z

Thanks! I'll try

Henri Schmidt 2021-02-22T15:01:17.168300Z

Updated version

ericdallo 2021-02-22T15:01:41.168400Z

Really cool!

borkdude 2021-02-22T15:01:48.168600Z

@ericdallo

(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

borkdude 2021-02-22T15:01:51.168800Z

Maybe something like this

ghadi 2021-02-22T15:01:56.169Z

fyi always require namespace clojure.string explicitly

πŸ‘ 3
borkdude 2021-02-22T15:02:32.169300Z

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

Henri Schmidt 2021-02-22T15:02:35.169500Z

yup just wanted to illustrate

Henri Schmidt 2021-02-22T15:03:19.170100Z

@ericdallo you maintain emacs-LSP?

Henri Schmidt 2021-02-22T15:03:31.170400Z

That's awesome tooling I thank you for that

borkdude 2021-02-22T15:03:36.170600Z

You can optimize this by searching for the next end line and end col by just continuing the loop

ericdallo 2021-02-22T15:03:45.171Z

clojure-lsp, Yes #lsp πŸ˜„

Henri Schmidt 2021-02-22T15:04:01.171500Z

Even though I use CIDER 😁

1
ericdallo 2021-02-22T15:04:17.171800Z

I use both πŸ˜›

borkdude 2021-02-22T15:04:18.171900Z

You should take into account Windows newline delimiters, which can be two characters

Henri Schmidt 2021-02-22T15:04:31.172400Z

that's high level, i don't code too much anymore

ericdallo 2021-02-22T15:04:44.172500Z

Thank you! I'll test it with some weird inputs and check if it's working, thank you very much for the help πŸ˜„

πŸ‘ 1
Henri Schmidt 2021-02-22T15:05:02.173100Z

will have to try when i have the chance

❀️ 1
ericdallo 2021-02-22T15:05:03.173200Z

Yes, this looks a alternative indeed to https://clojurians.slack.com/archives/C053AK3F9/p1614006073168100

borkdude 2021-02-22T15:05:14.173500Z

The above will probably a bit more performant

ericdallo 2021-02-22T15:05:18.173700Z

thanks borkdude, I'll try if the above solution don't work

ericdallo 2021-02-22T15:05:22.173900Z

yes, it seems to me too

borkdude 2021-02-22T15:06:13.174200Z

Beware of Windows though, should require careful testing

ericdallo 2021-02-22T15:06:25.174400Z

It's working but cropping the first col

borkdude 2021-02-22T15:06:26.174600Z

Always use the file system specific delimiter

ericdallo 2021-02-22T15:06:45.174800Z

(update-text "some \ncool\n\n text" "boring" {:start {:line 2
                                                      :column 1}
                                              :end {:line 2
                                                    :column 4}})
=> "some boring\n text"

ericdallo 2021-02-22T15:06:58.175Z

it should print "some \nboring\n text"

ericdallo 2021-02-22T15:08:09.175300Z

Yes, had issues with that some time ago in clojure-lsp crawler πŸ˜›

Henri Schmidt 2021-02-22T15:09:31.175600Z

remove (dec start-column)

Henri Schmidt 2021-02-22T15:09:42.175800Z

and replace with start-column

ericdallo 2021-02-22T15:11:26.176Z

almost there: "some cboring\n text"

Henri Schmidt 2021-02-22T15:12:26.176200Z

wait what was wrong with the dec?

ericdallo 2021-02-22T15:12:50.176400Z

it was removing the \n

ericdallo 2021-02-22T15:13:16.176600Z

"some boring\n text" should be "some \nboring\n text"

Henri Schmidt 2021-02-22T15:13:23.176800Z

yeah so stick a newline in between prefix and middle-pre

Henri Schmidt 2021-02-22T15:13:28.177Z

go back to dec

Henri Schmidt 2021-02-22T15:13:43.177200Z

[prefix "\n" middle-pre update-text middle-suff "\n" suffix]

Henri Schmidt 2021-02-22T15:13:45.177400Z

sorry i closed my repl

ericdallo 2021-02-22T15:14:09.177600Z

it works πŸ˜„

πŸ‘ 1
Henri Schmidt 2021-02-22T15:14:16.177900Z

sweet

ericdallo 2021-02-22T15:14:24.178100Z

will try multiple inputs, thanks @henrischmidt73 ❀️

Henri Schmidt 2021-02-22T15:14:32.178300Z

no problem!

borkdude 2021-02-22T15:18:00.178500Z

(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"

borkdude 2021-02-22T15:19:08.178900Z

You can optimize offset by collapsing the finding of the first and second offset into one loop

borkdude 2021-02-22T15:21:02.179100Z

Just another possibility ;)

borkdude 2021-02-22T15:22:36.179300Z

@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

❀️ 1
Henri Schmidt 2021-02-22T15:22:39.179600Z

@borkdude nice solution

Henri Schmidt 2021-02-22T15:23:00.179900Z

definitely more production than mine, i'm fond of code golfing myself

borkdude 2021-02-22T15:24:24.180200Z

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

πŸ‘ 1
ericdallo 2021-02-22T15:24:47.180500Z

Nice, it looks easier to understand indeed, I wonder about the performance, if it's relevant

borkdude 2021-02-22T15:25:11.180800Z

Weren't you starting with this because of performance in the first place? ;)

1
borkdude 2021-02-22T15:25:32.181Z

I would say anything that is executed on each keystroke should be as performant as possible

ericdallo 2021-02-22T15:26:27.181300Z

yes, agree

Henri Schmidt 2021-02-22T15:27:39.181500Z

if you're really after performance you should just write a loop with StringBuffer

borkdude 2021-02-22T15:29:09.181700Z

I could have sworn I had StringBuffer available in babashka, but that is java.lang.StringBuilder I see now...

borkdude 2021-02-22T15:29:51.181900Z

Ah, StringBuilder is more efficient I read now

borkdude 2021-02-22T15:30:12.182100Z

but not thread-safe, well, that doesn't matter here

ericdallo 2021-02-22T15:36:54.182300Z

@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}})

ericdallo 2021-02-22T15:37:04.182500Z

I'm getting NPE πŸ˜”

borkdude 2021-02-22T15:38:55.182700Z

@ericdallo My version returns:

(prn (replace-str "(+ 1 1)" 0 5 0 6 "2" ))
;;=> "(+ 1 2)"

borkdude 2021-02-22T15:39:03.182900Z

(it's 0-based)

ericdallo 2021-02-22T15:39:42.183100Z

let me try

ericdallo 2021-02-22T15:44:21.183400Z

Yeah it works perfectly @borkdude

ericdallo 2021-02-22T15:44:39.183600Z

thank you both, I'll do some tests yet and later a performance check

borkdude 2021-02-22T15:45:50.183900Z

@ericdallo on Windows this line (+ (count (str buf)) col line) should probably by incremented by one, if the newline is represented by 2 characters

borkdude 2021-02-22T15:46:08.184100Z

no, (+ ... (* 2 line)) even

borkdude 2021-02-22T15:46:24.184300Z

I am adding one offset for each line due to the newline char

borkdude 2021-02-22T15:47:19.184600Z

maybe LSP already does some normalization on the newlines? don't know

borkdude 2021-02-22T15:47:20.184800Z

probably not

ericdallo 2021-02-22T15:48:26.185Z

wait, windows has URI issues related to double \ and etc, why this is related with replacing a text?

borkdude 2021-02-22T15:49:05.185200Z

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

borkdude 2021-02-22T15:49:19.185400Z

but a newline is represented by 2 characters on Windows

ericdallo 2021-02-22T15:49:43.185600Z

oh yeah, the \r char... damm

borkdude 2021-02-22T15:49:44.185800Z

you can also just split literally on \n which probably also works

borkdude 2021-02-22T15:49:51.186Z

ignoring the \r

borkdude 2021-02-22T15:50:02.186200Z

yeah, I think that works

borkdude 2021-02-22T15:50:26.186400Z

so you only have to change the str/split-lines part to split on \n

ericdallo 2021-02-22T15:50:32.186600Z

But your script is already using string/split-lines that take cares of that, right?

ericdallo 2021-02-22T15:50:38.186800Z

hum, got it

ericdallo 2021-02-22T15:50:58.187Z

on windows it splits by \r\n , got it

borkdude 2021-02-22T15:50:59.187200Z

so foo\r\nbar will be foo\r and bar

borkdude 2021-02-22T15:51:25.187400Z

this will then calculate the right offset on each OS

borkdude 2021-02-22T15:51:59.187600Z

so in this case: totally ignore the file system specific newline :)

ericdallo 2021-02-22T15:52:05.187800Z

changed, I'll create a test for that case as well

ericdallo 2021-02-22T15:52:12.188Z

it makes sense

ericdallo 2021-02-22T15:52:20.188200Z

thanks!

borkdude 2021-02-22T16:05:45.188400Z

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)"

borkdude 2021-02-22T16:06:38.188600Z

It collapses the finding of both offsets into one loop

1
ericdallo 2021-02-22T16:11:09.189Z

Using that works too πŸ™‚ Thank you @borkdude will follow with that

borkdude 2021-02-22T16:34:58.189200Z

@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)"

ericdallo 2021-02-22T16:35:46.189500Z

Nice, mine is just missing the StringBuilder removal so

Pablo 2021-02-22T17:01:20.196800Z

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!

grazfather 2021-02-22T17:26:09.198500Z

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)]

grazfather 2021-02-22T17:26:38.198900Z

I could do a when and then another let to bind min-temp but that seems ugly

borkdude 2021-02-22T17:28:12.199600Z

@grazfather You can do min-temp (when outside-temp (minimum-legal-temp ...))

borkdude 2021-02-22T17:29:26.200400Z

or use a default temp outside-temp (or (get-man...) -1)

borkdude 2021-02-22T17:30:01.201Z

You can also use some-> and some->> for short-circuiting on nil

grazfather 2021-02-22T17:30:13.201300Z

ok cool, and is when outside-temp better thn when (not (nil? outside-temp?

borkdude 2021-02-22T17:30:50.201700Z

assuming that outside-temp is a number or nil, you can just use (when outside-temp ..)

borkdude 2021-02-22T17:33:38.202500Z

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

grazfather 2021-02-22T18:35:06.203200Z

ah, I did not know about when-let

2021-02-22T19:07:28.204600Z

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)

Henri Schmidt 2021-02-22T19:09:48.205300Z

@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

Henri Schmidt 2021-02-22T19:10:13.205900Z

if-let* binds forms but short circuits to the else branch if any of the bindings are nil.

william 2021-02-22T19:10:25.206200Z

@david.daigml that happens because (conj ids (:id jdoc)) is just returning the new seq, but never updating in place your ids binding.

Pablo 2021-02-22T19:10:27.206500Z

Remember that things are inmutable on Clojure

Henri Schmidt 2021-02-22T19:11:08.207300Z

It's very useful in general and I think there was some debate whether it should be included in the clojure.core

Pablo 2021-02-22T19:11:23.207400Z

So conj returns a new collection

william 2021-02-22T19:11:43.207800Z

you're probably looking for something along the line of:

(map :id [{:id "1" :data "x"} {:id "2" :data "y"}])

Henri Schmidt 2021-02-22T19:11:50.208100Z

In your case you would simply replace your let with if-let*

2021-02-22T19:17:55.208200Z

thanks guys, yea that’s definitely the problem