This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2024-05-03
Channels
- # aleph (62)
- # announcements (44)
- # babashka (2)
- # beginners (72)
- # biff (16)
- # calva (3)
- # clj-commons (4)
- # clj-kondo (2)
- # clojure (217)
- # clojure-brasil (2)
- # clojure-europe (21)
- # clojure-nl (1)
- # clojure-norway (12)
- # clojure-sweden (15)
- # clojure-uk (15)
- # clojuredesign-podcast (14)
- # clojurescript (21)
- # cursive (1)
- # datalevin (1)
- # datascript (4)
- # emacs (13)
- # gratitude (2)
- # helix (7)
- # integrant (5)
- # jobs-discuss (116)
- # lsp (4)
- # overtone (7)
Has anyone played with https://github.com/ryrobes/flowmaps ? The Rabbit demo is mind-blowing. If there’s any channel here exploring this area please lemme know!
but I usually try to avoid complex flows as much as possible, it would be interesting to see a “production example” of using this
Could Clojure implement print-method
for byte arrays? #object["[B" 0x14d916ef "[B@14d916ef"]
isn't very useful. JShell, for example, prints them like this:
byte[7] { 67, 108, 111, 106, 117, 114, 101 }
Also, I noticed Clojure doesn't print ASCII control character bytes at all:
jshell> new String(new byte[] { 0 }, "UTF-8");
$1 ==> "\000"
vs.
user=> (String. (byte-array [0]) "UTF-8")
""
(I know control characters are "unprintable", but the way JShell prints them seems more useful to me -- at least you know they're there.)Post on http://ask.clojure.org -- sounds like a reasonable enhancement. I think I'd vote for it.
This is what I have in my project:
(ns foo.bar.debug)
(def DEBUG?
(or (System/getenv "MY_DEBUG")
(System/getProperty "my.debug")))
(when DEBUG?
(defmethod print-method (type (byte-array []))
[b writer]
(print-method (vec b) writer)))
start the repl with MY_DEBUG=1, import the foo.bar.debug
ns, and byte arrays will look like [1 2 3]Yeah, I have this (most of the byte arrays in this project are UTF-8 strings):
(defmethod print-method (Class/forName "[B")
[^bytes v ^java.io.Writer w]
(.write w "#b ")
(binding [*print-readably* true]
(print-method (String. v "UTF-8") w)))
But it'd be nice to have a more useful representation built in, I think.It seems plausible that this could fire back for larger arrays Having a visual tool as part of your usual workflow helps here 🙂
Having a lot of numbers printed in output would seem noisy, most times There's `*print-length*` so yeah I don't think it would backfire spectacularly
I miss Common Lisp defpackage/in-package
. ie, define a hefty ns
and use it many places, so DRY. This is not like in-ns
, because a CL defpackage
can :use
multiple other packages.
I see two approaches. In one:
(ns my.namespace
(:require [some.lib :as lib])
(:import [java.util Date]))
...becomes:
In mono_pkg.clj:
(defn use-mono []
(require '[some.lib :as lib])
(import '[java.util Date])
Then in my_app.clj and a dozen other places:
(ns my-app
(:require [mono-pkg :as mono]))
(mono/use-mono)
...etc...
In a second approach, I just write a clever defpackage/in-package
pair that expands to a classic ns
form.
Anyone else have this same itch? Do we have known solutiopn? :thinking_face:I haven't used Common Lisp, but this approach seems to be only superficially different from what some other languages have built-in facilities for. Namely, re-exporting things from other packages under some specified names. You can make it work in JS, Python, and all the langs where importing is basically use
(R, Swift, C++, C, bash,...).
With that in mind, personally I'm quite happy that there's no such feature. :)
use-mono
is incredibly implicit and brittle (welcome to Alias lib already exists in namespace ...
) and it's just exchanging the cost of having to add a few more vectors to the ns
form for the cost of understanding and maintaining the thing. The exchange might make sense in your personal projects, but if you want other people to use those projects, it becomes more of a liability.
Invariably when I have to read such code where I don't understand every single dependency and the module graph, the amount of "WTH is going on" per minute rises dramatically. Especially when JS or Python folks try to be clever and do runtime introspection and use descriptors of all sorts just so they can save 3 lines in 5 files.
there's potemkin's import-vars
, but that's a bandaid over the issue
i think that it has it's place, and using it once or twice to share/re-export something can be good. however, i maintain an application that uses it to export roughly 600 functions, and that's a legitimate nightmare
Using import-vars
is a nightmare? Or not using it and having to "alias" 600 functions is?
I have to agree with @U2FRKM4TW. The explicit way the namespaces are declared makes code easier to read and aids in simple static analysis with things like grep.
building an application where one half of the app expects to call (:require [game.core :as core])
and get access to 600 functions, and then in the other half keeping track of name changes and namespace moves and excluding private/implementation details is a mess and adds a lot more work. it means one of the primary benefits of namespaces (no name clashes) is now moot and we've ended up adding ad-hoc namespaces to groups of functions (foo-x, foo-y) because we can't expect that someone will require the specific namespace and use the right function
i actually had to write a macro that expands to an IIFE (immediately invoked function expression) that wraps a call to import-vars
to get around method size because the number of top-level import-vars
calls was causing the jvm to throw "method too big" errors lol
but mine is a particularly bad situation, and most uses of import-vars
aren't this bad lol
A retelling of my interaction with a client of mine that works with Swift:
[We're checking out a commit on GitHub]
-- [me] Where does HttpRequest
class come from? Is it built-in or from some file in your project or some library?
-- [the client] I actually don't know, let's check. [Opens up Xcode, navigates to the file, tries to navigate to the definition of HttpRequest
- nothing happens, something is broken, which is a common thing with Xcode. Closes Xcode, runs a two-character alias in his terminal that clears all the Xcode caches because there's no other way to fix it, opens it up again. We wait for a couple of minutes till everything is indexed - now navigation works, hooray. It only took us 3 minutes instead of 0.3 seconds to see where the class is defined.]
And it's the same thing with all implicit importing. You cannot use it without tooling.
A more explicit approach, like import-vars
, might not require tooling. But it's still a PITA to navigate compared to the most direct approaches, and it's so easy for the author of that code to use import-vars
in a way that makes things dramatically worse (moving the macro to the middle of a namespace, using it multiple times, aliasing things not from your own library, and so on).
@UEENNMX0T I don't know the specifics of that app, and why an app needs to export functions (maybe you meant a lib?), but I find that it's more often than not that in similar cases it would be much better to split the API into separate namespaces. Both in Clojure and in other languages. But again, maybe your case is one of the exceptions to that, dunno.
@U2FRKM4TW ours is historical baggage: the game engine was originally in one namespace, then it grew "too big" and someone split it into multiple files but used (in-ns 'game.core)
in each file, and then i came along and made them actual namespaces but didn't want to touch the web server or test code that called those functions so i used import-vars to keep the game.core
entry-point, and now it's been multiple years and no one's put in the effort to fix this particular mistake lol
Well, even in the CL community there were two schools of thought, essentially mono vs granular packages.
In the granular camp, one advocate also wanted references prefixed every time, vs the CLJ trick of having refer
in the ns make a symbol available throughout that source.
I myself developed a humongous CL app and inadvertently did an alpha-beta test, first converting a mono approach to a granular approach with, say, a dozen packages covering sixty source files. It took a week and quickly proved to be a PITA to live with. Fortunately I could just build a mono "app" package by pulling in the dozen, so we were quickly back on our feet. The punch line being we never had a problem with the mono, contrasted with endless package issues before.
Indeed, I learned there is a Lisp saying about mysterious bugs: "It is probably a package issue. It is always a package issue."
One thing about the mono approach: name clashes get worked out exactly once, and we are done.
Maybe sth about CLJ will work out differently. I do know CLJ does not handle namespaces as well as CL when it comes to macros, forcing me to spell out references by reiterating complete paths to functions.
Something else that exacerbates the CLJ approach is both preferring small sources and initial development, where refactoring is common. Once an app gets to beta and the code is relatively stable, the namespaces have all been worked out and need only occasional attention. That said, in a library such as Matrix, a user normally needs to require a bunch of MX namespaces to get to all the functionality. That is unacceptable, and in the CLJD incarnation I have taken to artificailly defining an mx.api
namespace that pulls in other ns's, again an ugly solution.
Jes thinkin out loud.
> a granular approach with, say, a dozen packages covering sixty source files. If you mean completely different packages, each with its own version and source code location, then it's a completely orthogonal problem. Otherwise, it would be curious to know what the pain points were exactly. > "It is probably a package issue. It is always a package issue." Funnily enough, it has never happened to me with Clojure. :) JS/Python/R/C++ - yes. Clojure - no. Alas, no meaningful experience with other lisps, so can't comment on them. > name clashes get worked out exactly once, and we are done. Assuming you never have to add another name. It's a real problem in huge libraries. I used to be a somewhat active maintainer of a Python library called Bokeh. "What should we name this thing" was a frequent question discussed by multiple people for not insignificant periods of time. > I do know CLJ does not handle namespaces as well as CL when it comes to macros, forcing me to spell out references by reiterating complete paths to functions. Are you sure you're not mixing CLJ and CLJS up?
Clojure 1.11.2
user=> (require '[clojure.string :as str])
nil
user=> (defmacro m [x] `(str/join ~x))
#'user/m
user=> (ns other-ns)
nil
other-ns=> (macroexpand '(user/m [1 2 3]))
(clojure.string/join [1 2 3])
other-ns=>
Are you sure you're not mixing CLJ and CLJS up?
My experience has been that it relies on the including source aliasing consistently. Not the hardest thing in the world to arrange, but for a published library to rely on that is a tad fragile.
Actually, maybe this is only a CLJD issue?syntax quote in clojure (i don't know cljs or cljd) qualifies symbols at read time, so there's never any ambiguity
Please, don't use CLJS and other CLJ varieties when reasoning about problems in CLJ. There are enough differences that muddy the water, no need for that.
>> "It is probably a package issue. It is always a package issue." > Funnily enough, it has never happened to me with Clojure. dependency hell does though ... and it's Jackson ... it's always Jackson 😜
Dependency hell is language-agnostic. And Jackson is Java. :P With Clojure, incompatible dependencies are at least trivial to solve on a technical level (vendoring some deps into your project), and become a problem that needs some time to be solved only at the legal level (not all licenses are compatible with vendoring and your particular project).
Well, Matrix on CLJ is actually CLJC. And whatever way you cut it, an MX user needs several requires per source file to get to all the functionality.
This is just not an issue with Common Lisp defpackage
, which knows how to "package" CL symbols and present them to including source.
Can any CLJ do that?
clojure.core
doesn't give me clojure.string
and clojure.set
and all the rest. And I find it to be fantastic to work with.
having to import multiple things might be a little annoying, but that's pretty minor. in my work's codebase, nearly every file requires 5+ things (clojure.string/set/etc, database, honeysql, malli, countless other namespaces from the app). you require what you need and move on.
(imo clojure.core is too big and would have done well to move functions like subs
and re-*
to specific namespaces, but we're well past that now)
"Why is getting all the functionality a concern in the first place?"
I meant that an MX user could reasonably want to access two or three MX mechanisms in one source file, and instead of requiring one MX ns and then just coding mx/this and mx/that, they have to worry about which MX ns has what. To me that is leaking the internals of MX; a user should not have to deal with MX internal organization at all. And in Common Lisp, they do not have to because defpackage
.
> an MX user could reasonably want to access two or three MX mechanisms in one source file
If those mechanisms are roughly in the same category, I myself would like to use only a single require.
If those mechanisms are somewhat unrelated, I would definitely prefer to use multiple requires.
> they have to worry about which MX ns has what
It seems that you're thinking about your users from the perspective of your own preferences.
I love knowing where the stuff that I need comes from. I cannot call it a "worry".
It's a blessing that clojure.string
is clojure.string
and not a part of clojure.core
.
> To me that is leaking the internals of MX
If you split the API, then that split becomes a part of the API and isn't an internal detail at all.
In fact, you can have an API split that's completely misrepresentative of the internals. Not that I think it makes sense, but it's a possibility.
@U0PUGPSFR yea I've had that same itch. I've seen it be useful in other languages like Elixir. No good solution that I'm aware of.
"It seems that you're thinking about your users from the perspective of your own preferences."
I should have made clear up front that I am curious about the technical feasibilty of a CLJ defpackage
.
In the OP, the first question is about subjective experiences. ;)
It is technically feasible. ns
is just a macro, clojure.core
is available everywhere and, as a regular namespace, is mutable.
If you do something like that, almost every single tool will fail in figuring out what's going on. Same with any readers of your code that aren't you.
FWIW, the main general counterargument I've heard in the Clojure community is that if you are having a problem with repetition in your requires, you should consider having fewer namespaces.
I should add that the above only works with CLJ. It will definitely not work in CLJS. No clue about CLJD.
some packages have very large cores, such as malli (2600 lines) or spec.alpha (2000 lines). that's also a possibility here, just put everything into a single big namespace
@U08JKUHA9 I might've missed all those discussions, because I most definitely disagree. It's a spectrum, where both of the extremes are bad. The "repetition in requires" problem can be ameliorated by either combining required things together or by splitting the namespaces that have that problem. It's not rare that a combination of both gives the best results.
In other words, if I have 10 namespaces that all need 50 different and unrelated to each other namespaces, but that set is always more or less the same, it doesn't mean that I have to combine those 50 things into one thing. On the contrary - it probably means that those 10 namespaces should be completely reorganized.
> The "repetition in requires" problem can be ameliorated by either combining required things together or by splitting the namespaces that have that problem. Right, but that is what we are talking about - are there any good solutions for doing that in Clojure? Not really - it is tedious + manual, hacky and/or it breaks tooling.
I meant the "tedious + manual" way. Reorganizing your namespaces is not something you do frequently, so it's hardly a problem at all.
How they use it in Phoenix is you can define a grab bag of useful functions that are 'imported' to your views. Some of them can be your own, but they can come from frameworks and libraries too.
My only assumption is that I'm in control of the code that I write. If I need 50 requirements in 10 namespaces, it's my problem - it's not a problem of those 50 requirements.
Hmm that doesn't seem relevant to me. I guess you are saying you don't encounter this situation often because of your programming practices? It could also be your domain, and libraries you happen to come across though.
Well, any interesting library will have functionality that can be grouped usefully. In different source files/namespaces. Common Lisp does not have that problem because source files do not entail namespaces. I can have my source organization and namespace simplicity at one.
@U08JKUHA9 The issue is twofold.
One part is organizing your own code in a way that doesn't make the problem of repeating requires worse. That's completely under your control and can be solved to a reasonable degree by proper namespace organization.
The other part is organizing your own code in a way that doesn't make that very same problem worse for some other consumers of that code. Just as well, this is completely under your control to solve to a reasonable degree.
And it works pretty good in huge projects with however many people working on them - some examples are standard libraries of Java/Python/C++ (the latter puts everything under std
but you still have to include the right header).
Related library btw: https://github.com/clj-commons/potemkin > Clojure namespaces conflate the layout of your code and your API. For larger libraries, this generally means that you either have large namespaces (e.g. clojure.core) or a large number of namespaces that have to be used in concert to accomplish non-trivial tasks (e.g. Ring). > The former approach places an onus on the creator of the library; the various orthogonal pieces of his library all coexist, which can make it difficult to keep everything straight. The latter approach places an onus on the consumers of the library, forcing them to remember exactly what functionality resides where before they can actually use it.
@U2FRKM4TW I think we have a different outlook on this. You seem to be thinking of namespace organization as if the thing to consider if it is objectively good/true. I'm instead thinking 'is this collection of definitions useful for X?'.
I'm not sure I understand your message, but I think that any sort of organization is better than a lack of thereof. In Clojure, the main organization primitive is namespaces. There are no built-in secondary primitives, but you can invent your own mechanisms on top (not that you should). Other languages might have conceptually the same thing but name it differently, or maybe even the same. They can have additional organization on top of that. They can have completely different facilities for that. All are better than the absence of any organization - i.e. a complete inability to reference anything. All but one are better than the next worst thing - explicitly requiring files and receiving every single thing from those files, a.k.a. shell programming. All but two are better than the yet next worst thing - requiring a specific package and getting who knows what right in the global scope. C/C++ (if namespaces aren't used)/Swift (I think it errors out on collisions)/R (at least, up to 2.14.0). It's very "fun" debugging some BS only to figure out that two libraries define the same function and you got screwed over badly by not being omniscient. Other methods are harder to compare between each other. But in any case, an ability for a user to control what they want is better than a lack of such ability.
In Clojure, the main organization primitive is namespaces. There are no built-in secondary primitives, but you can invent your own mechanisms on top (not that you should).I don't think this is something the community can solve well, because there are tooling downsides to every approach. > But in any case, an ability for a user to control what they want is better than a lack of such ability. Then I guess at least we don't disagree in principle. One way to think about it is imagine if deps.edn didn't have aliases. We are kind of asking for deps aliases, just for namespaces.
tbh the only tooling that can't understand custom ns macros is static analysis tools like clj-kondo. if the code compiles, then repl-based development tools will understand it just fine
i don't know anything about cursive
Pretty popular Clojure editor that runs on IntelliJ, but is based on static analysis, not a REPL connection.
> I don't think this is something the community can solve well, because there are tooling downsides to every approach.
With having been exposed to some of the existing approaches out there, I don't quite see any possible approach that would be by and large better than what Clojure offers (if you subtract load-file
). Of course, I haven't seen everything and I would love to be proven wrong. But given how designers of languages that are keen on breaking stereotypes and improving the status quo by any means are still employing the same approaches w.r.t. packages, I'm quite pessimistic.
Of course, some approach might be just perfect for you, as you are right now at this moment, and within the context of the problems that you have right now. But after ruminating on it for more than a bit of time (granted, not in the most rigorous of manners), I don't think there's an approach that's better within the context of scale across time and people.
And if scale across time is irrelevant - you can write the code in any matter. You can use load-file
, you can use Clojure to generate new Clojure source files and load them at run time, you can do whatever.
If scale across people is irrelevant - just keep in mind that you today and you in a year are very different people. Maybe not in the "person" sense, but in the memory sense. So zero scale across people implies barely any scale across time.
The one thing I worry about is breaking tooling. That would be a show-stopper. So I am leaning towards a solution that macroexpands to the same ns
forms that are a pain to manage. I guess this would break any tooling that looks at my source vs the compiled result.
But ns
is just syntactic sugar, right? Can it be replaced with, as per the CL idiom, call-ns
? Then one "package" definition could serve multiple sources. :thinking_face:
@U2FRKM4TW It seems like you think I'm asking for something totally wild. I just want to be able to assign a name to a set of requires, then use those in multiple namespaces. Why would this be any more problematic than variables and functions, which are the same concept (for data and code)?
@U0PUGPSFR Who are the intended users of the "package" definition? You inside Mx? Or users of Mx?
If it's the latter, just don't do it. It breaks all user expectations and a lot of tooling.
If it's the former, load-file
and import-vars
are already a thing. You can ignore their issues if you like, but there's no need to invent something here.
@U08JKUHA9 Even just on the surface, it already has the Alias x already exists in namespace y
problem.
People use that very approach in other languages, via things similar to load-file
/`import-vars` or via some completely separate mechanisms.
It's fun to write such code. It's a bloody nightmare to understand and support it, especially when there are multiple people working on it. This is not me being puristic, this is me speaking from the experience of the proverbial blood, sweat, and tears. That's why I'm talking about the scopes of time and people and how certain things, despite how innocuous they might seem, can lead to disasters down the road.
The heuristic CLJ currently abides by is that if there is a symbol in the ns, you can trace it to somewhere in the file or to the ns in the file. Would this idea break that notion?
@U2FRKM4TW You would just warn or throw an error, same as today - doesn't seem like the end of the world. You may have some negative experience with it, but it it has worked in projects I've been on. So maybe it isn't necessarily a problem, just depends how it is used like most things.
@U050PJ2EU No, because it could still be static
@U050PJ2EU Static in the sense that a static analyzer could read the alias and see the requires, then inline them in your NS and proceed as before.
@U08JKUHA9 Nothing is the end of the world.
But if your library squats the alias lib
and imports java.util.Date
, just like in the OP, every single user of that library who wants to use lib
as their own alias or java.sql.Date
without having to FQN the name will be pissed off. Pissing off your users goes against "scale across people" in my mind.
@U2FRKM4TW Oh you are thinking global aliases? Not what I had in mind
Not global. The ones that you explicitly require in your ns. At least, that's the way I understood the original post.
Ah ok, I guess there are different ideas in this thread. I was thinking something like (ns foo.signup (:require-alias foo.view-utils))
@U08JKUHA9 So, would this proposed idea carry the same downsides as :use
? (which is still useful for some scenarios) Or does this idea not introduce similar ambiguity issues as :use
?
@U050PJ2EU Didn't mean to, changed it to require
You may have some negative experience with it, but it it has worked in projects I've been on.> So maybe it isn't necessarily a problem Or the scale of the projects to which you were exposed hasn't reached the breaking point. Or they just got lucky (niche audience, an incredible maintainer who remembers every file by heart, other things). Or your perception of "has worked" is different from mine. All four possibilities, including "maybe it's not necessarily a problem, depends how it's used", have equally unknown probabilities and are independent. Maybe there are other reasons for why we have different experience on the topic, dunno. As an example of the third "or" above - some people think that NPM works just fine. To me, it's a sad parody of Maven.
@U08JKUHA9 what's going on here? (ns foo.signup (:require-alias foo.view-utils))
I thought the proposal was more-so what Kenny mentioned, using a top level fn to require something in
Let's say in another file, you have this:
(ns-alias foo.view-utils
(require [foo.formatting :as f]
[foo.dates :as d]))
Then when you require it as v
, you use v.d.date-diff
(assuming date-diff was a function in foo.dates)@U050PJ2EU I wouldn't call it a proposal - more like a demonstration of the concept.
@U08JKUHA9 That can't work because v.d.date-diff
is not a valid symbol, unless it represents a class name.
If you mean v.d/date-diff
, that has the same problem of pissing people off because v.d
is an implicitly created alias.
@U2FRKM4TW Sorry yea that is what I meant.
@U2FRKM4TW how is it implicit?
The d
part of v.d
is not something a user has asked, but something that might easily affect their own code if they already have v.d
as an alias somewhere. Dots in aliases are a perfectly normal thing.
Of course you could, just like with regular clashes.
But if I require a library in a perfectly normal way but that throws an error because of a completely different :require
vector, I won't be a happy person at that moment.
I mean we are talking about new functionality, it wouldn't be implicit, it is just how the feature works. Same as referring to something under a java object, you don't need to import every little method call.
When referring to something under a Java object, you do it in a way that cannot possibly clash with already established aliases.
@U08JKUHA9, I thought you said the other alias wasn't passing through to the new ns? Isn't that what @U2FRKM4TW meant by "The d
part of v.d
is not something a user has asked"
> Why would there be a conflict? It is namespace local
(ns foo.signup
(:require-alias [foo.view-utils as v])
(:require [foo.view.date :as v.d]))
will lead to
Alias v.d already exists in namespace foo.signup, aliasing foo.dates
@U050PJ2EU I wasn't thinking about it that way, but it would be true in a sense. And it wouldn't be a problem if you just couldn't also use v
for something else.
@U2FRKM4TW that is an example of something that the compiler could just prevent you from doing.
That is precisely my point.
I don't want to be prevented from using v.d
by some foo.view-utilsns
because it wants to change my ns in a way that I do not fully control.
It not only pisses people off. It also creates brittle code.
Usually, additions are non-breaking. Your version of ns-alias
now has breaking additions because adding any alias there is a backwards-incompatible change.
If you needed complete freedom of your aliases in some namespace, for every prefix, you just wouldn't require an alias. Don't see the issue.
Because if I already have v.a
somewhere because I know that v
doesn't create any "sub-alias" that would clash with v.a
, v
can then add a
that would break my own code.
You can only break things if you add a :require-alias to your namespace. Are you complaining that if you make one change to your ns
, you may have to make one more change?
hmm, I'd have to go to the other ns to know where the fn actually came from though, right?
@U050PJ2EU If you are using notepad.exe then yea. But otherwise I think the editors would support goto definition (or a tooltip that shows it).
> Are you complaining that if you make one change to your ns
, you may have to make one more change?
No.
I'll try to make it as explicit as I possibly can.
Here goes the chain of events.
1. My project is started, I have (ns bar.core)
2. The project grows, there's now (ns bar.views.data)
3. bar.core
depends on bar.views.data
, so we now have
(ns bar.core
(:require [bar.views.users :as v.u]))
4. Still experiencing growth, we now need the lauded library foo
that does everything that we want in terms of views, so we use alias v
for its entry point
5. We notice that that library provides a feature that seems neat - ns-alias
. We start using it in our code, as suggested by the README of foo
, and we now have very convenient aliases v.d
and v.f
6. foo
gets a new version that adds an utils
namespace. It's a minor change that isn't perceived as breaking since it's an addition of a namespace that we don't even have to use
7. We upgrade foo
to that version, our code breaks because v
not "sub-aliases" foo.utils
as v.u
which we already hadAnd to add to what John has said - IMO tooling improving your experience is great. But having to rely on tooling sucks. There are plenty of cases where you don't have an easy access to a proper IDE that supports every single feature a language has.
@U2FRKM4TW again, the compiler would just not allow it. Having your own v.u
, and also having a require-alias :as v` would not be possible.
> the compiler would just not allow it "You update -> stuff breaks" is the definition of a breaking change. It does not matter whether it's at compile time or at run time. And it can still happen at run time if you have a more dynamic system than most.
> Having your own v.u
, and also having a require-alias :as v` would not be possible.
Now that's a different thing altogether.
In general, I think anything that could lead to problems or conflicts, it seems like you could just not allow it. I'm sure there are all kinds of things that could be problematic.
I think this would break the current contract that every symbol I see in front of me in a file I can know the fully unaliased namespace of, just by looking at the file, right?
Still a PITA for library authors:
1. A library has a single ns-alias
where all the reasonable things are stored
2. It decides to add an optional namespace that has dependencies that might be completely missing if the namespace is not used
3. The library cannot use that namespace within the existing ns-alias
at all
> Because of the intermediary namespaces people create today
Assuming "intermediary namespace" means a namespace with a whole lot of (def x foo/x)
, it doesn't break anything because it's fully explicit.
But that v.d
is "spooky action at a distance".
@U2FRKM4TW it breaks the contract that john was talking about
Yea exactly, maybe it would be bad feature for library authors. But I take that point @U2FRKM4TW, I agree it is a downside.
Yea I think often people would want to control it, can make more sense from the application side than the library side.
In Phoenix, the generated code you get in a new project does it that way, which seems good.
Yeah, there was a debate on clojureverse about the pros/cons of elixirs' import tricks for clojure
I agree with @U2FRKM4TW that I'd like not to need to depend on many tools
it breaks the contract that john was talking aboutI think I was talking about the same thing.
Right now, if I see v.d/x
, I can just look at the top of the very file I'm viewing to see that v.d
is an alias for view.data
or whatever.
With intermediary namespaces, I can e.g. see that v/x
is actually view/x
. The underlying thing might be in view.impl/x
or view.data/x
or anywhere else. But even if it's just (def x foo/x)
, it's still a proper var that has its own location and properties - no contract is broken.
But if I see v.d
and v
comes from ns-alias
, the mapping is to a completely different namespace, without anything of use, except for the pointer, being mentioned in the file where ns-alias
is defined. The file to which v
points.
I don't see anything conceptually wrong with ns-alias
at the moment. Of course, it doesn't mean that there's nothing wrong with it - I just have no idea.
But with the trade offs of the current approach and the ns-alias
approach from the perspective of both users and maintainers, it still doesn't seem to be a clear win. Maybe worth a post on "Ask" if you feel like it.
@U050PJ2EU I am very much a "go-to-definition driven developer". :) I read third-party sources much, much more often than the corresponding docs. But there are cases where tools just aren't there, and in those cases having to rely on them is not fun.
@U2FRKM4TW yea I understand. But that is kind of a letter of the law vs spirit of the law type thing. I'm thinking more of the spirit here.
But in a project I usually don't regret seeing all usages of thing and I usually find the def I'm looking for pretty quick
The thing I like most about go-to-def, besides the normal jar navigation, decompile, show sources for java, stuff outside your project, etc, is that there also is a back button, so you can go 'in' and back very quickly (potentially multiple layers)
Offtopic, but FWIW Cursive is quite reliable here. If anything, it might fail on the side of "I don't know where to go". When the project wan't properly imported, when the var is created by a macro, or when it's an interop with ambiguous types. But the real thing that stops me from using anything else is how IDEA itself works with Java. I read Java code all the time, including the Clojure's one, including decompiled classes. Would be nice to have something on the VSCode side for that, but I can't imagine how many human-days must be put to it.
parinfer is my favorite crutch and I try to go as lean as possible tool-wise outside of that
Mostly just because I get super pissed when a tool fails and I forgot what to do instead
Embrace the jank. :) (Not the lang, or not just the lang). Not too dissimilar from TCP vs UDP.
TCP is "we gotta do more stuff and it incurs some penalties, but at least there are specific guarantees". UDP is "gotta go fast, every packet for itself!". Aka things can and will fail, but it should all be used in contexts where such failures are tolerable.
Interesting thing about transformers - ancestors impls follow descendent impls, so requiring in one transformer is like requiring in its whole implementation history, which might span 3 different libs, at different layers of an abstraction. Because impls aren't closed over, ancestor impls are available ad hoc for usage in and from the function you're requiring in. So if http-transformer
gets transformed into github-api-transformer
, then a user requiring in github-api-transformer
could pull in, change, use or provide impls from http-transformer
without actually requiring in http-transformer
. Perhaps not exactly like in-package
but does achieve more DRY in maybe a similar way.
And of course the ability to transform things follows the transformer and doesn't need to be required in from some transformers library
Hi all, I have a question. Given these transducers:
(def even (filter even?))
(def odd (filter odd?))
using their compositions ends up in somewhat "logic AND" behaviour:
(transduce (comp even odd) + (range 10))
=> 0
is there a way to compose them to behave like an "OR"? Thanks 🙂no, not for the filter transducers. but you could "or" the predicates before building the transducer.
i don't think so. each of the step arities return the result, so you can't know if a given transducer "worked" or not
Thanks a lot to all! Will do as @U017HM6BG07 suggested as I realized it is the only way.
You can do it if you need to, but it's ugly and weird and (filter (some-fn even? odd?))
is way easier to read and write 😉
(defn xform-or-comp [& xfs]
(fn [rf]
(let [v (volatile! nil)
fake-rf (fn [r x]
(vreset! v true)
(rf r x))
steps (mapv (fn [f] (f fake-rf)) xfs)
step-fn (fn [r x]
(vreset! v nil)
(loop [fs steps]
(if (seq fs)
(let [f (first fs)
y (f r x)]
(if @v
y
(recur (next fs))))
r)))]
(fn
([] (rf))
([r] (rf r))
([r x]
(step-fn r x))))))
(comment
(into [] (xform-or-comp (filter even?) (filter #(zero? (mod % 5)))) (range 10)) ;; => [0 2 4 5 6 8]
(into [] (filter (some-fn even? #(zero? (mod % 5)))) (range 10)) ;; => [0 2 4 5 6 8]
)
but if you've only got two opaque transducers and you want to do it, that might work?When you know for certain that at least the final transducer is filter
, this could be used instead.
But yeah, also not pretty at all, and it's incomparably better to just use some-fn
on preds where possible.
(let [extract-filter-tx-pred (fn [flt-tx]
(let [f (flt-tx (fn [_result _input]
true))]
(fn [item]
(f false item))))
flt-tx1 (filter even?)
flt-tx2 (filter odd?)
data (range 10)]
{:even (filterv (extract-filter-tx-pred flt-tx1) data)
:odd (filterv (extract-filter-tx-pred flt-tx2) data)
:either (filterv (some-fn (extract-filter-tx-pred flt-tx1)
(extract-filter-tx-pred flt-tx2))
data)})
=> {:even [0 2 4 6 8], :odd [1 3 5 7 9], :either [0 1 2 3 4 5 6 7 8 9]}
I think the question was more about arbitrary predicates, which is what some-fn
and every-pred
are for, right?
I would just use different transducer pipelines and re-join them. Or you could tee the data (map #(do [% %]))
and filter over both streams and then rejoin them later in the pipeline
That will only work if the predicates are mutually exclusive and the order in the resulting collection doesn't matter.
@U017HM6BG07 I was really silly trying to compose filters, just composing predicates made it very straightforward:
(transduce (filter (every-pred even? odd?)) + (range 10))
=> 0
(transduce (filter (some-fn even? odd?)) + (range 10))
=> 45
thanks again all for your valuable hints! 😅coworker just ran into this. Anyone know what is wrong here?
foo=> (->> (clojure.reflect/reflect java.sql.Connection)
:members (filter (comp '#{isValid} :name)))
(#clojure.reflect.Method{:name isValid,
:return-type boolean,
:declaring-class java.sql.Connection,
:parameter-types [int],
:exception-types [java.sql.SQLException],
:flags #{:public :abstract}})
foo=> (reify java.sql.Connection
(isValid [_this timeout]
))
Syntax error (IllegalArgumentException) compiling reify* at (REPL:95:1).
Can't define method not in interfaces: isValid
java.sql.Connection
is an interface with a method isValid(int timeout)
but we can’t seem to reify itcontrast with
foo=> (reify java.sql.Connection
(getSchema [_]))
#object[foo$eval181075$reify__181076
"0x246a5bea"
"foo$eval181075$reify__181076@246a5bea"]
user=> (reify java.sql.Connection (^boolean isValid [this ^int timeout]))
#object[user$eval140$reify__141 0x25b865b5 "user$eval140$reify__141@25b865b5"]
weird, now it’s working
foo=> (reify java.sql.Connection
(isValid [_ timeout]))
#object[foo$eval181097$reify__181098
"0x18fbf2f4"
"foo$eval181097$reify__181098@18fbf2f4"]
if you omit any return value, that seems to be relevant
It's working for me now without the type hints too...
ditto, very weird
i’m not able to see any difference in these two
foo=> (reify java.sql.Connection
(isValid [_ timeout]
))
Syntax error (IllegalArgumentException) compiling reify* at (REPL:121:1).
Can't define method not in interfaces: isValid
foo=> (reify java.sql.Connection
(isValid [_ timeout]))
#object[foo$eval181109$reify__181110
"0x34d9ae3b"
"foo$eval181109$reify__181110@34d9ae3b"]
1.12 Alpha 9 -- works for me:
(~/clojure)-(!2008)-> clj
Clojure 1.12.0-alpha9
user=> (require 'clojure.reflect)
nil
(->> (clojure.reflect/reflect java.sql.Connection)
:members (filter (comp '#{isValid} :name)))
(#clojure.reflect.Method{:name isValid, :return-type boolean, :declaring-class java.sql.Connection, :parameter-types [int], :exception-types [java.sql.SQLException], :flags #{:public :abstract}})
(reify java.sql.Connection
(isValid [_ timeout]))
#object[user$eval140$reify__141 0xecfbe91 "user$eval140$reify__141@ecfbe91"]
user=>
I'm on 1.11.3 and was able to get same code to both fail and succeed
Explains why it worked for me when I just typed it in manually...
good old zero width space does it again
@U2FRKM4TW what mode shows the non-printing characters like that?
@U42REFCKA maybe you could try a tool like https://github.com/camsaul/whitespace-linter 😉

@U11BV7MTK vim --clean
shows it, so the default setup.
Cursive (or IDEA itself?) also renders it without me asking.
VS Code seems to highlight weird characters like this -- not sure if that's the default or whether I've enabled some specific setting:
@U11BV7MTK apparently there's a glyphless-display-mode
minor mode now https://www.gnu.org/software/emacs/manual/html_node/elisp/Glyphless-Chars.html
ZWSP
for zero-width space I guess