Now for some homework. Let’s see how well you absorbed the material in the previous post.

  • Exercise 1: Implement a simple integer range type that implements the golden trio of interfaces. You need only deal with increasing ranges. You should provide a create method that takes a start value and end value. The resulting sequence should be start (start+1) (start+2) ... (end-1). Note that if end <= start you should return some representation of an empty sequence.

  • Exercise 3: Implement a type representing a sequence over a string. Provide a create method that takes a string. If the string is null or empty, you should return some representation of an empty sequence.

Good luck. (But I do grade on the curve.)

Don’t read ahead.

And now for my solutions to the exercises. Like some textbooks you may have encountered, we will only provide solutions to the odd-numbered exercises. Fortunatately, all the questions I pose are odd.

An integer range

You should be able to use much of the code in SimpleCons. The novel aspect of this type is the generative nature of next. With SimpleCons, next returns something that exists already, namely, the value in the tail field. Here, the next of the integer range [7,20) is [8,20 – it is a new SimpleIntRange object.

Here is my implementation, leaving out the parts that are identical to SimpleCons.

[<AllowNullLiteral>]
type SimpleIntRange private (startVal: int, endVal: int) =

    interface ISeq with
        member _.first() = upcast startVal

        member this.more() =
            if startVal = endVal then
                upcast SimpleEmptySeq()
            else
                upcast SimpleIntRange(startVal + 1, endVal)

        member this.cons(o) = upcast SimpleCons(o, (this :> ISeq))

    interface IPersistentCollection with
        member _.count() = endVal - startVal + 1

Our type will hold two fields, startVal and endVal. I have made the constructor private, to be called only from our code. When we do so, we will make sure that startVal < endVal, because otherwise we have an empty sequence and can return something else.

The definition of first should be obvious. For more, we detect when we have hit the end, hence the special case return of a SimpleEmptySeq; otherwise, it returns an object representing the range one step further along.

For cons, note that we can cons anything on to the front, so the result will not be a SimpleIntRange. We need a sequence that has the item at the front and our SimpleIntRange following; a SimpleCons will provide exactly that.

As for count, you have to know how to count.

How about the create method. Here is one possible implementation.

    static member create(startVal,endVal) : ISeq = 
        if endVal <= startVal then  
            SimpleEmptySeq()
        else
            SimpleIntRange(startVal,endVal)

A string sequence

This is similar in that next requires a new object to be created. The next of the sequence of characters based on "abcd" is a sequence for "bcd". One could create a new SimpleStringSeq with the truncated string. However, that creates a new string on each iteration step – that seems wasteful. Instead, we can include in our object an index indicating the position of the first character in the string.

Again, I leave out the duplicate code.

type SimpleStringSeq private (index : int, source : string) =

    interface IPersistentCollection with
        member _.count() = 
            if index < source.Length then source.Length-index else 0

        member this.equiv(o) =
            match o with
            | :? Seqable as s -> Util.seqEquiv (this :> ISeq) (s.seq ())
            | _ -> false

    interface ISeq with
        member _.first() = upcast source[index]
        member this.next() =
            if index + 1 < source.Length then
                SimpleStringSeq(index+1,source)
             else
                null
            
        member this.more() =
            let s = (this :> ISeq).next()
            if isNull s then SimpleEmptySeq()
            else s

count and first should be obvious.

I did play a little trick with more versus next. In SimpleCons, we showed defining next in terms of more. That is one trick. Another trick seen in the Clojure source is the opposite: Define more in terms of next. This can be done when deciding what is the next item is very straightforward. Here, next directly decides whether there is a sequence with elements to follow. If not, null can be returned. more can call next. If null comes back, more cannot just return null – it must return “a logical sequence for which seq returns nil,” i.e., a SimpleEmptySeq.

Finally, our create function:

    static member create (source : string) : ISeq =
        match source.Length with
        | 0 -> null
        | _ -> upcast SimpleStringSeq(0,source)

Source code for these examples are available at ClojureCLR-Next repo.