Subclassing Stream

128 Views Asked by At

I am interested in creating my own Stream subclass and I'm wondering what methods I should override (deploying on pharo and Gemstone). I have a collection with various types of things in it and I want to be able to stream over a subset of it, containing elements of a class. I don't want to copy the collection or use a collect: block because the collection may be large. My first use case is something like this:

stream := self mailBox streamOf: QTurnMessage.
stream size > 1
    ifTrue: [ ^ stream at: 2 ]
    ifFalse: [ ^ nil ]

Any pointers on what methods to override?

2

There are 2 best solutions below

1
On

In Smalltalk, when we say Stream we refer to objects that respond to a basic protocol given by some few methods such as #next, #nextPut:, #contents, etc. So, before going into further detail I would say that stream at: 2, as you put in your example, is not a very appropriate expression. More appropriate expressions for a Stream would be

stream position: 2.
^stream next

So, the first thing you have to consider is whether you are looking for a Stream or a Collection. This basic decision depends on the behavior your objects will have to implement.

Use a subclass of Stream in case you decide that you want to enumerate the elements using #next, i.e. mostly in sequential order. However, if you want to access your elements via at: index, model your objects with a subclass of SequenceableCollection.

In case you choose streams, you will have to decide whether you will be only accessing them for reading operations or will also want to modify their contents. Your description of the problem seems to indicate that you will just read them. Therefore the basic protocol you have to implement first is

#next "retrieve the object at the following position and advance the position"
#atEnd "answer with true if there are no more objects left"
#position "answer the current position of the implicit index"
#position: "change the implicit index to a new value"

Also, if your streams will be read only, make your class a subclass of ReadStream.

There are some few other additional messages you will have to implement if you want to inherit fancier methods. An example would be #next: which retrives a subcollection of several consecutive elements (its size being given by the argument.)

If you instead think that it would be better to model your objects as collections, then the basic protocol you will have to implement consists of the following three methods

#at: index "retrieve the element at the given index"
#size "retrieve the total number of elements"
#do: aBlock "evaluate aBlock for every element of the receiver"

(I don't think your collections have to support at:put:.)

Very recently we had the same problem you are describing and decided to model our objects as collections (rather than streams.) However, regardless of which approach you will finally follow I think that you should try both and see which one is better. Nobody will give you better advice than your Smalltalk system.

Incidentally, note also that if you have a (sequenceable) Collection, you will get a Stream for free: just send #readStream to your collection!

5
On

I needed to override next and atEnd. My Stream subclass takes a block and a collection, and iterates over all elements of the collection for which block evaluates to true.

Example usage:

e := Array with: 1 with: 2 with: 3. 
a := QStream collection: e block: [ :i| i odd ].

a next. "1"
a next. "3"

Here is the core of it:

Stream subclass: #QStream
    instanceVariableNames: 'collection block index found'
    classVariableNames: ''
    poolDictionaries: ''
    category: 'QDialog-Core'

initialize
    super initialize.
    index := 1.
    self search.

next
    | result |
    result := found.
    self search.
    ^result.

search
    [ index < collection size and: [ (block value: (collection at: index)) not ] ]
      whileTrue: [ index := index + 1 ].
    self atEnd
        ifTrue: [ ^ found := nil ]
        ifFalse: [ 
            found := collection at: index.
            index := index + 1 ]

atEnd
^   index > collection size