[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 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
.
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)
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
:The result type ofdragF :: F a b -> DragF a b type DragF a b = SF DragCmd (DragEvt a b) a b
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 | ...
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 dynListF
(see
Section 13.4):
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
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
: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))
dragF
, dropAreaF
, and dragAndDropF
,
we can program the example (illustrated in Figure 58. As
drag fudgets, we use labelled stringInputF
's.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
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.
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.