I don't get it. Then why can't I load a namespace I create with create-ns
the same way wtv ns
does?
ns does more than just create the namsepace, it also registers it as something loaded
Right... so now we are full circle no? Why doesn't create-ns
does that as well?
so that the next require (without something like :reload
) doesn't attempt to load it
because create-ns is a much lower level tool, iirc ns uses create-ns
Hum, not from the code I look at
I mean, ns is a lot more complicated
I guess what confuses me here, is the concept of a namespace vs a lib and something that is loaded and not?
So I can create a namespace, but I can't ever load or require it?
That seems weird
loading and requiring need something to load or require from
if you are making something from scratch, you want create-ns
or ns
(you can also use in-ns
but that's not useful)
I mean the only difference is that ns
seem to add the name to loaded-libs.
and changes your current namespace
which you probably don't want in the middle of a file
Ya, I guess the magic is inside in-ns
but I think that's a special form in JAva
And ns
will auto-refer in clojure.core
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
(which is why if you do (in-ns 'foo.bar)
in a fresh REPL, you suddenly don't have any core functions accessible!)
@didibus What do you mean by "doing it differently"?
Using ns
it creates a namespace that I can then require
. But not so for create-ns
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)))
using require for something that doesn't have anything you can load is an off-label usage
ns
adds the namespace to the list of loaded libs so require
doesn't need to load it from disk.
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
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=>
So I understand the implementation is why they differ. But I don't understand the conceptual reason or difference.
Like what is loaded
when you use ns
with no associated file or class
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
.
Exactly, and that's why I don't understand why create-ns
doesn't also add it to loaded-libs
So ns
= create-ns
+ add to loaded-libs + refer clojure.core
+ in-ns
.
Maybe just an omission, or is there some conceptual reason
Hum, so I guess with that my question is, why doesn't create-ns
adds it to loaded-lib ?
I think because it is a lower-level function than the ns macro
Because it's a low-level primitive. Yeah, what he said.
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
That doesn't make it a higher-level function. It just means it does 2 things instead of 7
Haha, true true. I guess it just feels like not low level enough, and not high level enough
If create-ns did not add it to the global list of namespaces, then all-ns
would not include such namespaces.
hardly anyone uses it in applications, I expect.
So would it be safe for me to add a create-ns
namespace to loaded-lib ?
why? what's the utility here?
just to create aliases without calling alias
?
The namespace isn't going to contain anything so what would be the use in requiring it?
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.
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)
Well, ya I can use alias, it just seems a bit inconssistent
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
also, nothing stops you from doing using (create-ns :account)
and then:account/type
- it doesn't need aliasing to work
if you just did :account/type
doesn't this just go away?
if you have more than one thing that would shorten to :account
in one codebase you are doing something weird anyway
You don't need to create a namespace for :account/type
@dpsutton exactly
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
if you are actually using the spec you will error on one side or the other for the spec not being found...
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})
That creates conflicts
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 🙂
Is there a want for symbol aliases that don't require reified namespaces as well? Or just for keyword aliasing?
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.
I don't have one either, was just curious.
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
Bleh
Stateful non-lexical scope is gross
Is it not lexical? I think it is
If you look at just in-scope by itself what does it mean?
You have no way to know
Or I guess defalias by itself
Well, you know that every line of code after it which calls defalias
will use the in-scope namespace
Same as in-ns
Bleh
Hate it :)
The only "tricky" part, similar to in-ns
, is that the scope isn't put inside of a delimiter block, but is flat
Haha, well I agree with you a little. But the alternative was:
(with-scope [<http://com.app|com.app> :alias [user]]
(defn ...)
(defn ...)
(s/def ...)
(s/fdef)
(def)
)
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.
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.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.
(ns com.org.app.data-model ...)
(alias '[<http://com.org.app|com.org.app> :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
;; => :<http://com.org.app/user|com.org.app/user>
::other-user/name
;; => :com.org.other-app.user/name
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.
I don't know what problem you're trying to solve at this point
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
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.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)
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.
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)
when you say "refactoring", I hear "breaking stuff"
if you don't like stuff broken, don't break stuff
Well, since the spec registry is global, and keys outlive an app, I'd rather not couple them to some particular code namespace.
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: :<http://my-app.name/name|my-app.name/name>
. But now again, the short alias makes this difficult, because this does not work: ::<http://app.name/name|app.name/name>
is an errorSo that last issue is why when I alias, I'd like to be able to refer to the "parent namespace" like <http://com.org|com.org>.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.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
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
it seems like it's impossible at that point to have any idea what that actual keyword is
(obviously it's possible, but it would require walking through several distinct mappings to get there)
I have not seen any other cases where someone was taking aliases to this degree of phased construction
Well, most people stopped using them, and instead hand type: :my-app.entity/key
I see that a lot: :cool-app.user/name
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
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.
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.
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 <http://com.org|com.org>
and hope there won't be app name clashes.
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).
I think you're just making life harder for yourself (and you know my opinion on this already I suspect 🙂 )
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
It is a problem when speccing your domain model though. Data that will outlive your code over time.
<http://com.org|com.org>
is a straw man.
Haha, I mean replace org with the name of your org: com.worldsingles.user/id
A few people say "don't use ::
"
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.
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)
I don't think that's a problem I can fix :)
Are you using Datomic?
@alexmiller 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
?
"Do you purposely try to find a shorter name" - generally, no
Qualification of keywords is about how unique you need the names to be.
if relevant, I think entity would be part of the qualifier
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 "<mailto:john@gmail.com|john@gmail.com>"})
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.Your refactoring was wrong then.
As Alex says, if you don't want things to break, stop breaking them.
The database doesn't care about namespace qualifiers.
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.
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 "<mailto:john@gmail.com|john@gmail.com>"})
Except now you wish there was a more convenient syntax for these loooooong namespaces on your keys you have everywhereThose are real ugly in destructuring code as well
like #:com.my-company.my-service{:id "123" :name "John" :email "<mailto:john@gmail.com|john@gmail.com>"}
?
and like (let [:com.my-company.my-service/keys [id name email] ...)
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.
yes you can
Can you?
Yes.
use 2 :...keys
and both of those also support ::alias/ syntax too
I have no problem with ::
(just for the record).
(with the pesky constraint that the alias has to be a real namespace)
Hum, ok I didn't know that. So I just prepend a number to it?
I'm perfectly happy to use ::alias/key
for the shorter name.
no, I was just you could use two different :http://some.name/keys in the same destructuring map
{::foo/keys [a b c] ::bar/keys [b c d]}
^^
for a map {::foo/a ... ::foo/b ... ::foo/c ... ::bar/b ... ::bar/c ... ::bar/d ...}
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: <http://com.org.app|com.org.app>
That's my problem I'm looking a solution for
I think aliases are the solution to most of that
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?Aliases.
(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]))
although tbh, I'm perfectly fine with the original :)
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]))
as I said, yes
g'night all, stepping away
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.
Thanks Alex, much much appreciated. Have a good night.
And thanks Sean as well for the input
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
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.
I'm looking forward to it. And ya, all my attempts have had issues.
Though this last one seems the best yet. I was surprised that I can't require... but using alias will do
Well the “want” is easy - aliases that don’t require reified namespaces. Lots of interesting choices in the impl
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?
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:
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)
@javahippie If you call instance
multiple times you should get the same object every time
the only exception to this would be if some other method on ResourceReaper cleared out the static field
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
maximum cringe at the death note references in the code though
it doesn't even make that much sense
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.
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
@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.
(which is particularly useful when the read fails after a certain character I think)
@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 %)"]
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?
(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})
)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?
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 😞
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
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:
./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 testsJust 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.