External Enumerators in Smalltalk

Smalltalk collections have a rich protocol for enumerating their
elements: simple iteration with do:, mapping with collect:,
filtering with select: and reject:, reducing with fold: and
inject:into:, and more.

Many systems contain objects that are logically collections, but
rightfully do not inherit from classes in the collection hierarchy.
Perhaps they contain a collection internally, but expose a more
restricted API for interacting with the collection.

Some systems contain complex data structures that need to be
enumerated. It is often possible to expose some kind of a do:
method for enumerating the elements of the structure, but the other
enumeration methods are rarely provided. Behaviour>>subclassesDo:
and VisualComponent>>childrenDo: are examples.

If we want to provide the full enumeration API in these cases, our
choices are limited:

Expose an internal collection via a method. This allows client code
access to the full enumeration API, but also allows access to the
entire collection API. This can be dangerous, as the client code can
then modify the internal collection without our class knowing about
it. For complex data structures, there isn’t even an internal
collection to expose.

Use traits, assuming there is
a working implementation for your Smalltalk.

Re-implement the full enumeration API on the class, delegating the
methods to the internal collection. This is a lot of tedious work
that must be repeated for each collection-like object in the system.

ExternalEnumeration
provides a new option to solve these problems. It defines
Enumerator, an object that takes a target object and a do:
selector and provides the full, rich enumeration API that Smalltalk
programmers expect.

To create an Enumerator, send the #on:selector: message to the
Enumerator class. The resulting enumerator responds to all of the
normal collection enumeration messages. The selector must be the name
of a method that provides do: semantics for the target object.

Simple Enumerator

subclasses:=Enumeratoron:Collectionselector:#allSubclassesDo:.

(subclassesgroupedBy:#superclass)inspect.

Some enumeration messages return a collection (collect:, select:,
reject:, and groupedBy:). By default, the resulting collection
will be an OrderedCollection (or a Dictionary whose values are
OrderedCollections in the case of groupedBy:). If you’d like to
use a different collection type (like Set or Array), you can use
the #on:selector:species: method instead:

Enumerator With Species

MyCollectionObject>>elements

^Enumeratoron:internalElementsselector:#do:species:Set

ExternalEnumeration’s primary home is the
Cincom Public Store Repository.
Check there for the latest version. I’ve put a snapshot of the
current version of ExternalEnumeration on
GitHub. I’ve
also submitted it for inclusion as a contributed package in a
forthcoming Visualworks release.

ExternalEnumeration was developed in VW 7.9.1, but is intended to be
compatible with any version of Visualworks Smalltalk. The code could
also easily be ported to any other Smalltalk.