25 Moving stream processors

One distinguished feature of stream processors is that they are not directly connected to their input streams. Rather, a stream processor reacts to one message at a time. A better name would really be message processor, since there are no explicit streams anywhere, only messages. This is in contrast to functions operating on streams as lazy lists, which are instances of the type [i] -> [o] (if we only consider functions from one input stream to one output stream). To get the output stream of such a stream function, one must apply it to the input stream. Once that is done, there is no easy way to detach the stream function from the stream again.

Why would one want to do such a detachment? One reason arises if we want a stream processor to run for a while in one environment, and then move it to some other environment and continue running it there. Remember that stream processors are first class values and may be sent as messages. This, together with the fact that there is no difference (in the type) between a stream processor that has been executing for a while and a ``new'' one, allows us to program a combinator that can catch the ``current continuation'' of an arbitrary stream processor whenever we want.

extractSP :: SP i o -> SP (Either () i) (Either (SP i o) o)
extractSP s = case s of
   PutSP o s  -> PutSP (Right o) $ extractSP s
   NullSP     -> NullSP
   GetSP is   -> GetSP $ \m -> 
                 case m of
                   Right i  -> extractSP (is i)
                   Left ()  -> PutSP (Left s) $ 
                               NullSP
The stream processor extractSP s accepts messages of the form Right i, which are fed to s. Output o from s is output as Right o. At any time, we can feed the message Left () to it, and it will output the encapsulated stream processor in its current state as a message, tagged with Left. Note that in general, the stream processor that is output in this way is not equal to the original stream processor s.

So when we demand the continuation, extractSP s outputs it and dies. But why should it die? It might be useful to have it carry on as if nothing had happened. This reminds us of cloning of objects, and forking of processes. The variant is easily programmed, by modifying the last line of extractSP.

cloneSP :: SP i o -> SP (Either () i) (Either (SP i o) o)
cloneSP s = case s of
   PutSP o s  -> PutSP (Right o) $ cloneSP s
   NullSP     -> NullSP
   GetSP is   -> GetSP $ \m -> 
                 case m of
                   Right i  -> cloneSP (is i)
                   Left ()  -> PutSP (Left s) $
                               cloneSP s
Since stream processors are mere values, we do not need any machinery for duplication of state--this is indeed a case where we appreciate purely functional programming.

We can promote these ideas to fudgets as well, although the implementation gets more complicated. In the case of a GUI fudget, some action must be taken to ensure that it brings its associated window along when it moves, for example. We can then program drag and drop for any GUI fudget, as illustrated in Figure 58. In what follows, we will describe a set of combinators for supporting drag-and-drop in fudget programs.

About to drag
While dragging
After dropping

Figure 58. Pictures showing a fudget we are about to drag, while dragging, and after dropping it. After the fudget was dropped, the user changed its text. Note that the output from the moved fudget now goes to Drop area 2.

We call the fudgets that the user can drag and drop drag fudgets, and the areas in which they live drop areas. The communication of drag fudgets between the drop areas is mediated by a single invisible drag-and-drop fudget. A schematic picture of these fudgets is shown in Figure 59.

Figure 59. Schematic view of an invisible drag-and-drop fudget (indicated by the dashed frame) which in this case contains two rectangular drop areas, each of which contains a number of draggable fudgets. The dotted arrow indicates what will happen if a user drags the fudget f1 from the first to the second area: it will be extracted as a message from the first drop area and reach the drag-and-drop-fudget, which will bounce it to the second drop area.

These three types of fudgets exchange special messages to control the motion of the drag fudgets. To allow the drag fudgets to communicate with the rest of the program independently of these control messages, we pretend for a moment that fudgets have two mid-level input and output connections.

type SF mi mo hi ho = F (Either mi hi) (Either mo ho)
The type SF stands for stratified fudget. With this type, we can think of the message types of stream processors as stratified in three levels. The drag fudgets are formed by the container dragF:
dragF :: F a b -> DragF a b
type DragF a b = SF DragCmd (DragEvt a b) a b
The result type of dragF f is a stratified fudget in which the high-level streams are connected to f, and the mid-level streams are used for control, by means of drag commands and drag events. The drag commands are sent from the drop area to the drag fudgets during drag. The most important drag command is DragExtract, and informs the drag fudget that it has been accepted by another drop area. To this command, the drag fudget responds with an event containing itself:
data DragCmd = 
    DragExtract
 |  ...
data DragEvt a b = 
    DragExtracted (DragF a b)
 |  ...
Since the drag events can contain drag fudgets, we see that it is necessary to parameterise the type DragEvt. The exact type of the drag fudget must be visible in the type of drag events, as well as in other control message types we will introduce in the following. Thus, the type system ensures that a dragged fudget cannot be dropped in an area for fudgets of different type.

The drop area is a stratified variant of dynListF (see Section 13.4):

dropAreaF :: SF (DropCmd a b) (DropEvt a b) (Int,a) (Int,b)
The mid-level messages are called the drop commands and drop events, and are used by the drag-and-drop fudget to control the drop areas. Note that both these types are parameterised, because both can carry drag fudgets as messages. As indicated in Figure 59, the drop area contains drag fudgets, which furthermore are tagged. The high-level messages from these are therefore tagged when they enter or leave the drop area.

There is one drop command that is interesting for the application programmer:

dropNew :: DragF a b -> DropCmd a b
It is used to inject new drag fudgets inside a drop area.

Finally, we have the drag-and-drop fudget, which mediates dropped fudgets between drop areas.

dragAndDropF :: SF (t,DropCmd a b) (t,DropEvt a b) c d -> F c d
The argument to dragAndDropF is a stratified fudget whose mid-level messages should be uniquely tagged drop area messages. The intension is that the stratified fudget contains a list of drop area fudgets. Such a list can conveniently be created using a stratified variant of listF:
listSF :: Eq t => [(t,SF a b c d)] -> SF (t,a) (t,b) (t,c) (t,d)
listSF sfl = pullEither >^=< listF sfl >=^< pushEither

pushEither (Left (t,a))   = (t,Left a)
pushEither (Right (t,a))  = (t,Right a)

pullEither (t,Left a)   = (Left (t,a))
pullEither (t,Right a)  = (Right (t,a))
By means of dragF, dropAreaF, and dragAndDropF, we can program the example (illustrated in Figure 58. As drag fudgets, we use labelled stringInputF's.
drag :: Show i => DragF String String
drag i = dragF $ 
         labAboveF ("Drag me ("++show i++")") $ 
          ((show i++": ")++) >^=< stringInputF
The string output from the drag fudget is prepended with its identity i.

We define a drop area with an associated display which shows the output from the drag fudgets in it. We initialise the drop area by creating a drag fudget in it with the same identity as the drop area.

area :: Show i => 
    i -> SF (DropCmd String String) (DropEvt String String)
            (Int,String) a
area i = vBoxF $ 
           idLeftF (displayF >=^< snd) >==< 
           startupF [Left $ dropNew $ drag i] dropAreaF
Finally, we define a drag-and-drop fudget with two drop areas inside shell fudgets.
dnd :: F (Int,(Int,String)) (Int,a)
dnd = dragAndDropF $ listSF $ 
       [(t,shellF ("Drop area "++show t) (area t)) | t <- [1..2]]

main = fudlogue dnd

25.1 Problems with dragging windows in X Windows

Dragging objects as windows under the pointer in the X Window system is not problem free: it is difficult to determine where the object is dropped when the user releases the mouse button. This release generates a button event which contains information about what window is under the pointer. But this will not be the window in which we drop the object, it will be the object's window itself! If we are content with somewhat less spectacular visual feedback, we could choose not to move the object itself, but change the pointer to a symbol that carries a little object, as is done in Open Windows [Sol97].

What we need is to have the dragged object's window transparent with respect to certain events. We achieve this in a brutal way, by simply zapping a small temporary hole in the object window under the pointer, as shown in the detail.

However, we now have timing problem, which can appear if the user moves the pointer quickly and immediately drops the object. There is a delay in the movement of the pointer and the object, since it is the client which is doing the tracking. With a constant delay, the tracking error is proportional to the speed of the pointer, which means that if the speed is large enough, the pointer will not be above the hole anymore. Currently, we do not know if there exists a good solution to this ``drop problem'' in X Windows.