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
createmethod that takes a start value and end value. The resulting sequence should be
start (start+1) (start+2) ... (end-1). Note that if
end <= startyou should return some representation of an empty sequence.
Exercise 3: Implement a type representing a sequence over a string. Provide a
createmethod 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 returns something that exists already, namely, the value in the
tail field. Here, the
next of the integer range
[8,20 – it is a new
Here is my implementation, leaving out the parts that are identical to
[<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,
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.
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.
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
first should be obvious.
I did play a little trick with
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
null comes back,
more cannot just return
null – it must return “a logical sequence for which seq returns nil,” i.e., a
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.