Fork me on GitHub

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 ?


> 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 for more. Moreover, with Scala and Groovy, it is not possible to start a nested REPL for some other purpose (see things like or 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.


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


@UK0810AQ2: Are you referring to 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


Anyone here using instead of Cursive? (Mainly interested in the open source nature + integration with IDE scripting console.)


I have the following:

(loop [keys* (keys result) db* db]
                     (if-not (first keys*)
                         (rest keys*)
                         (assoc-in db* [:datasets (:id result) (first keys*)] ((first keys*) result)))))
But not sure why I’m getting this error:
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*)
     (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


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

(defn- swapper
  "Return swapper that executes f exactly once, preferably atomically"
  (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)
    Agent #(apply send ref %&)
    Ref (fn [f & args]
          (let [tried (atom false)]
              (alter ref (fn [v]
                           (if (compare-and-set! tried false true)
                             (apply f v args)
    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.



✔️ 3

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


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:


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


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


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


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


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


all terrible


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


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


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.


this sounds pretty different...


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


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


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


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


cas won't do that either


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


that is not a property of cas


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?


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.


what you just described is wanting thread safety


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


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


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


sticking systems inside mutable references is itself problematic


like an atom in an atom


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


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


you use it in a var probably


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


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


after that point


exactly, but mount uses atoms


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

😄 3
👍 3

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

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


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


Have you considered getting tshirts made?


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

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


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


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.


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.


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