[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.
The stream processorextractSP :: 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
extractSP saccepts 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,
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
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.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
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.
The typetype SF mi mo hi ho = F (Either mi hi) (Either mo ho)
SFstands 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
The result type ofdragF :: F a b -> DragF a b type DragF a b = SF DragCmd (DragEvt a b) a b
dragF fis 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 | ...
Since the drag events can contain drag fudgets, we see that it is necessary to parameterise the typedata DragEvt a b = DragExtracted (DragF a b) | ...
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
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.dropAreaF :: SF (DropCmd a b) (DropEvt a b) (Int,a) (Int,b)
There is one drop command that is interesting for the application programmer:
It is used to inject new drag fudgets inside a drop area.dropNew :: DragF a b -> DropCmd a b
Finally, we have the drag-and-drop fudget, which mediates dropped fudgets between drop areas.
The argument todragAndDropF :: SF (t,DropCmd a b) (t,DropEvt a b) c d -> F c d
dragAndDropFis 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
By means oflistSF :: 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))
dragAndDropF, we can program the example (illustrated in Figure 58. As drag fudgets, we use labelled
The string output from the drag fudget is prepended with its identitydrag :: Show i => DragF String String drag i = dragF $ labAboveF ("Drag me ("++show i++")") $ ((show i++": ")++) >^=< stringInputF
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.
Finally, we define a drag-and-drop fudget with two drop areas inside shell fudgets.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
dnd :: F (Int,(Int,String)) (Int,a) dnd = dragAndDropF $ listSF $ [(t,shellF ("Drop area "++show t) (area t)) | t <- [1..2]] main = fudlogue dnd
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.