This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-06-09
Channels
- # announcements (12)
- # babashka (22)
- # beginners (17)
- # boot (6)
- # calva (45)
- # clj-kondo (17)
- # clojure (70)
- # clojure-australia (4)
- # clojure-europe (35)
- # clojure-finland (6)
- # clojure-losangeles (2)
- # clojure-nl (1)
- # clojure-uk (2)
- # clojured (26)
- # clojurescript (10)
- # conjure (1)
- # datahike (1)
- # events (1)
- # honeysql (14)
- # introduce-yourself (5)
- # jobs (5)
- # joyride (2)
- # minecraft (6)
- # off-topic (5)
- # pathom (14)
- # rewrite-clj (1)
- # shadow-cljs (13)
- # tools-build (6)
- # tools-deps (13)
- # vim (29)
- # xtdb (8)
not an important question, just wondering: why doesn't clojure implement one-way ports? where one can only put and not take or only take and not put?
If you can put but can't take how would you get data from it?
Assume that someone else is already getting data from it, eg I set up a real channel then split the ports and hand pish to one guy and pull to another
I know one can already do that by convention but there's something a bit odd to me about all these channel combinators purely relying on convention that some are only to be read and some only written
I guess there's probably some reason why these two things are tied together but I don't know it
Pragmatically, there do seem to be a lot of times in code that I've written that "only I push to this channel" is a fundamental assumption that there's no possibility of carving open, and if one did want to having the ports tied together only makes it easier for your readers to do that, and in a weird way they're maybe less likely to become alternate write sources as they're downstream in the information flow
I don't have a string opinion about this was just curious if anyone knew a deep reason rich chose it to be this way
Internally there are different interfaces for read/write, but the built-in channels implement both. https://github.com/clojure/core.async/blob/master/src/main/clojure/clojure/core/async/impl/protocols.clj#L15-L19
I saw that, they're under "impl" though which I maybe misinterpreted as indicating "do not touch"
the simple reason is that if someone has to be able to write to a channel, presumably someone else is reading
I guess one could easily implement a record that does this in clojure, just hide the channel and forward the reduced interface members
@U63D7UXJB this is just a general thing in clojure, sometimes called garbage-in, garbage-out, but maybe more accurately it's just that in Clojure code we can and do make assumptions about our inputs and then don't enforce those assumptions, because in a dynamic language enforcement costs performance. All the functions that work with channels will work in a way where they assume the channel is used in a certain way, and it's up to the programmer to link them up in a sensible manner. The functions in clojure.set can take any kind of clojure data structure, but it's undefined behavior if you pass anything other than a set. Channel functions assume that the channel passed will be used in a certain way, and it's undefined behavior if the user breaks that invariant. These invariants are usually just encoded in the docstring, and it's up to the programmer to ensure their code works. Having a one-way channel could work with the existing apis, but there's not much motivation to include it into core.async directly because doing so would be doing a kind of validation clojure doesn't do elsewhere, so it's just more code to maintain for little benefit.
The actual protocols you need to implement to make your own channel aren't that bad, so you could just make your own one-way channel implementation by wrapping core.async channels and only providing some functions on each type, but doing that is just not something that's in core.async directly.
Yeah, that's fair
Is that something that causes you a lot of defects? It's never happened to me that I like put on a channel I wasn't supposed to by mistake. I feel in practice it's pretty obvious what you care about.
Also, the semantics are that you can do two way communication on the channel, so technically you can have two process ping pong using one channel. First process put and other take then the other puts while the prior takes. That makes them perfectly alternate between one another.
As to why core.async doesn't, I don't think there's a philosophy at play, it's just that it didn't find a need to do so. If there's good use cases with good rational, you can cut a ticket, but chances are here the pros/cons probably don't add up to it. Every added feature adds complexity and maintenance cost. So I think it tries to be weighted against that.
Two processes ping ponging is interesting, I see what you mean that it's a pattern which the coupling of read and write enables, intuitively it feels like an antipattern, do you know any examples of two processes push/pulling on the same chan?
I agree that convention "works" but one can be immutable by convention and yet we still enforce immutability by default, it's not quite clear cut
An argument would be that universal or near-universal convention might drive an implementation even if it's not needed, just to semantically+programmatically enforce the good practise
Ya, that's why I don't think this is philosophical. Clojure is just super pragmatic, no practical benefit, no feature 😛
The posturing of hypothetical: "oh but what if stupid me don't realize and now I'm putting to the same channel when I shouldn't" isn't a convincing practical argument. It would need to be: Me was doing such and such, and I accidentally put in the wrong channel, nothing failed, test didn't catch, went to prod, company lost money, we had downtime. This happened to my coworker too, and my friend, and someone else, and it happens often. Ok, now you might have a case. But still, what's the downsides? Maybe you have another set of people saying, I often thought I wouldn't need to put from anywhere else, and then I did, it was awesome that I could just do that without needing to majorly refactor.
This is what made the case for immutable by default for example, too many real issues caused by it, with real measurable impact and complexity.
For my example, well, I think for a lot of true concurrent algorithms, you need such behavior often. If you're just trying to do some concurrent IO, probably not. But actual concurrent computation you might need too, which is kind of what CSP was first designed for. I think for it to make more sense, you have to understand its about Processes, which are things that compute/do stuff. You have many Processes all computing stuff or doing things concurrently. Now if what each Process is computing/doing is independent from one another, no big deal, but what if they depend on each other in some way? Either one need data from another, or the order in which they do stuff can't be random, it has to make sure that one Process does something only after another has done something else. Each Process does its own compute/stuff sequentially. So within a Process, everything is sequential. But across Processes, it's all concurrent. That's why it's called Communicating Sequential Processes (CSP). You have these Processes computing/doing stuff sequentially, but they might need to Communicate with one another to coordinate when they do stuff with regards to where other people are at, or to pass data to one another. From that point of view, you can model a lot of stuff. For example, say you have a Cook making food and putting it on a plate, and a waiter taking the plate and bringing it to a customer. Say you only have 1 plate. You have two processes, Cook is cooking, and Waiter is serving, but they share the same plate, and need to coordinate. Chef would cook and wait for Plate to be on channel before putting the food on the plate. Waiter would put the Plate on the channel once last customer is done with it and after they cleaned it. Now waiter would wait for Cook to put food on the plate back on the channel. So they ping pong the plate between each other.
So that's one example, passing of a shared resource between two Processes. Only one can use it at a time, each have to wait for the other to be done with it.
> I didn't make that hypothetical, to be fair Ya, sorry, it's just often the hypotheticals that are made when it comes to "safeguards" in general. Any restrictions in what you can do with some construct generally is a question of, there should be some safety mechanism protecting from accidental misuse. You can posture about "accidental misuse" really easily for anything. So it's easy to fall in a trap of thinking since hypothetically everything can be misused, you need to put safeguards everywhere. But safeguards have downsides too, putting them everywhere isn't practical in a lot of cases, or it's really involved. So you want to really measure the risk of accidental misuse, how often, are there nothing else to catch it already, what the impact of the misuse is, etc., before going to the effort and loss in flexibility that adding safeguards often brings.
like for example timeout returns a read only port, but it implements both read, write, and close
like, nothing is perfect, and there are any number of path dependent ways we could arrive at it being the way it is, up to and possibly including no one has tried to submit a patch to make the change (who knows if that would be accepted and how long it would take to get in)
Right, but it's only up to some quantifiable good/bad, like its probably not a mistake you see often that someone tries to put to a timeout chan. That's my point with saying it would be good in a hypothetical, it would require effort, and possibly provide no value in practice, because maybe no one ever wrongly puts on a timeout chan, so throwing a NotImplementedError if you do might not really be practically worth it.
putting on a timeout chan is not something I see much of, true, but closing them comes up very often, and if it just returned a ReadPort, then either would throw an error
That's fair, but it has zero upvotes: https://ask.clojure.org/index.php/348/clarify-timeout-mention-close-should-called-timeout-channel?show=348#q348 I'm not saying these things are inherently bad, sometimes there are no downsides to safeguards, like in this case, seems it would be zero cost and not affect use or extensibility, but it still takes effort, and my impression over the years is that the core team values ROI for their effort when prioritizing.
my new timeout impl on https://clojure.atlassian.net/browse/ASYNC-234 also just returns a ReadPort
When implementing both overloads of java.util.Collection#toArray
I'm getting compile errors saying that Clojure expected an object array but found an object. Is there something specific that I'm missing to make this act correctly? 🧵
The implementation I've currently got going is the following:
(toArray [this]
(object-array (seq this)))
(toArray [this ^"[Ljava.lang.Object;" kind]
(let [^Class array-clazz (class kind)
_ (assert (.isArray array-clazz) "array argument is an array")
^Class clazz (.getComponentType array-clazz)]
(object-array (map (partial cast clazz) (seq this)))))
And I get a compile error of the following:
Mismatched return type: toArray, expected: [Ljava.lang.Object;, had:
java.lang.Object
This persists even if I type hint the return type of both methods and their bodies.
Is there something I'm missing about this?
so you have to have one method that takes either, and in the method test the argument type and then do whatever
Alright, so is there just no way for me to override both overloads? They take different numbers of arguments so it feels like it shouldn't cause an issue with differentiating on types.
One takes only this
while the other takes a second argument used to get type information.
Oh, that works
yeah, I was going to point to https://github.com/clojure/clojure/blob/master/src/clj/clojure/gvec.clj#L438-L439
I guess my problem was that I was trying to annotate the return type on the arg vector like defns and not on the method name.
yeah, protocol impls follow "Java style" and put the type hint there
Good to know, thanks
you almost never need those as they typically just take on the type of the interface method being implemented
Right, I'd have hoped it'd work out fine. It's a bit of a weird case with Collection though it seems.
yep, they managed to break several of our collections when they added that. the methods are not ambiguous with types so it's a case that was backwards compatible in Java but not in Clojure
Created an issue in clj-kondo: https://github.com/clj-kondo/clj-kondo/issues/1717