that computes the accumulating sum of a stream of integers. Let us write a complete Haskell program that usessumSP :: Int -> SP Int Int
sumSP
to 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
runSP
,
(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 = ...
sumSP
we 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 lines
and
unlines
are 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 = ...
show
and read
are
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
combinator 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 implement lineBufferSP
using loopThroughRightSP
:
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 = ...
bufSP
will 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
lineBufferSP
.
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 -- TheEnter
key: '\n' -> putSP (toScreen '\n') $ putSP (toProgsp (reverse line)) $ bufSP -- Thebackspace
key: '\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 = LeftFigure 39.
bufSP
- the core oflineBufferSP
.
Using lineBufferSP
, the adding machine in the previous
section can be adapted to run in raw terminal mode by change
mainSP
to:
mainSP = lineBufferSP (unlinesSP -==- adderSP)
A simple implementation ofsplitViewSP :: SP Char Char -> SP Char Char -> SP Char Char
splitViewSP
can 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 = ...
distrSP
takes 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
cursor control
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 sequenceFigure 41. An implementation of
splitViewSP
.