I can't speak to the implications of changing the API, but the concept of separating the reader and writer ends of a channel makes absolute sense. So at the very least, this would be useful as a separate package.
In a side node - I used to code in an old concurrent language where even variables were split into reader and writer ends (at the lowest level, not as a library). That is, each use of "x" was either "the reader of x" or "the writer of x". The compiler automatically infrerred which one was used, but manual annotation was possible. The compiler also enforced that only a single occurence of "the writer of x" existed at any given time in the system.
This allowed doing things that are difficult to do in Haskell right now, without dropping into IO for IVars, such as passing around a deep structure with writers at the leaves to a function that would fill them with values, establishing a trivial two-way communication between multiple threads, and difference lists rather than lists being the most common collection (implemented as a simple pair of a Reader [ T ] and a Writer [ T ]).
Extensive use of difference lists turned out to be very useful - in naive code list concatenation was O(1) in many more cases than in naive Haskell code, and there are all sort of concurrency control algorithms that can be trivially implemented by them (e.g., detecting/enforcing when a lazy evaluation of multiple threads is done, passing exceptions in a side channel to the real result, etc.).
It is a different approach than the one took by lazy functional languages, and it has its up-sides; I miss its clarity of expressing the code intent when I am dealing with parallel lazy Haskell code. It would be "very nice" if there was a pure version of IVars that basically allowed direct lower-level access to the lazy evaluation mechanism.
Of course, the language I used had other issues (this was the early 90s, and a lot of work has been done since then on type systems and type inference, efficient implementation, etc.). It was a research language that dies with the 5th generation project
At any rate, +1 for separate reader and writer endpoints for channels, at least for those who need it.
On Sat, Oct 27, 2012 at 5:23 AM, Brandon Simmons <brandon.m.simmons <at> gmail.com>
Just discovered all the great discussion here, and although my
google-fu turned up no prior discussions on this subject, I apologize
if it has already been discussed.
I propose that Chan and TChan should be implemented as a pair of read
and write ends, initialized as follows:
newSplitChan :: IO (InChan a, OutChan a)
I've implemented this already here:
http://hackage.haskell.org/package/chan-split . You can ignore the
type classes I've defined; they're there for my own reasons and not
part of the proposal.
Here is my best defense:
1) My own (and I assume others') use of Chans involves some bits of
code which do only reads, and others which do only writes; a split
implementation lets us use the type-checker to allocate read or write
"permissions" when we pass around either end, and generally makes
things easier to reason about. Others have independently reached this
conclusion as well.
2) The API is simpler (and in the case of TChans *much* simpler) with
a split approach; some examples: in TChan the types of the 'duplicate'
functions actually suggest (IMHO) the details of how they treat
existing messages, and require a less roundabout explanation
dupTChan :: InTChan a -> STM (OutTChan a)
cloneTChan :: OutTChan a -> STM (OutTChan a)
another example, 'newBroadcastTChan' is actually just:
-- | Create a new write end of a TChan. Use dupTChan to get an
OutChan that values can be read from.
newInTChan :: STM (InTChan a)
and doesn't require any explanation beyond that.
3) It's trivial to modify the *Chan libs in this way with a few edits
(really, they're already implemented this way, but with some added
contortions to return both a read and write end for each operation),
so there're no new tricky concurrency issues to reason about.
4) Values written to the write end can be reliably GC'd when readers
go away, although recent GHC seems to be able to do this on the
current implementation with -O2 on my simple tests.
5) While this is a big API change, I imagine the vast majority of
users would only have to change a few type signatures and the Chan
initialization action. An `OldChan` module could easily be implemented
in terms of the new split version. Alternatively, a *Chan.Split module
could be added, and the current Chan module defined in terms of it.
So two weeks of discussion?
Libraries mailing list
Libraries <at> haskell.org