2

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?

Lyn Headley
  • 11,368
  • 3
  • 33
  • 35

2 Answers2

5

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!

Leandro Caniglia
  • 14,495
  • 4
  • 29
  • 51
  • Thank you for this, it helped me find my way. I am posting my own answer to try and give one more specific to my context. – Lyn Headley Mar 28 '15 at 20:42
2

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
Lyn Headley
  • 11,368
  • 3
  • 33
  • 35
  • Have you seen [this](http://stackoverflow.com/questions/29177818/how-to-stream-a-collection-backwards-without-copies)? In one of the answers Bert Freudenberg suggests using Generators for ad hoc streams. – Leandro Caniglia Mar 28 '15 at 21:05
  • 1
    One remark I would make is that your `QStream` class would be better defined as a subclass of `ReadStream`, as it models a read-only stream. – Leandro Caniglia Mar 28 '15 at 21:10
  • You also maybe went to have a look at the `Generator` class, just for inspiration – Tobias Mar 29 '15 at 10:53
  • If I understand right, ReadStream is a subclass of PositionableStream, but my QStream is not positionable (it has to "search" for its next position). – Lyn Headley Mar 29 '15 at 15:53
  • That's fine. Even if your stream has to search to find any given position, it can actually do that and so you are able to support the #position protocol. – Leandro Caniglia Mar 29 '15 at 17:28