parent previous next question (Smalltalk Textbook 21)

Stream

I would like to talk about 'Stream' in this section. 'Stream' represents a point of view into an ordered collection, and allows you to view a collection as a sequence of objects.


Program-21-1: (ReadStream, Array; on:from:to:, atEnd, next)
----------------------------------------------------------------
    | anArray aStream anInteger |
    anArray := #(10 20 30 40 50 60 70 80 90 100 ).
    aStream := ReadStream
        on: anArray
        from: 3
        to: 7.
    [aStream atEnd]
        whileFalse:
            [anInteger := aStream next.
            Transcript cr; show: anInteger printString]
----------------------------------------------------------------

Program 21-1 creates a stream (aStream) view of a subset of an array, from the third to the seventh item. Then it accesses each element of this subset via the 'next' message and writes each to the transcript. So we have added the ability to use the 'next' message on an Array. To see that neither Array nor any of its superclasses define 'next', just inspect:

----------------------------------------------------------------
    | aClass aCollection |
    aClass := Array.
    aCollection := OrderedCollection new.
    aClass withAllSuperclasses
        reverseDo: [:each |
            aCollection addAll:
                (each selectors select: [:x | x first asUppercase = $N]).
            aCollection addAll:
                (each class selectors select: [:x | x first asUppercase = $N])].
    ^aCollection asSortedCollection
----------------------------------------------------------------

You can also create more than one stream on a collection as seen in Program 21-2.


Program-21-2: (ReadStream, Array; on:from:to:, atEnd, next)
----------------------------------------------------------------
    | anArray aStream1 aStream2 anInteger anInteger2 |
    anArray := #(10 20 30 40 50 60 70 80 90 100 ).
    aStream1 := ReadStream
        on: anArray
        from: 3
        to: 7.
    aStream2 := ReadStream
        on: anArray
        from: 2
        to: 6.
    Transcript cr.
    [aStream1 atEnd or: [aStream2 atEnd]]
    whileFalse:
        [anInteger := aStream1 next.
        Transcript cr; show: anInteger printString.
        anInteger2 := aStream2 next.
        Transcript space; show: anInteger2 printString]
----------------------------------------------------------------

Program 21-3 creates a stream without start and end indexes. The entire collection is a stream in this case.


Program-21-3: (ReadStream, Array; on:, do:)
------------------------------------------------------------
    | anArray aStream |
    anArray := #(10 20 30 40 50 60 70 80 90 100 ).
    aStream := ReadStream on: anArray.
    Transcript cr.
    aStream do: [:each | Transcript cr; show: each printString]
-------------------------------------------------------------

A stream understands 'do:' message as shown in Program 21-3, to simplify access to the whole stream.

A common misconception of a Stream is that only a character strings are amenable to stream processing. However any collection can be accessed as a stream. For example Program 21-4 creates a stream of colors.


Program-21-4: (ReadStream, Screen, GraphicsContext; paint:, displayRectangle:)
----------------------------------------------------------------------
    | colorValues aStream activeWindow graphicsContext |
    colorValues := Screen default colorPalette asArray.
    aStream := ReadStream on: colorValues.
    activeWindow := ScheduledControllers activeController view.
    graphicsContext := activeWindow graphicsContext.
    aStream
        do:
        [:color |
            graphicsContext paint: color.
            graphicsContext displayRectangle: activeWindow bounds].
    activeWindow display
----------------------------------------------------------------------

And for another effect, try:


Program-21-5: (ReadStream, Screen, GraphicsContext, Random; paint:, displayRectangle:)
----------------------------------------------------------------------
    | colorValues aStream activeWindow graphicsContext 
      rn rWidth rHeight px py pw ph |
    activeWindow := ScheduledControllers activeController view.
    rn := Random new.
    rWidth := activeWindow bounds width.
    rHeight := activeWindow bounds height.
    colorValues := Screen default colorPalette asArray.
    aStream := ReadStream on: colorValues.
    graphicsContext := activeWindow graphicsContext.
    aStream
        do:
        [:color |
            graphicsContext paint: color.
            px := (rn next * rWidth) rounded.
            py := (rn next * rHeight) rounded.
            pw := (rn next * (rWidth - px)) rounded.
            ph := (rn next * (rHeight - py)) rounded.
            graphicsContext displayRectangle: ((px @ py) extent: (pw @ ph))].
    ScheduledControllers activeController sensor waitClickButton.
    activeWindow display
----------------------------------------------------------------------

This is the inheritance tree of 'Stream'. The tree is classified roughly into external stream, internal stream and random.

-----------------------------------------------
Object
. . Stream
. . . . PeekableStream
. . . . . . PositionableStream
. . . . . . . . ExternalStream
. . . . . . . . . . BufferedExternalStream
. . . . . . . . . . . . ExternalReadStream
. . . . . . . . . . . . . . ExternalReadAppendStream
. . . . . . . . . . . . . . ExternalReadWriteStream
. . . . . . . . . . . . ExternalWriteStream
. . . . . . . . InternalStream
. . . . . . . . . . ReadStream
. . . . . . . . . . WriteStream
. . . . . . . . . . . . ReadWriteStream
. . . . . . . . . . . . . . ByteCodeReadWriteStream
. . . . . . . . . . . . TextStream
. . . . Random
-----------------------------------------------

The above examples show read only stream (ReadStream) which can not modify the streams. This stream is a kind of 'InternalStream' which is a kind of 'PositionableStream' which is a kind of 'PeekableStream'.

'Positionable' means a stream can respond to 'position' or 'position:' messages, shown in Program 21-6.


Program-21-6: (ReadStream, PositionableStream; position, positionable)
-------------------------------------------------------------
    | anArray aStream |
    anArray := #(10 20 30 40 50 60 70 80 90 100 ).
    aStream := ReadStream on: anArray.
    Transcript cr.
    aStream position: 3.
    aStream do:
        [:each |
        Transcript cr; show: aStream position printString.
        Transcript space; show: each printString]
-------------------------------------------------------------

Peekable means you can examine elements of a stream without moving the c urrent position. This is different from the 'next' message. Program 21-7 shows an example of using 'peek'.


Program-21-7: (ReadStream, PeekableStream; position, peek, atEnd)
---------------------------------------------------------------
    | anArray aStream |
    anArray := #(10 20 30 40 50 60 70 80 90 100 ).
    aStream := ReadStream on: anArray.
    Transcript cr.
    [aStream atEnd]
        whileFalse:
            [Transcript cr; show: aStream position printString.
            Transcript space; show: aStream peek printString.
            Transcript cr; show: aStream position printString.
            Transcript space; show: aStream next printString]
---------------------------------------------------------------

Program 21-8 is an example of 'WriteStream' which does not respond to 'next' message for instance, because it is a write only stream.


Program-21-8: (WriteStream, Array; nextPut:, contents)
----------------------------------------------------
    | aStream |
    aStream := WriteStream on: Array new.
    aStream nextPutAll: #(10 20 30 40 50 60 70 80 90 100 ).
    aStream nextPut: 110.
    aStream nextPut: 120.
    aStream contents
----------------------------------------------------

Of course there is a 'ReadWriteStream' to which you have write and read access. 'TextStream' adds support for character attributes. The above is a summary of 'InternalStream' which works only on objects in memory.

Now let's examine 'ExternalStream' which accesses files. Program 21-9 writes strings to a file. It writes "VisualWorks" for first line and "ObjectWorks" for second line to "zzz" file. Use the File List tool to verify this.


Program-21-9: (WriteStream; writeStream, nextPutAll:, 
valueNowOrOnUnwindDo;, close)
------------------------------------------------------
    | aFilename aStream writeBlock |
    aFilename := 'zzz' asFilename.
    aStream := aFilename writeStream.
    writeBlock :=
        [aStream nextPutAll: 'VisualWorks'.
        aStream cr.
        aStream nextPutAll: 'ObjectWorks'.
        aStream cr].
    [writeBlock value]
        valueNowOrOnUnwindDo: [aStream close]
------------------------------------------------------

It is easy to create an external write only stream. To receive a File object, send the 'asFilename' message to an instance of 'String' which contains a legal file name. Then send a message to create a stream as follows.

------------------------------------
aFilename readStream
aFilename writeStream
aFilename appendStream
aFilename readWriteStream
aFilename readAppendStream
------------------------------------

You must always 'close' an external stream when your processing is done, because you are interacting with the Operating System, not just Smalltalk. You need to explicitly release resources by closing files. This is different from an internal stream, which will eventually get garbage collected automatically. Forgetting to close streams will screw up your image and lead to strange behavior. For example, you won't be able to open a write stream and write to it if you previously opened it and forgot to close it.

Notice that you also need to close DisplaySurface instances, because DisplaySurface is not a Smalltalk object alone, but is a resource of the window system on which Smalltalk is running. Therefore you need to close it just like an external stream to release a resource. This is tedious for Smalltalkers because the Smalltalk environment does not automatically manage external objects.

You will need to use the 'doBlock valueNowOrOnUnwindDo: ensureBlock' message to ensure that external resources really do get closed. This message executes 'ensureBlock' after 'doBlock' completes. And even if 'doBlock' does not complete because of an error, 'ensureBlock' is guaranteed to be executed.

Let's continue with external files. The next expression gets the contents of file "zzz".

--------------------------------------
FileBrowser openOnFileNamed: 'zzz'
--------------------------------------

Program 21-10 also shows you the contents of file "zzz". The program creates a read stream on a file then writes the contents of a file to the transcript.


Program-21-10: (ReadStream; readStream, nextPut:, flush, 
valueNowOrOnUnwindDo;, close)
---------------------------------------------------------
    | aFilename aStream readBlock |
    aFilename := 'zzz' asFilename.
    aStream := aFilename readStream.
    readBlock :=
        [aStream do: [:char | Transcript nextPut: char].
        Transcript flush].
    Transcript cr.
    [readBlock value]
        valueNowOrOnUnwindDo: [aStream close]
---------------------------------------------------------

External streams can only write and read values such as bytes or words. This is a very strict limitation in comparison with internal streams which can read and write any object. BOSS (Binary Object Streaming Service) addresses this shortcoming. BOSS allows you to treat a file as a stream of objects.

Program 21-11 writes three instances of Image to file "yyy" using BOSS.


Program-21-11: (BinaryObjectStorage; BOSS, onNew:, nextPut:, 
valueNowOrOnUnwindDo;, close)
-----------------------------------------------------------
    | fileStream bossStream writeBlock |
    fileStream := 'yyy' asFilename writeStream.
    bossStream := BinaryObjectStorage onNew: fileStream.
    writeBlock :=
        [| anImage |
        3 timesRepeat:
            [anImage := Image fromUser.
            bossStream nextPut: anImage]].
    [writeBlock value]
        valueNowOrOnUnwindDo: [bossStream close]
-----------------------------------------------------------

Program 21-12 reads three instances to display in a window one by one as you click the mouse.


Program-21-12: (BinaryObjectStorage; BOSS, onOld:, next, 
valueNowOrOnUnwindDo;, close)
-----------------------------------------------------------
    | fileStream bossStream readBlock |
    fileStream := 'yyy' asFilename readStream.
    bossStream := BinaryObjectStorage onOld: fileStream.
    readBlock :=
        [| activeWindow graphicsContext anImage |
        activeWindow := ScheduledControllers activeController view.
        graphicsContext := activeWindow graphicsContext.
        activeWindow clear.
        3 timesRepeat:
                [anImage := bossStream next.
                anImage displayOn: graphicsContext.
                activeWindow sensor waitClickButton].
        activeWindow display].
    [readBlock value]
        valueNowOrOnUnwindDo: [bossStream close]
-----------------------------------------------------------

BOSS manage persisten objects so that you can save objects from session to session.

Finally, let's switch subjects and look at 'Random'. You can think of Random as a read only internal stream or as a generator. Program 21-13 generates random numbers and is straight forward enough not to require further explanation.


Program-21-13: (Random; next)
-------------------------------------------------------
    | aRandom aNumber |
    aRandom := Random new.
    Transcript cr.
    25 timesRepeat:
        [aNumber := aRandom next.
        Transcript cr; show: aNumber printString]
--------------------------------------------------------


parent previous next question
Copyright (C) 1994-1996 by Atsushi Aoki
Translated by Kaoru Rin Hayashi & Brent N. Reeves