Next Previous Contents

3. Interface of the Module Ports

Ports are an abstraction for modeling variables whose values evolve over time without the need to resort to mutable variable, such as IORefs. More precisely, a port represents all values that a time-dependent variable successively takes as a stream, where each element of the stream corresponds to a state change - we can also say that a port represents a time series.

3.1 Ports and Port Filters

Ports containing values of type a are represented as

data Port a

One or more streams can be associated with a port, which contain (a subset) of the values that the port assumes while evolving over time. The values in the output stream are determined by the messages or events that are directed to the port.

Port filters serve as guards that control the values that a port can take, i.e., they screen the incoming messages and may adjust incoming port values (e.g., to ensure that all values appearing in the output stream of a port stay within certain bounds) or even reject them.

type PortFilter a = a -> a -> Maybe a

The first argument given to a port filter is the value of the port at the moment where a given incoming messages is processed and the second argument is that incoming value. The port filter can decide to entirely reject the value by returning Nothing, or it can return the value that should be assumed by the port next. This can, but need not be the same as the incoming value.

3.2 Creating and Closing Ports

A port without a port filter (i.e., one that accepts any incoming message) is created by

newPort :: a -> IO (Port a)

The argument constitutes the initial value of the port, but it never appears in any output stream of the port. This value is mainly important for filtering ports, which are created with

newFilteringPort :: a -> PortFilter a -> IO (Port a)

These ports apply the given port filter to any value before placing it into the output stream.

Ports can be closed, which means that all corresponding streams are ended.

closePort :: Port a -> IO ()

It can be checked whether a port is closed or not.

isClosedPort :: Port a -> IO Bool

3.3 Accessing and Querying Ports

The following function obtains a stream containing the values assumed by the port as it evolves over time.

listenToPort :: Port a -> IO [a]

Note that while this stream has to be obtained within the IO monad, the stream itself can be processed purely functional (i.e., an observer of the port need not be coded in the monad). A stream contains the values assumed by the port after listenToPort is executed; previous values are not contained in the stream.

The following operation sends the second argument to the port given in the first argument:

(<--) :: Port a -> a -> IO ()

As it is sometimes convenient to send a list of values to a port, the following function, which is usually more efficient than the previous one, is also available:

(<==) :: Port a -> [a] -> IO ()

More generally, we can send a function that transforms the current port value to a port:

(<-$) :: Port a -> (a -> a) -> IO ()

For example, p <-$ (+1) would increment the current value of the port p by one. Obviously, p <-$ const x corresponds to p <-- x.

Finally, the current value of a port can be obtained within the IO monad.

peekIntoPort :: Port a -> IO (Maybe a)

This function should only be used for purposes like initialisation. It breaks the idea of observing the time series as a whole instead of individual elements, i.e., snapshots of the port and when multiple threads are sending messages to a port, the order of a peekIntoPort call with respect to the messages is usually not deterministic. If the port is closed, Nothing is returned.

3.4 Port Linking

Two ports can be to be synchronised, or linked, so that they share all incoming events.

(<->) :: Port a -> Port a -> IO ()

In fact, they can even have different domains and use translation functions to map values between their domains.

linkPorts :: (a -> b) -> (b -> a) -> Port a -> Port b -> IO ()

3.5 Proxy Ports

Ports that represent an external state as a port are called proxy ports. To keep a proxy port in sync with some external state, we use Port notifiers, which either the port of a change in the external state or vice versa. The argument to a notifier is the new value. Depending on the kind of the notifier, it puts this value into the port or updates the external state.

type PortNotifier a = a -> IO ()

When a port is created, it is given the notifier that it should use to notify the external state of changes in the port. As a result it does not only produce the port, but also a second notifier that is to be invoked whenever the external state changes.

newProxyPort :: a -> PortNotifier a -> IO (Port a, PortNotifier a)

The produced notifier will take care that no feedback loop is introduced.

Next Previous Contents