Version 1 (modified by blamario, 5 years ago)

--

# Architecture

The SCC framework is implemented in multiple layers. Lower layers are useful by themselves.

## The lowest layer: trampoline-style nestable coroutines

This layer, implemented by the Control.Concurrent.Trampoline module, provides a limited coroutine functionality in Haskell. The centerpiece of the approach is the monad transformer Trampoline, that transforms an arbitrary monadic computation into a suspendable and resumable one. The basic definition is simple:

```newtype Trampoline s m r = Trampoline {bounce :: m (TrampolineState s m r)}

data TrampolineState s m r = Done r | Suspend! (s (Trampoline s m r))

return x = Trampoline (return (Done x))
t >>= f = Trampoline (bounce t >>= apply f)
where apply f (Done x) = bounce (f x)
apply f (Suspend s) = return (Suspend (fmap (>>= f) s))
```

The Trampoline transformer type is parameterized by a functor. Here is an example of one functor particularly useful for a Trampoline computation:

```data Yield x y = Yield x y
instance Functor (Yield x) where
fmap f (Yield x y) = Yield x (f y)
```

## Streams

The next layer builds on the trampoline foundation to provide streaming computations. The main idea here is to introduce sinks and sources:

```data Sink (m :: * -> *) a x = Sink {
put :: forall d. (AncestorFunctor a d) => x -> Trampoline d m Bool,
canPut :: forall d. (AncestorFunctor a d) => Trampoline d m Bool
}

newtype Source (m :: * -> *) a x = Source {
get :: forall d. (AncestorFunctor a d) => Trampoline d m (Maybe x)
}
```

The only way to obtain a new source to read from, or a sink to write to, is by launching a new nested coroutine using the function pipe:

```pipe :: forall m a a1 a2 x r1 r2. (Monad m, Functor a, a1 ~ SinkFunctor a x, a2 ~ SourceFunctor a x) =>
(Sink m a1 x -> Trampoline a1 m r1) -> (Source m a2 x -> Trampoline a2 m r2) -> Trampoline a m (r1, r2)
```

This function takes two coroutines as arguments, producer and consumer. The producer gets a sink argument, and the consumer a source argument. All data that the producer writes to the sink can be read from the source by the consumer. This arrangement couldn't be simpler.

## Components and combinators

What can one do with a number of sources and sinks? The next layer tries to organize the answers. First, it defines the types of various actors on sources and sinks:

```type OpenConsumer m a d x r = AncestorFunctor a d => Source m a x -> Trampoline d m r
type OpenProducer m a d x r = AncestorFunctor a d => Sink m a x -> Trampoline d m r
type OpenTransducer m a1 a2 d x y =
(AncestorFunctor a1 d, AncestorFunctor a2 d) => Source m a1 x -> Sink m a2 y -> Trampoline d m [x]
type OpenSplitter m a1 a2 a3 a4 d x b =
(AncestorFunctor a1 d, AncestorFunctor a2 d, AncestorFunctor a3 d, AncestorFunctor a4 d) =>
Source m a1 x -> Sink m a2 x -> Sink m a3 x -> Sink m a4 b -> Trampoline d m [x]

newtype Consumer m x r = Consumer {consume :: forall a d. OpenConsumer m a d x r}
newtype Producer m x r = Producer {produce :: forall a d. OpenProducer m a d x r}
newtype Transducer m x y = Transducer {transduce :: forall a1 a2 d. OpenTransducer m a1 a2 d x y}
newtype Splitter m x b = Splitter {split :: forall a1 a2 a3 a4 d. OpenSplitter m a1 a2 a3 a4 d x b}
```

## Dynamic component configuration

This is not so much a layer as an overlay, because it's quite generic. Any value can be made into a configurable component, provided that we supply the following information about it:

• a name,
• the maximum number of threads it can use, and
• the cost of using the component.