Fork me on GitHub
#clojure
<
2020-11-14
>
didibus00:11:00

I don't get it. Then why can't I load a namespace I create with create-ns the same way wtv ns does?

noisesmith00:11:22

ns does more than just create the namsepace, it also registers it as something loaded

didibus00:11:48

Right... so now we are full circle no? Why doesn't create-ns does that as well?

noisesmith00:11:48

so that the next require (without something like :reload) doesn't attempt to load it

noisesmith00:11:09

because create-ns is a much lower level tool, iirc ns uses create-ns

didibus00:11:35

Hum, not from the code I look at

didibus00:11:06

I mean, ns is a lot more complicated

didibus00:11:35

I guess what confuses me here, is the concept of a namespace vs a lib and something that is loaded and not?

didibus00:11:55

So I can create a namespace, but I can't ever load or require it?

didibus00:11:02

That seems weird

noisesmith00:11:16

loading and requiring need something to load or require from

noisesmith00:11:35

if you are making something from scratch, you want create-ns or ns (you can also use in-ns but that's not useful)

didibus00:11:46

I mean the only difference is that ns seem to add the name to loaded-libs.

noisesmith00:11:16

and changes your current namespace

noisesmith00:11:24

which you probably don't want in the middle of a file

didibus00:11:38

Ya, I guess the magic is inside in-ns but I think that's a special form in JAva

seancorfield00:11:57

And ns will auto-refer in clojure.core

didibus00:11:33

So I mean, semantically ns does a lot more. But the part where it creates a namespace seems to be doing it differently then when I call create-ns. That's what confuses me

seancorfield00:11:45

(which is why if you do (in-ns 'foo.bar) in a fresh REPL, you suddenly don't have any core functions accessible!)

seancorfield00:11:16

@didibus What do you mean by "doing it differently"?

didibus00:11:54

Using ns it creates a namespace that I can then require. But not so for create-ns

andy.fingerhut00:11:23

user=> (pprint (macroexpand-1 '(ns foo.bar)))
(do
 (clojure.core/in-ns 'foo.bar)
 (clojure.core/with-loading-context (clojure.core/refer 'clojure.core))
 (if
  (.equals 'foo.bar 'clojure.core)
  nil
  (do
   (clojure.core/dosync
    (clojure.core/commute
     @#'clojure.core/*loaded-libs*
     clojure.core/conj
     'foo.bar))
   nil)))

andy.fingerhut00:11:06

Doing a successful ns on a namespace should, I believe, add it to *loaded-libs*, so that future calls to require on that namespace are a no-op unless you add something like :reload-all

didibus00:11:45

Exactly, and that's why I don't understand why create-ns doesn't also add it to loaded-libs

didibus00:11:06

Maybe just an omission, or is there some conceptual reason

noisesmith00:11:31

using require for something that doesn't have anything you can load is an off-label usage

seancorfield00:11:38

ns adds the namespace to the list of loaded libs so require doesn't need to load it from disk.

seancorfield00:11:16

user=> (ns quux.wibble)
nil
quux.wibble=> (in-ns 'user)
#object[clojure.lang.Namespace 0x3f22ce2a "user"]
user=> (require 'quux.wibble :reload)
Execution error (FileNotFoundException) at user/eval121530 (REPL:1).
Could not locate quux/wibble__init.class, quux/wibble.clj or quux/wibble.cljc on classpath.
user=> 

didibus00:11:24

So I understand the implementation is why they differ. But I don't understand the conceptual reason or difference.

didibus00:11:42

Like what is loaded when you use ns with no associated file or class

seancorfield00:11:46

If you ask require to reload a namespace only created in memory, it fails, just like trying to require a namespace created by create-ns.

seancorfield00:11:59

So ns = create-ns + add to loaded-libs + refer clojure.core + in-ns.

didibus00:11:52

Hum, so I guess with that my question is, why doesn't create-ns adds it to loaded-lib ?

andy.fingerhut00:11:08

I think because it is a lower-level function than the ns macro

seancorfield00:11:17

Because it's a low-level primitive. Yeah, what he said.

didibus00:11:44

Ok, but its weird. If it just created a namespace object I'd get it. But it also adds it to the global list of namespaces

andy.fingerhut00:11:20

That doesn't make it a higher-level function. It just means it does 2 things instead of 7

didibus00:11:41

Haha, true true. I guess it just feels like not low level enough, and not high level enough

andy.fingerhut00:11:50

If create-ns did not add it to the global list of namespaces, then all-ns would not include such namespaces.

andy.fingerhut00:11:03

hardly anyone uses it in applications, I expect.

didibus00:11:05

So would it be safe for me to add a create-ns namespace to loaded-lib ?

noisesmith00:11:31

why? what's the utility here?

noisesmith00:11:45

just to create aliases without calling alias ?

seancorfield00:11:43

The namespace isn't going to contain anything so what would be the use in requiring it?

didibus00:11:10

Hum, its a bit complicated. Basically, I have a data-model namespace where I create namespaces and alias them to use as my keys for my specs. Now in some other namespace, I have a fdef which I want to say this arg is of spec ::account/type but that is not a real namespace, but one I create with create-ns in data-model namespace. So my first thought was, ok, well I need to require :as the specs where I use them.

noisesmith00:11:54

right, that's what alias is for (this is the only reason I have seen to care about namespaces that don't have code in them)

didibus00:11:08

Well, ya I can use alias, it just seems a bit inconssistent

dpsutton00:11:20

i think these are some of the shenanigans that i see hiredman point out as completely unnecessary hoops you have to jump through when you use namespace aliased keywords for your specs and i just agree more and more as time goes on

didibus00:11:55

Ya, its definitely missing something. But I find none is ideal. If you go with :foo/bar instead of ::foo/bar, then you lose compile error for non existent namespace

noisesmith00:11:40

if you are actually using the spec you will error on one side or the other for the spec not being found...

didibus00:11:20

I feel spec should have used interned symbols. And s/keys shouldn't have done its weird magic of like the key is the spec and the key. Should have been like: (s/keys :req {spec :foo/bar})

noisesmith00:11:01

also, nothing stops you from doing (create-ns :account) and then using :account/type - it doesn't need aliasing to work

dpsutton00:11:09

if you just did :account/type doesn't this just go away?

noisesmith00:11:29

if you have more than one thing that would shorten to :account in one codebase you are doing something weird anyway

seancorfield00:11:30

You don't need to create a namespace for :account/type

💯 6
didibus00:11:22

That creates conflicts

seancorfield00:11:30

As Alex said, there's probably going to be something built into Clojure 1.11 to make this more tractable -- they just haven't figure out the right thing for it yet. And I suspect the pain you're going through trying to "solve" this today @didibus reflects why they're still trying to figure it out 🙂

clj 3
didibus00:11:22

I'm looking forward to it. And ya, all my attempts have had issues.

didibus00:11:57

Though this last one seems the best yet. I was surprised that I can't require... but using alias will do

Alex Miller (Clojure team)02:11:44

Well the “want” is easy - aliases that don’t require reified namespaces. Lots of interesting choices in the impl

didibus23:11:58

Is there a want for symbol aliases that don't require reified namespaces as well? Or just for keyword aliasing?

Alex Miller (Clojure team)01:11:08

an excellent question we have considered. I don't have a use case for lightweight symbol aliases right now but would be curious to see one if you find one. symbols don't have auto-aliasing so that's one context that doesn't exist.

didibus23:11:54

I don't have one either, was just curious.

didibus23:11:40

By the way, I don't know if it helps. But where I've landed with my experimentation is something very similar to how symbols and namespaces work. I quickly details it here: https://clojureverse.org/t/spec-ing-entities-and-how-to-organize-the-specs-and-our-entity-keys-a-new-approach/6817/8?u=didibus

Alex Miller (Clojure team)00:11:10

Stateful non-lexical scope is gross

didibus00:11:54

Is it not lexical? I think it is

Alex Miller (Clojure team)00:11:03

If you look at just in-scope by itself what does it mean?

Alex Miller (Clojure team)00:11:12

You have no way to know

Alex Miller (Clojure team)00:11:38

Or I guess defalias by itself

didibus00:11:56

Well, you know that every line of code after it which calls defalias will use the in-scope namespace

didibus00:11:05

Same as in-ns

didibus00:11:39

The only "tricky" part, similar to in-ns, is that the scope isn't put inside of a delimiter block, but is flat

didibus00:11:13

Haha, well I agree with you a little. But the alternative was:

(with-scope [ :alias [user]]
  (defn ...)
  (defn ...)
  (s/def ...)
  (s/fdef)
  (def)
)

didibus00:11:22

Which completely messed up clj-kondo. And felt less Clojury. Kind of gave me vibes of Java Classes actually (which is interesting, since its really the same problem of Entity scope that OOP has that I have here as well. I'll see how it goes in practice, but for now it works pretty much the same as symbols and namespaces, so I've found it quite intuitive. Like I'm already used to symbols having an implicit namespace that is defined by some preceding call to ns or in-ns. So this felt normal.

didibus00:11:48

There's a chance I drop the in-scope as well, and just accept the fact I'm going to be using defalias like:

(defalias 'user 'com.org.app.user)
(defalias 'transact 'com.org.app.transact)
(defalias 'account 'com.org.app.account)
(defalias 'cart 'com.org.app.cart)
And just copy/paste this everywhere. The repetition annoys me, but maybe the clarity is worth it, not sure yet.

didibus01:11:26

Well, ok, had to rethink this again 😛 If Alex Miller don't like it, something must not sit right with it lol. And I realized the only reason for my in-scope was that I was currently sprinkling the defalias call throughout my data-model namespace, above their corresponding s/keys for each. But I don't need to do that. Then I can scope all alias in a single block, put it at the top (and ideally ns could be extended to have an :alias option as well, making it even better.

didibus01:11:00

(ns com.org.app.data-model ...)

(alias '[ :as app :refer [user account transact cart]]
       '[com.org.other-app :as other-app :refer [user] :rename {user other-user}])

::user/name
;; => :com.org.app.user/name

::app/user
;; => :

::other-user/name
;; => :com.org.other-app.user/name

didibus01:11:47

Not sure about the DSL here. Maybe it doesn't have to shoehorn :refer and :as and :rename, could use some other DSL. But I think with this one, I'll be able to tell clj-kondo to lint it as require and it should work which is nice.

Alex Miller (Clojure team)01:11:52

I don't know what problem you're trying to solve at this point

Alex Miller (Clojure team)01:11:31

why do want renaming and referring? just seems like a lot of stuff to make it as hard as possible to understand what kw you're actually using

didibus02:11:33

Say I have a map which models a Person's Name:

{:name "John Doe"}
And so I have a spec for it (s/def ::name (s/keys :req [::name])) And now some dependency of mine also has a spec for a map that models a name, but the map is different:
{:first-name "John"
 :last-name "Doe"}
And also have a spec: (s/def ::name (s/keys :req [::first-name ::last-name])) Now some service uses both of these. That service will s/fdef the functions which uses those maps and those maps will also be persisted forever in some DB. So there are two problems with the above, if the namespace in which the spec are defined changes, all my fdefs are broken, and all my keys are broken. But also if I don't use a namespace on my keys, the specs and map keys now conflict. Also, with the first example, the spec for the key and the map itself also conflicts. So that's the scenario. It means I need a way to namespace my specs and my keys that is independent from the namespace they are in, and also that avoids conflicts. But I also don't want to fully be typing giant namespaces everywhere, since that's both annoying and ugly, but also prone to hard to find typos.

didibus02:11:24

And imagine my function that uses those is this:

(ns foo.bar
  (:require [com.org.my-app.name :as name]
            [com.org.some-dep.name :as other-name]))

(defn name->other-type-of-name
  [name]
  {::other-name/name (str (::name/first-name) "." (::name/last-name))})

(s/fdef name->other-type-of-name
  :args (s/cat :name ::name/name)
  :ret ::other-name/name)

didibus02:11:07

So here I'm using the normal ::. It means that each entity must be in their own namespace so I can require each one, so imagine I also had a map called group, and account, and all that, and I didn't want their spec all in separate files. It also means that my specs and keys can be broken inadvertently by a refactor of those namespaces, so if the specs ever move to some other namespace, my code is broken.

Alex Miller (Clojure team)02:11:03

a) the only thing that will work in the spec registry is sufficiently qualified ("long") namespaces b) what you want to type in your code is something short (an alias, "short" name) c) existing aliases give you exactly this feature, with the constraint that the aliased qualifier must be an actual namespace d) the thing we're going to add is the ability to do c but without that constraint (actually api still in flux)

Alex Miller (Clojure team)02:11:21

when you say "refactoring", I hear "breaking stuff"

Alex Miller (Clojure team)02:11:42

if you don't like stuff broken, don't break stuff

didibus02:11:19

Well, since the spec registry is global, and keys outlive an app, I'd rather not couple them to some particular code namespace.

didibus02:11:30

So with c without the constraint, I would do:

(ns foo.bar
  (:require [com.org.my-app.specs]
            [com.org.some-dep.specs]))

(alias 'name 'com.org.my-app.name)
(alias 'other-name 'com.org.some-dep.name)

(defn name->other-type-of-name
  [name]
  {::other-name/name (str (::name/first-name) "." (::name/last-name))})

(s/fdef name->other-type-of-name
  :args (s/cat :name ::name/name)
  :ret ::other-name/name)
Which works fine. Except if my-app has 28 entity specs, than I need to do:
(alias 'name 'com.org.my-app.name)
(alias 'user 'com.org.my-app.user)
(alias 'car 'com.org.my-app.car)
(alias 'etc  'com.org.my-app.etc)
Which is not that bad, but :refer was a way to shortcut the repetition here. The other thing with :as and :refer, is I guess what I mentioned with (s/def ::name (s/keys :req [::name])). What namespace should the map belong too? And what namespace should the key of the map belong too? Ideally it would be the map is :my-app/name and the key inside it is: :. But now again, the short alias makes this difficult, because this does not work: :: is an error

didibus02:11:52

So that last issue is why when I alias, I'd like to be able to refer to the "parent namespace" like .my-app as well as the child namespaces like: com.org.my-app.name. I can still do it with c like so:

(alias 'my-app 'com.org.my-app)
(alias 'name 'com.org.my-app.name)
(alias 'user 'com.org.my-app.user)
(alias 'car 'com.org.my-app.car)
(alias 'etc  'com.org.my-app.etc)
But again, I do this all the time, so I thought shortening this would be good.

didibus02:11:16

It ends up very similar to Java honestly, with how it has a package.class/field. Basically, in practice, I've felt like I needed this scheme for my specs and my keys as well: context.entity/key

didibus02:11:56

So I think of it as:

(alias '[some-context :as context :refer [entity1 entity2 entity3]])
And then I can define the keys with ::context.entity2/key

Alex Miller (Clojure team)02:11:16

it seems like it's impossible at that point to have any idea what that actual keyword is

Alex Miller (Clojure team)02:11:49

(obviously it's possible, but it would require walking through several distinct mappings to get there)

Alex Miller (Clojure team)02:11:04

I have not seen any other cases where someone was taking aliases to this degree of phased construction

didibus02:11:03

Well, most people stopped using them, and instead hand type: :my-app.entity/key

didibus02:11:33

I see that a lot: :cool-app.user/name

didibus02:11:57

And :cool-app/user or sometimes: :cool-app.user/user depending on convention, though latter means you can't have the map and the key inside be named the same like in my ::name map with key ::name

Alex Miller (Clojure team)02:11:00

I generally see people using a relatively small number of qualifiers for specs. it could be that that's because it's hard otherwise, but I did actually survey a large number of uses of alias / create-ns / and similar things on github to see what was out in the wild.

seancorfield02:11:57

As far as I'm concerned, anything in a library that should be combined with other code, should use spec names that match actual namespaces, or at least fully-qualified names that "match" similar namespaces in the library. The whole point of qualified keywords in specs is that they shouldn't conflict with other code.

didibus02:11:48

I've done the same, because it was too hard personally. You either go: :app.entity/key or even lazier: :app/key and then your keys are called: :app/entity-key. And you forget trying to have a full URI like and hope there won't be app name clashes.

seancorfield02:11:51

You can easily use short names just within your own code (modulo not conflicting with obvious library names -- but if libraries as using properly qualified names, that won't be an issue).

seancorfield02:11:30

I think you're just making life harder for yourself (and you know my opinion on this already I suspect 🙂 )

didibus02:11:23

I mean, as soon as I say :: people respond with don't use ::, I don't use it, etc. There's a reason for that

didibus02:11:02

It is a problem when speccing your domain model though. Data that will outlive your code over time.

seancorfield02:11:03

is a straw man.

didibus02:11:38

Haha, I mean replace org with the name of your org: com.worldsingles.user/id

seancorfield02:11:43

A few people say "don't use ::"

Alex Miller (Clojure team)02:11:14

I mean for me, I just fully qualified names in specs most of the time. I can't remember anything and then when I look at the code next, it's explicit. most code using the data is transforming it (not constructing it) and then I use :foo.bar/keys in destructuring or whatever and that covers a lot of places where I'm actually typing those namespaces.

didibus02:11:54

The thing with :: is the same as dynamic vars. People don't realize that your data escapes the scope of your namespace. I've had multiple devs on my team make this mistake. They use :: in their specs, and thus their keys, and then don't realize now when you go to the DB, or return to the client, that namespace is the key's namespace as well. And maybe the spec started in: com.org.app.api. And in the future there is a service rewrite, in a new package of a different app name, or api was a bad place to put the spec cause now there are many APIs using it, and you need to move it out to prevent a circular dependency, but now it changes the key and breaks your clients (has happened to us)

Alex Miller (Clojure team)02:11:16

I don't think that's a problem I can fix :)

seancorfield02:11:16

Are you using Datomic?

didibus02:11:01

@U064X3EF3 When you say you use: :foo.bar/keys what do you make foo and bar equal too? Do you purposely try to find a shorter name then your application namespace? Is bar ever the entity name? Or do you do :foo.bar/entity-key ?

Alex Miller (Clojure team)02:11:35

"Do you purposely try to find a shorter name" - generally, no

seancorfield02:11:34

Qualification of keywords is about how unique you need the names to be.

Alex Miller (Clojure team)02:11:36

if relevant, I think entity would be part of the qualifier

didibus02:11:44

Not using Datomic no. Even without a DB though:

(ns com.my-company.my-service.api1)

(s/def ::user (s/keys :req [::id ::name ::email]))

(defn api1
  [input-request]
  {::id "123" ::name "John" ::email ""})
It starts like this, and then another API starts using the ::user map, so the spec is moved into a common com.my-company.my-service.specs namespace, but the client is broken now, because the namespace of the keys in the user map were changed by accident.

seancorfield03:11:13

Your refactoring was wrong then.

seancorfield03:11:28

As Alex says, if you don't want things to break, stop breaking them.

seancorfield03:11:40

The database doesn't care about namespace qualifiers.

didibus03:11:11

Yes it was, but the language kinds of make you prone to it. Like at first :: pretends to be this nice convenience, but it turns out that its really inconvenient in a non toy example.

didibus03:11:30

So you'd be much better doing:

(ns com.my-company.my-service.api1)

(s/def ::user (s/keys :req [::id ::name ::email]))

(defn api1
  [input-request]
  {:com.my-company.my-service/id "123"
   :com.my-company.my-service/name "John"
   :com.my-company.my-service/email ""})
Except now you wish there was a more convenient syntax for these loooooong namespaces on your keys you have everywhere

didibus03:11:11

Those are real ugly in destructuring code as well

Alex Miller (Clojure team)03:11:18

like #:com.my-company.my-service{:id "123" :name "John" :email ""} ?

Alex Miller (Clojure team)03:11:37

and like (let [:com.my-company.my-service/keys [id name email] ...)

didibus03:11:12

Ya, in a toy example as well, until your payload returns a map of user + item keys mixed in, now you can't map alias both of them.

Alex Miller (Clojure team)03:11:01

and both of those also support ::alias/ syntax too

seancorfield03:11:06

I have no problem with :: (just for the record).

Alex Miller (Clojure team)03:11:21

(with the pesky constraint that the alias has to be a real namespace)

didibus03:11:24

Hum, ok I didn't know that. So I just prepend a number to it?

seancorfield03:11:27

I'm perfectly happy to use ::alias/key for the shorter name.

Alex Miller (Clojure team)03:11:50

no, I was just you could use two different :http://some.name/keys in the same destructuring map

seancorfield03:11:55

{::foo/keys [a b c] ::bar/keys [b c d]}

Alex Miller (Clojure team)03:11:27

for a map {::foo/a ... ::foo/b ... ::foo/c ... ::bar/b ... ::bar/c ... ::bar/d ...}

didibus03:11:27

Well, ok, the way I use namespaces on key is like so. My entity specs are keyed on: :unique-namespace/entity and my entity fields are keyed: :unique-namespace.entity/key. So at the end of the day, I just need a way to make unique-namespace shorter, because to make it unique I make it really long as I follow the format:

didibus03:11:07

That's my problem I'm looking a solution for

Alex Miller (Clojure team)03:11:20

I think aliases are the solution to most of that

didibus03:11:40

So imagine I have:

(s/def :com.my-company.my-service/user
  (s/keys :req [:com.my-company.my-service.user/id]
                :com.my-company.my-service.user/name
                :com.my-company.my-service.user/address
                :com.my-company.my-service.user/email
                :com.my-company.my-service.user/dob]))
How would I manage this in a shorter syntax way?

Alex Miller (Clojure team)03:11:59

(alias 's 'com.my-company.my-service)
(alias 'u 'com.my-company.my-service.user)
(s/def ::s/user
  (s/keys :req [::u/id]
                ::u/name
                ::u/address
                ::u/email
                ::u/dob]))

👍 3
Alex Miller (Clojure team)03:11:32

although tbh, I'm perfectly fine with the original :)

didibus03:11:09

So like this?

(alias 'my-service 'com.my-company.my-service)
(alias 'user 'com.my-company.my-service.user)

(s/def ::my-service/user
  (s/keys :req [::user/id]
                ::user/name
                ::user/address
                ::user/email
                ::user/dob]))

Alex Miller (Clojure team)03:11:30

g'night all, stepping away

didibus03:11:48

Ya, this is fine (assuming if they are not real namespaces for those alias will still work). Its just when I started using it in my app like that, the top of my namespaces started looking like Java import statements, they became huge, like 20 lines long, because I have big namespaces that use a lot of entities, so I was just looking for a shorter way to define those aliases. Maybe I've become allergic to verbosity since I moved away from Java 😛, but it was a sore point on my eyes.

didibus03:11:47

Thanks Alex, much much appreciated. Have a good night.

didibus03:11:12

And thanks Sean as well for the input

3
didibus00:11:00

Its super likely that some package depends on a library that has an :account/type spec and that you also have an :account/type spec

didibus00:11:01

Actually is the case for me. This lib has a different way it models accounts from the main service. In both I want an account spec for how they model it.

javahippie12:11:33

I believe I am having some issues with Singletons from a Java library in the REPL. The affected project is a leiningen project, I am using Cider from within Emacs. Im working with Testcontainers, and the library uses a singleton class ResourceReaper, which can be accessed statically. Every container instance which is created registers itself in that ResourceReaper. This reaper provides a method to stop and remove all container instances. If the library is used from a Clojure REPL, a lot of instances might be created and I would like to clean them up with a call. When I call the instance method of the Java library (see code block), I seem to get a new instance of ResourceReaper. It does not contain any registered containers. Might there be multiple Java instances in the background that I don’t know about?

javahippie12:11:54

This is how I try to obtain the instance from the REPL, and perform a lookup of the (private) field registeredContainers (for debugging only, atm)

the2bears22:11:46

Just a thought, but a singleton in Java might not actually be a singleton. It's been awhile since I spent time with class loaders, but if I recall correctly you might have a case of "peer" ClassLoaders, same parent. Each of those can instantiate the ResourceReaper class and the two will be considered different. Consider this as a class being a singleton only with respect to its ClassLoader. Usually this doesn't come up, as the loading hierarchy works this out, but for example in the case of OSGi things are done to break the hierarchy somewhat.

javahippie08:11:14

That’s a good point, thanks! I guess it is not too far of to assume, that there might be multiple classloaders at work when connecting Cider to a REPL
 :thinking_face:

emccue16:11:46

@javahippie If you call instance multiple times you should get the same object every time

emccue16:11:11

the only exception to this would be if some other method on ResourceReaper cleared out the static field

emccue16:11:59

in which case, and you should read the docs to confirm, I would expect there to be some cleanup logic before it loses the reference to the instance

emccue16:11:59

maximum cringe at the death note references in the code though

emccue16:11:13

it doesn't even make that much sense

javahippie17:11:43

Thanks! From the docs (and checking back with the maintainers) this instance should not be cleaned out from anywhere, except when the JVM is shutdown. If the tracked instances were removed, the Docker containers belonging to those instances would be gone, too, but they are still there. But if this is not a “known” behavior for Java Singletons in a REPL, then it’s not really a fitting question for this channel 😉 I will try and debug into the Java code, maybe this clears something up.

borkdude17:11:36

Why exactly does prepl need read+string? Was read+string invented for prepl? I have seen two things using prepl now and neither use the raw string I believe, but I'm curious about the reason it exists

seancorfield18:11:40

@borkdude As I recall, yes, it was added for prepl support and it was a nice, clean way to get back both an expression that was read in and the remaining string that is leftover, so you can iterate over the data easily.

seancorfield18:11:25

(which is particularly useful when the read fails after a certain character I think)

borkdude18:11:22

@seancorfield read+string gives back the read string, not the remaining string?

user=> (with-in-str "#(foo %) [1 2 3]" (read+string))
[(fn* [p1__139#] (foo p1__139#)) "#(foo %)"]

andy.fingerhut19:11:02

Just guessing, but perhaps to enable features that might give error messages based on the original expression, and/or save source code for later viewing?

borkdude19:11:21

(added a feature to edamame to have the original source as metadata on each expression:

user=> (map meta (m/parse-string "[x y z]" {:source true}))
({:source "x", :row 1, :col 2, :end-row 1, :end-col 3} {:source "y", :row 1, :col 4, :end-row 1, :end-col 5} {:source "z", :row 1, :col 6, :end-row 1, :end-col 7})
)

seancorfield19:11:50

Oh right, yeah, it's been a while since I looked at it. That's right, because they want the string version of the form that was read in, because that's what prepl returns, right?

seancorfield19:11:32

I remember looking at this right when it was released but haven't done anything with it since... tool builders still say prepl doesn't really work well enough for them 😞

hiredman19:11:03

I used prepl once. Doing some adhoc metrics collecting at work, I would connect to our normal-ish repo then invoke prepl to turn the normal reply into a prepl and then speak prepl maps back and forth evaluating forms to collect numbers. It seemed ok, but I definitely wasn't using all the bits and bobs

borkdude20:11:24

I could see the output from the io-prepl being useful to record repl sessions and print them in a readable (e.g. markdown) format:

borkdude20:11:12

./bb -e '(clojure.core.server/io-prepl)' <<< '(defn foo []) (foo)'
{:tag :ret, :val "#'user/foo", :ns "user", :ms 2, :form "(defn foo [])"}
{:tag :ret, :val "nil", :ns "user", :ms 0, :form "(foo)"}
Surely I'm going to use this output format for writing some tests