Fork me on GitHub
#beginners
<
2022-08-13
>
JoshLemer04:08:57

I find that for me, the biggest barrier to productivity to Clojure is just the difficulty of editing it. I know the basics of structural editing and practice with slurp and barf and wrap/raise but yeah maybe I'm missing something. How would you go about making this change from top to bottom, what kind of usage of structural editing would you use if any (just moving the q binding into the upper if-let

(fn foo [a b c]
  (let [q (bar a b c)
         r (baz q)
         s (qux b r a)]
         (if s a b))) 



(fn foo [a b c]
  (if-let [q (bar a b c)]
    (let [r (baz q)
          s (qux b r a)]
      (if s a b))))

andy.fingerhut04:08:01

Caveat: I have 0 experience with structural editing. When I hear that it is creating problems or puzzles to solve, it makes me wonder whether people typically just temporarily disable structural editing and make such changes with more basic text editing facilities. Why solve a puzzle of your own creation if it slows you down even one millisecond?

👍 1
JoshLemer04:08:06

Well, structural editing or no, I just find it really daunting to try and rearrange clojure code 😞

seancorfield04:08:46

I'd probably add in (if-let []) then slurp, then drop down into the let binding and select right twice, cut, and then paste that into the if-let binding.

seancorfield04:08:42

Hmm, just tried it. Probably the easiest is to put the cursor before the r binding and "split sexp" then wrap with ( and type let, then just up cursor and back sexp to insert if-

JoshLemer04:08:59

Oh split is a nice one!

seancorfield04:08:04

In Calva, that's ctrl+shift+s, return (to indent), ctrl+alt+shift+p (to wrap), type let, space, ctrl+up, ctrl+up (now you're at the ( of the original let so right and type if-. Then tab to reindent.

JoshLemer04:08:46

you're on mac?

seancorfield04:08:48

I switched from Mac (after being an Apple customer for 30 years).

JoshLemer04:08:07

Hey Sean I heard you on the ClojureStream podcast that was a nice episode 🙂

seancorfield04:08:27

Oh, thanks. 😊 Jacek is a great host!

seancorfield04:08:12

He's had me on before -- a long time ago. I can't remember what the previous episode was about now...

seancorfield04:08:03

As for Paredit, I'll be honest: I am still learning it even after years of using it for Clojure work in various editors! 🙂

JoshLemer04:08:29

There's a lot of anxiety/hesitation like "oh I'm not even gonna try and change this expression, if I start pulling it apart I'll never get it back together again and it'll just be randomly arranged forms"

seancorfield04:08:17

ctrl+z (undo). Repeatedly 🙂

seancorfield05:08:11

You want randomly arranged forms? Play with "convolute sexp" a bit!

seancorfield05:08:33

I think I've used it successfully maybe twice ever...

JoshLemer05:08:30

neither the name nor the animation is helping me understand that

seancorfield05:08:33

Yeah, it's a weird one. It swaps an inner "function call" with an outer "function call" -- you can use it to lift a let and its bindings out around the enclosing form:

(if something
    (let [a 42]
      (+ a something))
    0)
with cursor just before the (+ ..) expression, convolutes to this:
(let [a 42]
      (if something
        (+ a something)
        0))

😲 1
seancorfield05:08:41

(it doesn't reindent afterward so you have to ctrl+up and tab to fix that)

skylize13:08:12

Also just at the beginning of my Paredit journey. The first thing I notice is that only slurp and barf can go a surprisingly long way, even if that is not as sexy as (and maybe slightly slower than) correctly using more advanced commands. With your example, starting inside the second ( before let (ignoring editor-specific commands, and assuming auto-closing brackets): • if-<space><end><barf forward> x4 • <end or ➡️><enter>(let<space>[<slurp forward> x8 To some extent you can abuse the formatted layout of code in place of remembering how to do structural movements. The first <end> in my example would be more accurately replaced by <move down forward sexp>, but the end of the line happens to also be inside that same down-forward form. So in this case it works fine.

teodorlu14:08:09

I gave your example a shot. I'm on #doom-emacs, so this may look weird. But I expect you can go for the same motions in any editor. D to kill the rest of the line, to get the let binding out. slurp to get both the bindings and the old let body where they should be J to join lines to clean up. I still don't consider myself good at structural editing, so please take this as inspiration, and not an answer 🙂

Chase17:08:13

Have you tried parinfer yet? I use a mix of that and more traditional structural editing (w/ vim-sexp & vim-sexp-mappings-for-regular-people). With parinfer you basically just shape, delete, add code however you want and all the parens and brackets are taken care of for you. The caveat is implementations vary a lot in quality. I use neovim with parinfer-rust and it's amazing, probably the best out there and I hope it stays maintained forever. I've also heard intellij w/ cursive has a good implementation. I have not found the same for VS Code and Emacs. I would be tempted to move back to Emacs if that weren't the case because of Cider tbh. See here for a recent discussion on r/clojure: https://www.reddit.com/r/Clojure/comments/wlx2lu/why_is_parinfer_not_as_good_as_i_think_it_is/

seancorfield17:08:56

I loved Parinfer when I came out and used it as part of my "daily driver" in Atom for quite a while. But the more I used it on a large codebase at work -- we have 140k lines in nearly 1,000 files -- the more dissatisfied I got: copy'n'paste with Parinfer enabled often produced the "wrong" code so I found myself constantly turning it off before pasting and then turning it back on. Eventually, I just turned it off. But then I switched to VS Code where it wouldn't have worked as well. As another data point, Lee Spector, who's a professor here that teaches Clojure to students as a tool for his (non-programming) classes says that complete beginners can find Parinfer very confusing because it "changes their code" in ways they don't understand and I can definitely see that.

Chase17:08:53

Yeah I can definitely see how others don't like it (just like folks don't like the paredit style even after many years). Unfortunately, you don't have many choices after that unless you want to manually balance parens I guess. I don't work on code bases that big yet so haven't seen those pain points. Even then though, I don't imagine I would ever go back. I love it (when the implementation is great). I love the shape of Clojure code and if it's shaped "right" I don't think parinfer should produce "wrong" code unless it's a bug, which there still are (https://github.com/eraserhd/parinfer-rust/issues).

Chase18:08:09

For example, it can get wonky with multi arity functions if you aren't starting the expressions right next to the variable list (as opposed to under it as some would want to do). Still never giving it up! lol And I think others should try it in case it is something they would love but ya gotta be using a decent implementation as I mentioned above.

skylize19:08:57

I cannot comprehend why I would give up typing parens that explicitly tell the reader what I want, and letting an algorithm insert whitespace just for visual consistency; in exchange for learning a specific ruleset of typing otherwise meaningless whitespace, in hopes an algorithm can correctly guess what parens I intended to be there. :thinking_face: For me, the maximum potential keystrokes I can possibly imagine being saved, in an absolutely perfect scenario, would still not even begin to make up for the loss of control and determinism here.

seancorfield19:08:40

Parinfer can be pretty polarizing for a lot of people 🙂

skylize19:08:27

Don't, get me wrong. If it works for you. 💪 But I will just never be able to empathize with that choice.

Chase20:08:24

Are you saying you don't even use paredit style editing? Because with that you also aren't specifically typing parens but instead using slurp and barf, etc. And if you can't get the commands right you literally can't write the code you want. That's where my frustration with paredit came from. It took away my control. But I still keep at it though as I can't help but admire the paredit wizards like the parens of the dead guy (and even what @U3X7174KS is doing above).

Chase20:08:13

But for me, with parinfer I can write it exactly how I want to and the parens take care of themselves. Seems magical but I know "magic" is supposed to be bad. I find it fun that we can come to different conclusions but seemingly from actually having similar motivations. I'm so curious to get into this industry and see how different my self taught approaches are from what's considered the "professional" way. (*edited message because I might have come off too combative but honestly just enjoying the convo and different perspective. don't mean to derail the original thread but want others to know parinfer is a perfectly valid option)

seancorfield20:08:43

I think this just underlines how subjective the whole editor/IDE experience is... There are some folks here who jump into almost any discussion of editors and say "You should try Emacs!" which isn't terribly helpful when a person is asking about, say, Calva, but the reality is that we make our editor choices and our plugin/package choices mostly on a subjective and emotional level.

❤️ 1
seancorfield20:08:24

I used Emacs decades ago (from 17.x to early 19.x) then switched across many other editors and IDEs over the years and when I came to Clojure I was mostly using Eclipse (for other stuff) so after trying a few things (including TextMate and Emacs), I settled on Eclipse with CCW for a while, then switched to Emacs and tried many different curated Emacs setups as well as building several of my own from scratch. Mostly I used Prelude. But I also tried LightTable and really liked that -- but it fell out of active maintenance so I went back to Emacs, then finally switched to Atom after seeing Jason Gilman demo proto-repl at one of the Clojure conferences. I stayed with that until it fell out of maintenance, then switched to Chlorine (for Atom), then to Clover (the same basic extension as Chlorine) for VS Code, and finally to Calva for VS Code. And I went back to Emacs on and off throughout all of that.

seancorfield20:08:09

Much the same as I started using Cognitect's REBL when that came out, switched to Vlad's Reveal, then to Chris's Portal -- which is what I'm using these days in VS Code.

seancorfield20:08:20

Folks ask me about IntelliJ/Cursive but, for whatever reason, I've never been able to get into IntelliJ. JetBrains used to keep offering me free licenses to try it and write a review for magazines, many years ago, but I just didn't like it. ¯\(ツ)

skylize03:08:12

> Are you saying you don't even use paredit style editing? > No. It is Parinfer that I could never get behind. Whitespace has no meaning beyond aesthetics and readability. So I can safely delegate its management to an algorithm, and then not think much about it. Parinfer does the opposite, whitespace is turned into an invisible DSL that I must learn, and remember, and debug, with a payout of a few parentheses I don't have to type. :man-facepalming: > you also aren't specifically typing parens but instead using slurp and barf, ... > And if you can't get the commands right you literally can't write the code you want > By default Paredit runs in Strict Mode, which means you cannot delete only an open paren or only a close paren, because it's dangling sister would break valid structure. You can disable the feature, (or replace any delete or backspace with a Force Delete command), hack away like it's Notepad, and forget Paredit is even there. Strict Mode is the only way Paredit forces your hand toward structural editing. It is is not trying to write code for you. It is there to help you move your cursor around based on structure and move forms around based on structure, and do miscellaneous other editing tasks that can be enhanced by working within structure (Thus Strict Mode to keep a valid structure in place.) Rather than disable Strict Mode, it would be better to use Splice. That removes the full matching pair of open/close parens. So your code is left structurally-valid, even if maybe not valid in other ways. Or select and cut the contents out. You can also select and delete the entire form with both parens, or cut out the contents before deleting the empty pair. From there, you are free to learn structural editing at you leisure, one or two commands at a time. Slurp and Barf are good starting points because they are quite simple but still pretty powerful, and you will get a lot of mileage from them. Calva's docs have great little animations to demonstrate each command. https://calva.io/paredit/

Chase03:08:18

Ahh ok, thanks for your clarifications. I assume we are so used to our own approaches we see things in a different light. Part of my love for Clojure comes from the aesthetics of the code it leads you to creating. Not quite like structuring a poem (and I know I'm entering r/pcj territory now) but it gives me some of that feeling as I craft the shape of it all. In terms of readability I would be structuring my clojure code the exact same whether I was using parinfer, paredit, or nothing. I would guess calling it "idiomatic" would not be exactly accurate but I would be interested in seeing if your code looks in any way different than most "idiomatic" Clojure code. I suspect it doesn't. How we get there might just be arguing semantics. So I don't see it as learning some weird invisible whitespace dsl as you do but more just writing Clojure code exactly how I want it to look which in my subjective opinion is of course making it as readable I can. I definitely don't do it to save from writing a few parens. I imagine I use a lot more keystrokes actually as I play around with the code. I just don't have to think about the parens balancing at all. But I'm also one of those people that lines up the values in my hashmaps and let bindings (within reason of course) as I find it more readable (and aesthetic, haha) but know that some in the community also viscerally hate it. Ironically, that approach is also using even more "forced" whitespace! lol

stantheman16:08:52

Thanks Sean the noob friendly guide to the use of Calva was excellent. I had been working with Neovim & conjure but didnt want vimscript so left out any plug-ins that wouldn't translate to lua. This was awkward so I've switched to VsCode. Remote WSL, and Calva after some unrelated fun and games (deps.edn file structures and running hyperfiddle.rcf) things are really tying together. Your edit aims followed by Calva VsCode really cracked a lot of understanding by me!

1
quan xing15:08:36

I use the seesaw,

(defn sync-projectbyid-action [e]
  (println "sync=>" e)
  (let [root (w/to-root e)
        projectIDText  (s/select root [:#projectIDText])
        projectid (w/text projectIDText)]
    projectid
    (lw/log logwin (str "root=" root "\n"))
    (lw/log logwin (str "projectIDText=" projectIDText "\n"))
    (lw/log logwin (str "projectid=" projectid "\n"))
    (lw/log logwin (str "projectid=" (first projectid) "\n"))))
(defn sync-projectbyid-area
 [] (w/flow-panel
     :border "Sync Project Data"
     :align :left 
     :hgap 20
     :items ["Please input project ID"
             (w/text :id :projectIDText :columns 10)
             (w/button :listen [:action sync-projectbyid-action] :text "Sync")]))
why projectIDText (s/select root [:#projectIDText]) return lazyseq . But in repl I call (s/select root [:#projectIDText]) return right object (#object[seesaw.core.proxy$javax.swing.JTextField....

brtmr20:08:33

What is your preferred setup for reloading/REPLs when when developing a webapp that has a clojurescript frontend and a clojure backend? In general, when I do clojurescript only, I use figwheel. Is there a way to have figwheel-style hot reloading, while also having an open clojure REPL for the backend?

dpsutton20:08:26

the two are largely unrelated. If you have are used to a cljs frontend you presumably already have a repl setup. For the backend, that seems far easier. Just start a repl and then start your webserver from your repl

popeye20:08:15

I have a variable like below , how to change value of key :d to true ?

(def p {:t {:u {:v #{{:a false} {:b false} {:c false} {:d false}}}}})

pppaul20:08:16

that set is messing things up

pppaul20:08:40

if you turn that set into a map, then things are easy

popeye20:08:04

agree, but that is how the data is 🙂

pppaul20:08:14

update-in [:t :u :v] (partial into {})

pppaul20:08:27

give me a sec

popeye20:08:34

i do not want to change the datatype

pppaul20:08:43

(-> p (update-in [:t :u :v] (partial into {})) (assoc-in [:t :u :v :d] true) (update-in [:t :u :v] (partial into #{})))

pppaul20:08:31

you could do that all in the first update-in, i'm just lazy

pppaul20:08:48

you can also do walk

pppaul20:08:51

give me 1 sec

pppaul20:08:55

(->> p
     (walk/postwalk
       (fn [form]
         (if (and (map-entry? form) (= :d (key form)))
           [:d true]
           form
           ))))

pppaul20:08:24

[clojure.walk :as walk] in ns

pppaul20:08:57

walk to the rescue for working with messy data!

dpsutton20:08:18

You already know how to use update-in. So you just need to know how to remove and add items to a set. disj and conj

pppaul20:08:58

the set is a bunch of map-entries, if you know exactly what you want to change, you could use walk/postwalk-replace

pppaul20:08:28

(->> p
     (walk/postwalk-replace {{:d false} {:d true}}))

pppaul20:08:16

if you run into a lot of messy data manipulation on your project, you may want to look into SPECTR

popeye20:08:13

what if we have only this, how can I replace d value

#{{:a false} {:b false} {:c false} {:d false}}

dpsutton20:08:42

(-> s (disj {:d false}) (conj {:d true})

dpsutton20:08:53

sets have add and remove and contains? semantics. use those operations

popeye20:08:23

let me try, it is late night for me... I will update after trying , Thanks for your help @U11BV7MTK @U0LAJQLQ1 🙂

popeye20:08:56

yeah, it is woking as expected 🙂

dpsutton20:08:01

in the future, don’t think about the nested structure. your problem is not a set nested deeply, you just want to change the contents of a set

1
🙌 1
dpsutton20:08:20

do it with a function and then update-in [path] that-function …

🙌 1
popeye20:08:19

if I want to remove whatever the value in the map , then how can we do it ?

#{{:a false} {:b false} {:c false} {:d false}}

popeye20:08:29

like just disj :a

dpsutton20:08:16

what are the elements of the set you want to remove?

dpsutton20:08:25

(disj s {:d false})

dpsutton20:08:29

you remove based on identity

pppaul20:08:18

i have a feeling he doesn't know the identity of the thing he want's to change

pppaul20:08:35

maybe just a partial identity

popeye20:08:42

something like (disj s :d) , so that it will remove both {:d false}

pppaul20:08:01

(disj s {:d false})

pppaul20:08:21

the whole map is the identity

popeye20:08:43

can I remove based on the key which is inside set ?

dpsutton20:08:44

{:d false} is one thing. what do you mean both?

pppaul20:08:02

the solutions that i gave are for partial identity changes

popeye20:08:23

i mean both key and value will be removed by specifying key ( which is inside a set)

dpsutton20:08:44

sets don’t have keys and values

dpsutton20:08:47

they just have elements

popeye20:08:48

how can try partial identity on disj ?

pppaul20:08:33

you do something like what i showed... you have to iterate over the items in the set

pppaul20:08:25

clojure doesn't have stuff that deals with partial identity, you have to write that yourself

pppaul20:08:27

(->> s (filter (complement partial-id-fn)) (into #{}))

pppaul20:08:09

remove over filter is better, though