that computes the accumulating sum of a stream of integers. Let us write a complete Haskell program that usessumSP :: Int -> SP Int Int
sumSPto implement a simple adding machine.
Haskell provides the function
interact, which allows
functions of type
[Char] -> [Char] to be used as programs (as in
Landin's stream I/O model outlined in
Chapter 4). By combining this with the function
(from Section 16.1) we can run stream processors of typerunSP :: SP i o -> [i] -> [o]
SP Char Char:
To be able to usemain = interact (runSP mainSP) mainSP :: SP Char Char mainSP = ...
sumSPwe need only add some glue functions that convert the input stream of characters to a stream of numbers and conversely for the output stream. This is done in two stages. First, the stream-processor equivalents of the standard list functions
unlinesare used to process input and output line by line, instead of character by character:
Now the standard functionsmainSP = unlinesSP -==- adderSP -==- linesSP adderSP :: SP String String adderSP = ...
readare used to convert between strings and numbers,
and the program is complete.adderSP = mapSP show -==- sumSP 0 -==- mapSP read
unlinesSP :: SP String Char.
unlinesSP = concatMapSP (\s -> s++"\n")
linesSP :: SP Char String
linesSP = lnSP  where lnSP acc = getSP $ \msg -> case msg of '\n' -> putSP (reverse acc) (lnSP ) c -> lnSP (c : acc)
Figure 36. Line buffered input.
Assuming a simpler system, where keyboard input is fed
directly to the program, and the only characters shown on the
screen are those output by the program (raw terminal mode in
Unix) (Figure 37), the stream-processor
lineBufferSP is now defined to do the job:
Figure 37. Unbuffered input.
It takes a stream processor that expects the input to be line buffered, and returns a stream processor that does the necessary processing of the input: buffering, echoing, etc., so that it can work in an unbuffered environment.lineBufferSP :: SP String Char -> SP Char Char
We get the connectivity shown in Figure 38, i.e.,lineBufferSP progsp = loopThroughRightSP bufSP progsp where bufSP :: SP (Either Char Char) (Either String Char) bufSP = ...
bufSPwill receive program output and keyboard input on its input stream and should produce input lines and screen output on its output stream.
Figure 38. Circuit diagram for
The implementation of
bufSP is shown in Figure 39.
bufSP = inputSP "" inputSP line = getSP $ either fromProgsp fromKeyboard where fromProgsp c = putSP (toScreen c) (inputSP line) fromKeyboard c = case c of -- The
Enterkey: '\n' -> putSP (toScreen '\n') $ putSP (toProgsp (reverse line)) $ bufSP -- The
backspacekey: '\b' -> if null line then inputSP line else putsSP (map toScreen "\b \b") $ inputSP (tail line) -- Printable characters: _ -> putSP (toScreen c) $ inputSP (c:line) toScreen = Right toProgsp = Left
bufSP- the core of
lineBufferSP, the adding machine in the previous
section can be adapted to run in raw terminal mode by change
mainSP = lineBufferSP (unlinesSP -==- adderSP)
A simple implementation ofsplitViewSP :: SP Char Char -> SP Char Char -> SP Char Char
splitViewSPcan be structured as follows:
splitViewSP sp1 sp2 = mergeSP -==- (sp1 -+- sp2) -==- distrSP where distrSP :: SP Char (Either Char Char) distrSP = ... mergeSP :: SP (Either Char Char) Char mergeSP = ...
distrSPtakes the keyboard input and sends it to one of the two windows. The user can switch windows by pressing a designated key.
mergeSP takes the two output streams from the windows
and produces a merged stream, which contains the appropriate
sequences to make the text appear in the right places on the
screen. This can be done in different ways depending on the
terminal characteristics. A simple solution, if scrolling is
not required, is to split the processing into two steps: the first
being to interpret the output streams from the two windows
individually to keep track of the current cursor position
using a stream processor like
It takes a character stream containing a mixture of printable characters and cursor control characters, and produces a stream with pairs of cursor positions and printable characters. The next step is to merge the two streams and feed them into a stream processor that generates the appropriate cursor motion commands for the terminal:trackCursorSP :: SP Char ((Int,Int),Char)
Thus we haveencodeCursorMotionSP :: SP ((Int,Int),Char) Char
Using the above outlined implementation ofmergeSP = encodeCursorMotionSP -==- mapSP stripEither -==- (trackCursorSP -+- trackCursorSP)
mergeSP, we get the circuit diagram shown in Figure 40 for
splitViewSP sp1 sp2:
Figure 40. Circuit diagram for
splitViewSP sp1 sp2.
Filling in some details we ignored in the above description, we get the implementation shown in Figure 41.
splitViewSP :: (Int,Int) -> SP Char Char -> SP Char Char -> SP Char Char splitViewSP (w,h) sp1 sp2 = mergeSP -==- (sp1 -+- sp2) -==- distrSP Left Right where mergeSP = encodeCursorMotionSP -==- mapSP stripEither -==- (trackCursorSP (w,h1) -+- (mapSP movey -==- trackCursorSP (w,h2))) h1 = (h-1) `div` 2 h2 = h-1-h1 movey ((x,y),c) = ((x,y+h1+1),c) distrSP dst1 dst2 = getSP $ \ c -> case c of '\t' -> distrSP dst2 dst1 _ -> putSP (dst1 c) $ distrSP dst1 dst2 trackCursorSP :: (Int,Int) -> SP Char ((Int,Int),Char) trackCursorSP size = mapstateSP winpos (0,0) where winpos p c = (nextpos p c,[(p,c)]) encodeCursorMotionSP :: SP ((Int,Int),Char) Char encodeCursorMotionSP = mapstateSP term (-1,-1) where term cur@(curx,cury) (p@(x,y),c) = (nextpos p c,move++[c]) where move = if p==cur then "" else moveTo p nextpos :: (Int,Int) -> Char -> (Int,Int) nextpos p c = ... -- cursor position after c has been printed moveTo :: (Int,Int) -> String moveTo (x,y) = ... -- generate the appropriate cursor control sequence
Figure 41. An implementation of