Fork me on GitHub
#clojure
<
2021-12-10
>
qqq09:12:16

Besides s-exprs (which I'm not sure if they really matter; but definitely want to avoid for this thread), what feature doe the Clojure REPL have that the Scala/Groovy REPL lacks that allows Clojure REPL-driven-development to go more nicely than Scala/Groovy REPL-driven-development ?

flowthing09:12:22

> Besides s-exprs (which I'm not sure if they really matter; but definitely want to avoid for this thread) They certainly matter. Without s-expressions, it is much more difficult to determine what to send from the editor to the REPL, and therefore evaluate individual pieces of code. Besides that: • Clojure allows (and encourages) you to develop entire programs at the REPL. In other words, there are no limitations on what you can do at the REPL. Clojuer does not require you to have any code in a file in the file system. • Scala and Groovy REPLs are not REPLs. They're prompts, or shells, or something else, not sure. A REPL is composed of individual read, eval, print, and loop steps. Scala and Groovy do not have a read step, for instance. See https://groups.google.com/g/clojure-dev/c/Dl3Stw5iRVA/m/IHoVWiJz5UIJ for more. Moreover, with Scala and Groovy, it is not possible to start a nested REPL for some other purpose (see things like https://github.com/eggsyntax/datawalk or https://github.com/dpsutton/grepl for examples). • Scala and Groovy do not have (to my knowledge, at least) a tangible runtime. Clojure offers a multitude of ways to inspect (e.g. clojure.repl/apropos etc.) and modify (e.g. by redefining functions) the runtime. • It is not (to my knowledge) possible to specify a Java system property that serves a Scala/Groovy REPL over a socket connection which you can then connect to debug (and if you're brave, modify) code running in a production or staging environment. Just the first couple of things that came to mind.

flowthing09:12:13

I'm not intimately familiar with Scala or Groovy REPLs, of course, so I'm happy to be corrected on any of the above.

Ben Sless10:12:26

They are repls, they're just much less useful. The way lisps work, redefinitions dynamically influence the entire program

Ben Sless10:12:52

Clojure does this by implementing a sort of dynamic linking

Ben Sless10:12:24

This is why you even can develop your program interactively against a repl

Ben Sless10:12:29

If in Scala you have a class whose behavior changed, the previous instances living in your REPL are not updated, then you need to rebuild your state

qqq12:12:51

@UK0810AQ2: Are you referring to https://clojure.org/reference/vars or something else ?

Ben Sless12:12:36

@U3JURM9B6 vars enable that by introducing a level of indirection

Ben Sless12:12:44

Instead of directly calling the invoke method on the function object, you first dynamically deref the var. You can eliminate this by turning on direct linking

qqq09:12:16

Anyone here using https://github.com/gregsh/Clojure-Kit instead of Cursive? (Mainly interested in the open source nature + integration with IDE scripting console.)

zendevil.eth14:12:44

I have the following:

(loop [keys* (keys result) db* db]
                     (if-not (first keys*)
                       db*
                       (recur
                         (rest keys*)
                         (assoc-in db* [:datasets (:id result) (first keys*)] ((first keys*) result)))))
But not sure why I’m getting this error:
null
Can't recur here at line 493 projectgun/events.cljs

Antonio Bibiano15:12:58

it works for me

(def result {:id 100 :a 1 :b 2})
(def db {})
(loop [keys* (keys result) db* db]
  (if-not (first keys*)
    db*
    (recur
     (rest keys*)
     (assoc-in db* [:datasets (:id result) (first keys*)] ((first keys*) result))))) => {:datasets {100 {:id 100, :a 1, :b 2}}}

Antonio Bibiano15:12:01

ah but I did this in clojure , not cljs

vlaaad14:12:29

How horrible is this if I want swap polymorphic on ref type?

(defn- swapper
  "Return swapper that executes f exactly once, preferably atomically"
  [ref]
  (condp instance? ref
    Atom (fn [f & args]
           (let [tried (atom false)]
             (swap! ref (fn [v]
                          (if (compare-and-set! tried false true)
                            (apply f v args)
                            v)))))
    Agent #(apply send ref %&)
    Ref (fn [f & args]
          (let [tried (atom false)]
            (dosync
              (alter ref (fn [v]
                           (if (compare-and-set! tried false true)
                             (apply f v args)
                             v))))))
    Var #(apply alter-var-root ref %&)))

Drew Verlee05:12:14

fwiw ref is a clojure core reserved word/symobol, would avoid re-using it, to avoid confusion.

Drew Verlee05:12:44

As an example i copied in some code to dissect what was going on, and was very confused. Imagine trying to read a giant code block, of course you hope they can quickly find the source, but still it forces you out of the context you hoped to stay in.

hiredman15:12:17

🤢

✔️ 3
ghadi15:12:25

the code isn't horrible, but the raison d'etre probably is

dpsutton15:12:58

the Atom one looks suspicious to me. I can see how it would execute f once but the result is thrown away. Not sure what guarantees you want to make on f’s return value.

Bobbi Towers16:12:49

This looks a lot like the problem recently dealt with here: https://www.youtube.com/watch?v=IsS8ZCSUTUQ

ghadi16:12:59

I can't conceive of a situation where I need to "swap" on opaque targets

dpsutton16:12:18

there’s also a subtle issue where you need to determine your semantics. Are you ensuring f is only called once per call site or f is called only once per value in the atom. If its the former, this is basically just a “deref, and reset!” with that explicit race value as you just keep clobbering. Otherwise it is very close to just a regular atom

vlaaad19:12:23

Good points, thanks yall! I think in my case retries should throw. The reason is I'm making a UI for Reveal (live view) that watches any type of ref and has controls to update it

vlaaad19:12:07

basically integrant/component/mount live view, where systems typically live in atoms or vars

vlaaad19:12:02

I'm ensuring 1 call per call site since calls are side-effecting and non-retriable - starts and stops of the system

hiredman19:12:41

all terrible

hiredman19:12:23

use reset!, this sucks, pretending it is thread safe in anyway is a joke

vlaaad19:12:24

it's, like, super useful for me at least

p-himik19:12:46

I might be reading it wrong but it reminds me of some Python code I had to debug a few years ago that extensively used properties where getters were mutating things.

vlaaad19:12:27

this sounds pretty different...

vlaaad19:12:03

but I think I'll use CAS at least for atoms

hiredman19:12:03

you have no reason to cas, and start/stop of systems in an atom via swap! explicitly violates the contract of swap

hiredman19:12:32

this is not threadsafe, and cannot be made threadsafe, to clutching to constructs like cas is silly

vlaaad19:12:42

I'm not trying to make it threadsafe, I'm trying to make it recoverable

hiredman19:12:54

cas won't do that either

vlaaad19:12:28

it will because the exception will contain started system that user will be able to stop manually

hiredman19:12:21

that is not a property of cas

phronmophobic19:12:07

I think it might be useful to have a little more context on the use case. • what's a story of the typical usage? • how many refs are usually involved? Always one, or varying numbers? • what's the story for usage when a "swap" fails?

vlaaad19:12:25

Well yes, it is a property of swapper. I don't want to do just deref+reset because if a system was started by some other code between deref and reset I might have 2 systems running and both might need cleanup. Just doing a reset will leave prev system running, but it will be lost. CAS will keep that system running and will return extra system to the user so they can deal with it.

hiredman19:12:26

what you just described is wanting thread safety

hiredman19:12:47

"if a system was started by some other code between deref and reset"

hiredman19:12:41

the only way you are going to make a pile of mutable state and side effects thread safe is a big lock around it, which is basically what you are doing with your extra atoms

hiredman19:12:30

and once you have a lock around it, mutating it using clojure's reference model, why bother?

hiredman19:12:12

sticking systems inside mutable references is itself problematic

hiredman19:12:22

like an atom in an atom

vlaaad19:12:02

that's how integrant/mount/component works, not much I can do about that

hiredman19:12:58

I cannot speak for integrant/mount specifically, but I use component a lot and can't recall ever putting the system in a ref, agent, or atom

vlaaad19:12:15

you use it in a var probably

vlaaad19:12:29

which is very nice, because alter-var-root is synchronised

hiredman19:12:59

usually there main function of the server starts the system and sets the var, the var is just there for repl reference and is never mutated

hiredman19:12:06

after that point

vlaaad19:12:32

exactly, but mount uses atoms

hiredman19:12:56

and I hate mount and never use it, so this all tracks

😄 3
1
👍 3
hiredman19:12:09

I imagine people put component systems in atoms because the idea of mutating a var at system startup makes them squeamish, and "oh, if I am mutating something, it must go in an atom"

yes 1
tolitius20:12:25

atoms are great. so is CAS. hating with no context is not

potetm20:12:04

The real irony is this is the comment that’s ignoring all context lol

hiredman20:12:35

Have you considered getting tshirts made?

delaguardo20:12:03

Hate is nether helpful. Be nice to each other ☮️

Cora (she/her)20:12:06

feels like a whole lot of hostility in this channel today

👍 1
Cora (she/her)20:12:19

but maybe I'm reading into things

Darin Douglass20:12:46

it’s what happens when we all, as a community, have to go deal with java logging /s

😆 6
vlaaad20:12:59

btw I don't hate mount, I use it at work now and it's more convenient than integrant (I have experience with both)

potetm20:12:04

The real irony is this is the comment that’s ignoring all context lol

didibus21:12:15

The component-like libs always seem to be the flame wars in Clojure haha. But in my experience, like all good flame wars, they're all a pretty good choice actually, and that's why it creates flame wars. Someone once said, when two senior engineer argue loudly, it means both their ideas are pretty damn good, so much so that they can't agree which one is better.

vlaaad21:12:29

somewhat vaguely related note: when it comes to start/stop lifecycles, I find the most simple and easy approach to define it is to make a start function return stop function. then all imperative stuff can be in lexical scope of a single expr.

vlaaad21:12:47

For example, reveal uses this start/stop pattern for its observable view. Defining a subscription behavior that watches taps, and then stops watching when the view is closed looks like this:

(defn- subscribe-tap [notify]
  (add-tap notify)
  #(remove-tap notify))
pretty isolated imo