yield

yield

yield

The generators so far have been built `by hand' (as lists) or by chaining and alternation. Although theoretically sufficient, these methods become ungainly when building generators encapsulating traversals with large amount of state. In a recursive traversal function, the state is implicit in the local variables of recursive invocations of the function. When writing generators, or zippers, the state has to be explicated, to be manipulated by the three operations of the generator interface (emptiness test, query and advance). Generators encapsulating traversal are closely related to zippers. Huet zippers however deal with algebraic data structures; the algebraic structure helps writing zippers, essentially by differentiation. Generators are more general, encapsulating traversals of virtual collections, with arbitrary effects.

When CLU borrowed generators from Alphard it introduced a new way of building them. Rather than writing the emptiness test, the query and the advance operations and packaging them up, a CLU programmer would use yields . The only way to write a generator (called iterator) in CLU was by suspending, usually an iterative computation. Since all iterative computations in CLU were built around iterators, yields was a mode of composing them. The inspiration for yields came from Simula 67 coroutines. Icon has a similar form, called suspend , borrowed from SL5, also inspired by Simula's coroutines.

Here is an example of suspend in Icon from the Icon's overview. Applying the procedure findodd defined below to the strings s1 and s2 non-deterministically gives all odd-valued positions at which s1 occurs in s2 .

procedure findodd(s1, s2) every i := find(s1, s2) do if i % 2 = 1 then suspend i end

find

findodd

findodd

findodd :: MonadPlus m => String -> String -> m Int findodd s1 s2 = do i <- findIM s1 s2 if i `mod` 2 == 1 then return i else mzero

find

The construct suspend , often called yield , has become the emblem of generators, spreading to many other languages. Our next example comes from Python, which, since version 2.2, has generators and the keyword yield to build them. The example is quoted from David Mertz's Charming Python article, as an elegant, exemplary generator. The similarity to Icon is quite striking.

>>>> # A recursive generator that generates Tree leaves in in-order. >>> def inorder(t): ... if t: ... for x in inorder(t.left): ... yield x ... yield t.label ... for x in inorder(t.right): ... yield x

We describe several attempts at implementing this example in Haskell. First, we need a tree datatype

type Label = Int data Tree = Leaf | Node Label Tree Tree deriving Show

tree1

Node 1 (Node 2 (Node 4 Leaf Leaf) (Node 5 Leaf Leaf)) (Node 3 (Node 6 Leaf Leaf) (Node 7 Leaf Leaf))

The first attempt builds the in_order generator by alternation. The current element may come from the left branch, from Node's label, or from the right branch:

in_order1 Leaf = mzero in_order1 (Node label left right) = in_order1 left `mplus` return label `mplus` in_order1 right

in_order1_r = observe (bagofN Nothing $ in_order1 tree1) >>= print -- [4,2,5,1,6,3,7] in_order1_r' = observe $ iter Nothing $ in_order1 tree1 >>= liftIO . print

Traversing with yield looks quite similar to the logging of values as we compute -- to the Writer monad. It is tantalizing to implement yield as tell . It indeed works, in the first approximation. Here is our traversal generator:

in_orderW :: (MonadWriter [Label] m, MonadIO m) => Tree -> m () in_orderW Leaf = return () in_orderW (Node label left right) = do in_orderW left liftIO . putStrLn $ "traversing: " ++ show label tell [label] in_orderW right

We have added the statement to print the label, the trace, right before `yielding.' Alas, this code is too strict, making the traversal not incremental. We have to complete the traversal before we get hold of the accumulated `log'. The following makes the problem clear:

in_orderW_r = do (_,labels) <- runWriterT $ in_orderW tree1 let some_labels = take 3 labels print some_labels

We only wanted the first three labels encountered during the traversal. The printed trace shows that we have traversed the whole tree nevertheless.

Our last attempt uses `final zippers', implemented with the library of delimited control, the CC monad.

in_orderCC :: Monad m => Tree -> CC (P m Label) m () in_orderCC Leaf = return () in_orderCC (Node label left right) = do in_orderCC left yield label in_orderCC right