This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-02-16
Channels
- # announcements (18)
- # architecture (12)
- # babashka (20)
- # beginners (32)
- # biff (21)
- # calva (81)
- # clerk (6)
- # clj-kondo (16)
- # clj-otel (5)
- # cljsrn (8)
- # clojure (94)
- # clojure-austin (1)
- # clojure-australia (1)
- # clojure-europe (68)
- # clojure-nl (2)
- # clojure-norway (6)
- # clojure-uk (2)
- # clojurescript (13)
- # conjure (1)
- # core-logic (1)
- # cursive (7)
- # data-science (2)
- # datahike (3)
- # datomic (12)
- # emacs (33)
- # etaoin (1)
- # fulcro (8)
- # graalvm (2)
- # graphql (1)
- # honeysql (1)
- # hyperfiddle (97)
- # improve-getting-started (40)
- # jobs (2)
- # jobs-discuss (12)
- # lsp (9)
- # membrane (6)
- # nbb (2)
- # off-topic (16)
- # portal (6)
- # re-frame (2)
- # reagent (3)
- # releases (2)
- # remote-jobs (1)
- # tools-deps (7)
- # xtdb (38)
So I was a good little boy and split my O/S library code up into different files/namespaces according to some sensible quality, and everything seems tidy, but...now I am using the library and I have to require
a ton of different namespaces. It is not fun.
So do I:
• get used to it;
• get rid of the small namespace/files. Pile all the code into one or two;
• create a core NS that has functions of the same name that call the NS function; or
• other ______________________.
Any advice appreciated! 🙏
to your degree of taste
optionality has a lot to do with it imo - if the consumer always needs 3 namespaces to do anything, well maybe that should just be one
you can also make a consolidation namespace, that aliases forms from other name spaces with different names. sometimes that's a nice way to do things
(defmacro defalias
"Defines an alias for qualified source symbol, preserving its metadata (clj only):
(defalias my-map-alias clojure.core/map)
Cannot alias Cljs macros.
Changes to source are not automatically applied to alias."
;; TODO Any way to reliably preserve cljs metadata? See #53, commit 2a63a29, etc.
([ src ] `(defalias ~(symbol (name src)) ~src nil))
([sym src ] `(defalias ~sym ~src nil))
([sym src attrs]
(let [attrs (if (string? attrs) {:doc attrs} attrs)] ; Back compatibility
`(let [sym-meta# (meta (var ~src))
;;(select-keys (meta (var ~src)) [:doc :arglists :private :macro])
attrs# (merge sym-meta# ~attrs)]
(alter-meta! (def ~sym @(var ~src)) merge attrs#)
(var ~sym)))))
"consolidation namespace" I like it! The term, anyway. Sadly, my API is 80% macrology.
Thx also for defalias
! 🙏
In my opinion, all that aliasing junk is a mess. If you want it all in one namespace, put it all in one namespace in the first place. You can still split it into files if that helps you manage it on your side and use load-file (which is how clojure.core is defined)
"load-file"? Sounds promising. 🤞
sorry, load
in this case (see https://github.com/clojure/clojure/blob/master/src/clj/clojure/core.clj#L6822)
i agree with alex, however sometimes it's convenient to have namespaces that use the same symbols, and then rename them in the consolidation namespace
I remain unconvinced :)
i think it's pretty rare, and i probably did this in one of my code bases without knowing better
The doc on load
is a little thin, or at least the doc I found. The question is, what NS gets assigned to what? I gather from the clojure.core
example...well, it must resolve to that. But does that mean the loaded NS name is ignored? I cannot imagine the NS is left blank. I'll do some experimentation. 🔬 Thx for a good lead! Seems ideal.
it is loading in the current namespace
because it does not call ns
namespaces do not have to match 1-1 with files
require
finds a namespace to load by mapping the namespace to a file name, then the ns
macro at the top is what creates and sets the namespace context for the subsequent load
Other... I typically thing of namespaces in a project as a tree (in winter, so there are only branches and no leaves). The main namespace is the trunk of the tree with a few major branches. Each branch may have a small number of branches. This minimises the requires in each namespace. I will start with one namespace and divide that into sections using line comments (using a snippet). Creating these sections adds to the readability and discover-ability of the overall namespace. Sectioning also helps identify which parts of the namespace may be useful to section off into their own namespace. I try avoid breaking up into namespaces across the tree as this creates complexity. The exception being a specification namespace which would be defined as a single namespace at the top level anyway. I try focus how valuable the logical separation into separate files is, mainly for readability and comprehension of what the code is trying to achieve. This keeps the projects pretty flat and easy to refactor.
In your case, you could just make a helper that requires them all. Well, ok, that'll also not work well with tooling probably :white_frowning_face:
your IDE should place the requires for you.
Similarly if you use an aliasing convention, you can infer that foo
stands for myproject.foo
. So aliasing shouldn't have a cognitive cost.
With clj-refactor latest, if you hit foo/
anywhere in your code, [myproject.foo :as foo]
will be automatically inserted in the ns form, even if foo
wasn't used as an alias anywhere in the project.
It's magic!
clojure-lsp also has similar functionality I believe.
Thx, @U45T93RA6, but I should have emphasized that the context here is not wanting to bedevil users of my library with excessive requires. I mean, it annoys me, too, which is why it concerns me, but even with IDE conveniences, it seems like sth I should cure.
Yeah it's a delicate topic, and also one there will never be a definitive truth for. One could argue that consumers can also enjoy a non-monolithic API, for conceptual reasons. If those concepts (namespaces) make sense on their own, you're giving consumers a chance to learn about your project one bite-sized chunk at a time. And to disregard concepts that might be irrelevant for their use case. It also seems perfectly possible do to both approaches at the same time, i.e. separate namespaces + one opt-in namespace merging all those.
But for libraries like that, normally I do what Alex said. Once I'm ready to release, I reconsider if certain ns should be merged together or not to improve ergonomics. If I still want more segregation on my side on the implementation, I create like a "Facade" NS, and I expose functions that call others underneath. Or I do something like: (def fn other/fn) And also copy the meta of other/fn to fn so it has the same doc and all. Doing that for macros is trickier, there's still a way to do it though, but I already forgot what it is. Maybe that's similar to the defalias? Though I would not call this aliasing really, I'm declaring new Vars and setting their function in them.
Having to require more namespaces is a bit of extra work for the library user but it can give more optionality to the library maker (e.g. a namespace becomes it's own dedicated library without breaking backwards compatibility). So as a library user I don't mind having to require in a bunch of namespaces if the trade off is the library has more flexibile and future proof. One approach I take when creating a new namespace is pasting in a bunch of require statements, writing the code then having the code linter to tell me which require statements I can delete at a later date. So going to your question original question, I would lean towards (A) "get used to it" and (B) "get rid of the small namespaces/files" - if you really don't need the flexibility after all.
Assuming I've created a practicalli.service.utils
namespace with a couple of functions and they were evaluated in the repl. Then decide I didn't actually need the namespace or functions.
What happens when I use https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/remove-ns to remove the namespace?
Are the function definitions in that namespace removed from the REPL, or do they remain even though I assume they are unreachable?
Assuming I wasnt using clojure.tools.namespace.repl/refresh
, would I first remove each function definition from the REPL via ns-unmap
and then use remove-ns to remove the (now hopefully empty) namespace.
there is a map of namespace names to namespace objects (which themselves are a sort of map) remove-ns just removes the entry for the named namespace from that map
I am sure at some point in over a decade of using clojure I've called remove-ns, but I cannot remember it. hard to see the point.
it doesn't even touch the state that require keeps to avoid double loading, so if you require a namespace, then remove-ns it, then require it again it won't load the second time
Yeah, I tried using remove-ns
for a while and was quite puzzled by its apparent behavior -- hiredman's comments make sense of my puzzlement (and a couple of tests in the REPL verify he is correct).
Here's what I use nowadays in the occasional situation that I want to "clean up" a namespace in-memory and then re-eval (parts of) it: https://github.com/seancorfield/vscode-calva-setup/blob/develop/calva/config.edn#L23-L39
I want to learn more about parsing data, grammars, schemas, coercing, transforming, term rewriting, just to name a few keywords. I would like to understand the foundation better so I can use libraries like Malli and Meander to work with models of all sorts of shapes and be able to parse them meaningfully and then transform them powerfully in a declarative and scalable way. I understand if this is a bit of a broad request, but I'm looking for any book recommendations that come to mind 🙂 Edit: it does not have to be a Clojure book.
Very nice 🙂! These do sound a little advanced for my level right now though
I'm not going to pretend I read all of them, but you asked and I poked Joel's head for the details some time ago, so might as well keep them at hand I also found those books tend to look insurmountable but if you just pop one open and begin it Just Works (TM)
Definitely useful indeed. Interesting books to read for years 😉
I'd recommend any of the books by Terence Parr too.
Thanks all 🙂. Are these books mostly about parsing string-based languages? Any tips maybe on languages that are defined in other structures, most notably graphs or hash maps?
Why is (+ nil ) an error and not 0 in clj like it is in cljs. I would like to think of nil like the emptyset and behavior doesnt fit. My guess is, it's because the host does it that way.
There are a lot of cases -- in both clj and cljs -- where the behavior is "undefined" and so you get whatever the underlying host platform does.
but thinking of nil as the empty set doesn’t seem to help much here. (+ #{})
doesn’t sound very well formed either
Clojure 1.11.1
user=> (+ "x")
Execution error (ClassCastException) at java.lang.Class/cast (Class.java:3946).
Cannot cast java.lang.String to java.lang.Number
user=>
but
> + "x"
NaN
>
(and Clojure certainly has a NaN)I'm guessing that there's an issue in type inference in CLJS - you should've been given a similar warning for (+ nil)
:
cljs.user=> (+ "a")
WARNING: cljs.core/+, all arguments must be numbers, got [string] instead at line 1 <cljs repl>
"a"
Right - here's a part of the implementation, note the TODO:
(defn numeric-type?
#?(:cljs {:tag boolean})
[t]
;; TODO: type inference is not strong enough to detect that
;; when functions like first won't return nil, so variadic
;; numeric functions like cljs.core/< would produce a spurious
;; warning without this - David
(cond
(nil? t) true
(= 'clj-nil t) true
(js-tag? t) true ;; TODO: revisit
:else
(if (and (symbol? t) (some? (get NUMERIC_SET t)))
true
(when #?(:clj (set? t)
:cljs (impl/cljs-set? t))
(or (contains? t 'number)
(contains? t 'long)
(contains? t 'double)
(contains? t 'any)
(contains? t 'js))))))
We just had another thread saying (int \8)
differed across clj/s and I bet that's another weird hosted difference that is only partly papered over by clj/s.
I'm hitting a case where I can eval a form but I can't eval the macroexpansion of the form :
(require '[com.rpl.specter :as sp])
;; pasting this at the repl works just fine
(let [n 0]
(sp/select [:b n] {:b [{1 2} {2 4}]}))
;; but pasting the output of the macroexpansion doesn't
(clojure.walk/macroexpand-all
'(let [n 0]
(sp/select [:b n] {:b [{1 2} {2 4}]})))
looking at the macroexpansion make sense it should't work since it is refering to (var n) which doesn't exist (it is a local binding, not a global var)
what am I missing?It sounds like you aren't just doing the expansion differently, but are also introducing serialization through the repl printing out the expansion and deserialization when you paste it back into the repl
I don't think it is related to serialization since you get the same issue when you do
(def form
(clojure.walk/macroexpand-all
'(let [n 0]
(sp/select [:b n] {:b [{1 2} {2 4}]}))))
(eval form)
Any clean way to match a range (which is too big to write out in code)? Looking to do something like:
(case foo
(0x8000 => 0x9fff) ...
0xff40 ...
...)
Surely I can cond
here, but was curious if I can do better. core.match doesn't seem to have anything for this either.Something like this?
(def vals {0x8000 0x9fff ...});
(get vals foo)
I don't think there's anything better than cond
along with the fact that you can pass multiple arguments to the comparison functions.
No, that's mapping 0x8000 to 0x9fff. I'm looking for anything within the range of 0x8000 and 0x9fff to be matched.
the size of the code that is generated for case is proportional to the number of values to test against
is it ok to use decimal for this example? can i make a range as [0 1] is that good enough?
(case x
1 :a
10 :b)
decompiles to :
tableswitch {
2: 76
3: 108
4: 108
5: 108
6: 108
7: 108
8: 108
9: 108
10: 108
11: 92
default: 108
}
which is interesting since there is already kind of a range thing going on therenot that it answers your question, just interesting
you can use core.match with guards to match on a range if you represent it as a vector
@U0LAJQLQ1 Base 10 or 16 doesn't change anything here. Same question applies. 🙂
but, how do you want to do dispatch on a range, exactly? core.match to me sounds like you are interested in partial matching
The equivalent of:
(cond
(and (<= 0x8000 addr) (<= addr 0x9fff)) ...
(= 0xff40 addr) ...
:else ...)
qp=> (let [between? (fn [[low high] x] (<= low x high))]
(condp between? 17
[0 10] :first
[11 20] :second
[21 30]))
:second
@U11BV7MTK Hm. With that, I'd need to have the fn handle single values, too, for the 0xff40 case.
(let [target-range-start 0
target-range-end 5
test-point [1 1]
test-range [1 5]]
(clojure.core.match/match
test-range
[target-range-start target-range-end] :exact-match
[(_ :guard #(>= % target-range-start)) (_ :guard #(<= % target-range-end))] :between
:else :no-match))
Hm, thanks for sharing that, @U0LAJQLQ1. Match guards are neat.
i feel they get in the way of the pattern, too many of them and pattern matching fails at what it's good at (documentation)
i think a nice way to do this is to make a function that output something that a case statement can use (keywords). then the case can dispatch on something that is easy to read like the things i returned in the pattern matching
Just cooked it up with cond
. It's tolerable. https://gist.github.com/jeaye/274dfc6ffb0be4e020fcffd3bacd5f22
yeah, that's close to what i mean. i guess i like making the documentation more a part of the code
You can put all the constant comparison in a case
and the cond
part for ranges into its default branch.
Something like
(case addr
0xff40 0
0xff41 0
(0xff42 0xff43 0xff44) 0 ;; Note that you can also group constants.
; else
(cond
(<= 0x8000 addr 0x9fff)
(.getUint8 (-> emu :gpu :vram) addr)
...))
I think it also depends on the order of the clauses, since a cond can also be faster than a case https://insideclojure.org/2015/04/27/poly-perf/
I am using deftype
to implement an instance of a protocol, and it might be handy to have instances of that type hold on to some discrete bits of internal state. Is there any drawback to using an ordinary map internally to represent this mutable state? I intend to keep this state fully private and internal to instances of this type.