#lang scribble/manual @(require (for-label racket/base racket/contract/base racket/async-channel racket/place racket/serialize port-channel uni-channel)) @title{uni-channel} @author[@author+email["Hans Dijkema" "hans@dijkewijk.nl"]] @defmodule[uni-channel] The @racketmodname[uni-channel] module provides a small uniform facade over three channel-like transports: @racketmodname[racket/async-channel] asynchronous channels, @racketmodname[racket/place] place channels, and @racketmodname[port-channel] port channels. The purpose is not to hide transport semantics completely. A place channel still uses place-channel message constraints and a port channel still serializes values with @racketmodname[racket/serialize]. The purpose is to make code that only needs send, receive, event synchronization and close operations independent from the concrete channel implementation. @section{Model} A uni-channel has a kind, a direction, a name and an underlying backend object. The kind is one of @racket['async], @racket['place] or @racket['port]. The direction is one of @racket['input], @racket['output] or @racket['bidirectional]. Receiving is supported for input and bidirectional channels. Sending is supported for output and bidirectional channels. A uni-channel that supports receiving is itself a synchronizable event: @racketblock[(sync ch)] For output-only channels, synchronizing on the uni-channel uses @racket[never-evt]. @section{Constructors} @defproc[(make-uni-channel [backend any/c] [#:kind kind (or/c 'auto 'async 'place 'port) 'auto] [#:direction direction (or/c 'auto 'input 'output 'bidirectional) 'auto] [#:name name any/c #f]) uni-channel?]{ Wraps an existing backend channel. With @racket['auto], the kind is inferred from @racket[async-channel?], @racket[place-channel?] or @racket[port-channel?]. For a @racket[port-channel?] backend, the direction is read from @racket[port-channel-direction]. A single port-channel endpoint is therefore input-only or output-only. Use @racket[make-uni-port-channel] when one uni channel should combine an input port and an output port into a bidirectional endpoint.} @defproc[(make-uni-async-channel [ch async-channel? (make-async-channel)] [#:direction direction (or/c 'auto 'input 'output 'bidirectional) 'bidirectional] [#:name name any/c 'async-channel]) uni-channel?]{ Wraps a single asynchronous channel. The same underlying queue is used for both sending and receiving when the direction is @racket['bidirectional].} @defproc[(make-uni-async-channel-pair [#:name-a name-a any/c 'async-channel-a] [#:name-b name-b any/c 'async-channel-b]) (values uni-channel? uni-channel?)]{ Creates two bidirectional uni-channel endpoints backed by two asynchronous channels. A value sent on the first endpoint is received on the second endpoint, and the other way around.} @defproc[(make-uni-place-channel [ch place-channel?] [#:direction direction (or/c 'auto 'input 'output 'bidirectional) 'bidirectional] [#:name name any/c 'place-channel]) uni-channel?]{ Wraps an existing place-channel endpoint. Values sent through this backend must satisfy Racket's place-message constraints.} @defproc[(make-uni-place-channel-pair [#:name-a name-a any/c 'place-channel-a] [#:name-b name-b any/c 'place-channel-b]) (values uni-channel? uni-channel?)]{ Creates two bidirectional uni-channel endpoints using @racket[place-channel].} @defproc[(make-uni-port-channel [#:input in (or/c #f input-port?) #f] [#:output out (or/c #f output-port?) #f] [#:source source any/c 'port-channel] [#:close? close? any/c #t] [#:name name any/c 'port-channel]) uni-channel?]{ Creates a uni-channel backed by one or two @racketmodname[port-channel] endpoints. At least one of @racket[#:input] or @racket[#:output] must be provided. With both, the result is bidirectional: receives come from the input port-channel and sends go to the output port-channel.} @section{Predicates and accessors} @defproc[(uni-channel? [v any/c]) boolean?]{Returns true when @racket[v] is a uni-channel.} @defproc[(uni-channel-kind [ch uni-channel?]) (or/c 'async 'place 'port)]{Returns the backend kind.} @defproc[(uni-channel-direction [ch uni-channel?]) (or/c 'input 'output 'bidirectional)]{Returns the channel direction.} @defproc[(uni-channel-name [ch uni-channel?]) any/c]{Returns the diagnostic name supplied when the uni-channel was created.} @defproc[(uni-channel-impl [ch uni-channel?]) any/c]{Returns the underlying backend object.} @section{Sending and receiving} @defproc[(uni-channel-put [ch uni-channel?] [v any/c]) void?]{ Sends @racket[v] on @racket[ch]. The channel must be output-capable. For @racket['port] channels, the value must be serializable by @racket[serialize]. For @racket['place] channels, the value must be allowed as a place message.} @defproc[(uni-channel-send [ch uni-channel?] [v any/c]) void?]{Alias for @racket[uni-channel-put].} @defproc[(uni-channel-get [ch uni-channel?]) any/c]{Receives the next value from @racket[ch].} @defproc[(uni-channel-recv [ch uni-channel?]) any/c]{Alias for @racket[uni-channel-get].} @defproc[(uni-channel-try-get [ch uni-channel?]) any/c]{Attempts to receive a value without blocking. Returns @racket[#f] when no value is currently available.} @defproc[(uni-channel-get-evt [ch uni-channel?]) evt?]{Returns a synchronizable receive event.} @defproc[(uni-channel-put-evt [ch uni-channel?] [v any/c]) evt?]{Returns an event that sends @racket[v] when selected.} @section{Closing} @defproc[(uni-channel-close [ch uni-channel?]) void?]{ Closes @racket[ch]. For async-channel and place-channel backends this marks the uni-channel wrapper as closed; Racket does not provide a native close operation for those channel types. For port-channel backends this delegates to @racket[close-port-channel]. For a bidirectional port channel only the output side is closed, so already queued values can still be received from the input side until end-of-file.} @defproc[(uni-channel-closed? [ch uni-channel?]) boolean?]{Returns true when @racket[uni-channel-close] has been called on the wrapper.} @section{Port-channel errors} @defproc[(uni-channel-error-get [ch uni-channel?]) any/c]{Blocks until a backend error is available. This is meaningful for @racket['port] channels and raises an exception for backends that do not have an error channel.} @defproc[(uni-channel-error-try-get [ch uni-channel?]) any/c]{Attempts to get a backend error without blocking. Returns @racket[#f] when no error is available.} @defproc[(uni-channel-error-evt [ch uni-channel?]) evt?]{Returns a synchronizable backend-error event. For non-port backends this is @racket[never-evt].} @section{Examples} @subsection{Async-channel pair} @racketblock[ (require uni-channel) (define-values (a b) (make-uni-async-channel-pair)) (uni-channel-send a '(hello from a)) (uni-channel-recv b) ] @subsection{Place-channel pair} @racketblock[ (require uni-channel) (define-values (a b) (make-uni-place-channel-pair)) (uni-channel-send a '(hello from a)) (sync b) ] @subsection{Port-channel over a pipe} @racketblock[ (require uni-channel) (define-values (in out) (make-pipe)) (define ch (make-uni-port-channel #:input in #:output out)) (uni-channel-send ch '(hello 1 2 3)) (uni-channel-recv ch) ]