Repository navigation
Category Iterators
Home › All Categories › Iterators
OPERATOR FOR / OPERATOR NEXT / OPERATOR STEP form an iteration protocol of their own, which serves
for i as T = a to bThat is a RANGE protocol. It does not solve the collection problem, for three reasons:
- Seven declarations and seven definitions to iterate one type -- two
constructors and an OPERATOR LET on top of the three iteration operators,
because the loop variable is copy-constructed and assigned.- The loop variable IS the iterator, not the element. The body says
'i.cur'. Every use site has to know the iterator's representation.- FOR is 'start TO limit', and the limit is structural to the statement.
Iterating a linked list means inventing a sentinel object meaning
"exhausted". The collection is never named in the loop at all.This protocol is the collection half. A collection hands out an iterator; the iterator hands out elements until it runs out.
A type is ITERABLE if it has
declare function GetIterator( ) as Itaking no parameters, where I is an ITERATOR TYPE. GetIterator may be CONST.
A type is an ITERATOR TYPE if it has all three of
declare function IsValid( ) as boolean '' still on an element?
declare function Value( ) [byref] as E '' the current element
declare sub MoveNext( ) '' advanceall taking no parameters. E -- whatever Value returns -- is the ELEMENT TYPE. IsValid and Value may be CONST; MoveNext may not.
That is the whole thing:
type IntListIterator
as IntNode ptr node
declare function IsValid( ) as boolean
declare function Value( ) byref as int32
declare sub MoveNext( )
end type
function IntListIterator.IsValid( ) as boolean
return this.node <> 0
end function
function IntListIterator.Value( ) byref as int32
return this.node->value
end function
sub IntListIterator.MoveNext( )
this.node = this.node->nxt
end sub
type IntList
as IntNode ptr head
declare function GetIterator( ) as IntListIterator
end type
function IntList.GetIterator( ) as IntListIterator
dim it as IntListIterator
it.node = this.head
return it
end functionThere is no Iterable interface to implement and nothing to inherit. A type is iterable because name lookup finds the members.
This is deliberate. tiko has no interface feature independent of inheritance, so a nominal protocol would consume the type's single inheritance slot, add a vtable and a virtual call per element to types that are otherwise plain data, and force every existing collection to be modified. Structural matching costs one name lookup at compile time and produces a direct, inlinable call.
The cost is that a type can conform by accident, and that a near-miss ('MoveNxt') reads as "not iterable" rather than "you misspelled it".
None of this is checkable by the compiler.
- IsValid is pure and repeatable. Calling it twice without an intervening
MoveNext gives the same answer.- Value may be called only while IsValid is true, and may be called more than
once for the same element.- MoveNext may be called only while IsValid is true. After the last element it
leaves the iterator with IsValid false.- A freshly returned iterator is ALREADY ON the first element, or has IsValid
false for an empty collection. THERE IS NO "BEFORE THE FIRST ELEMENT" STATE
-- MoveNext is not called before the first Value. This differs from C#'s
IEnumerator, and it is what lets a linked-list iterator be one pointer and
an array iterator be a pointer and an index, with the empty case falling
out for free.- Mutating the collection's STRUCTURE during iteration invalidates the
iterator. Mutating an element's VALUE through a byref Value does not.The element type is whatever Value returns. Declare it BYREF and elements can be modified in place:
function ArrayIterator( of T ).Value( ) byref as T
return this.first[ this.idx ]
end functionDeclare it by value and the caller gets a copy. Both are legal; the choice is the implementer's.
With generics the iterator is written once:
type ArrayIterator( of T )
as T ptr first
as int32 idx, count
declare function IsValid( ) as boolean
declare function Value( ) byref as T
declare sub MoveNext( )
end type
type Buf( of T )
as T e( 0 to 7 )
as int32 n
declare function GetIterator( ) as ArrayIterator( of T )
end typeNote that Buf's GetIterator returns an instantiation of a DIFFERENT generic. That works, and it is the shape the standard containers use.
An iterator has ordinary tiko lifetime. If it holds a resource, give it a destructor and the destructor runs when it goes out of scope -- including on EXIT FOR, on GOTO out of a loop, and while an error unwinds the scope. The protocol adds no lifetime rule of its own; it relies on the one the language already has.
This is why GetIterator returns BY VALUE rather than allocating. A typical iterator is a pointer and two integers and costs nothing.
OPERATOR FOR / NEXT / STEP is a fully supported protocol in its own right. A type may implement both protocols and they do not interfere: they are looked up separately and neither falls back to the other.
A collection is even allowed to BE its own iterator (GetIterator returning itself by value). Convenient for one-shot generators, and an easy way to make an aliasing mistake -- two "independent" iterations would share one cursor.
- CONST OVERLOADING IS NOT AVAILABLE. tiko does not distinguish
overloads by constness alone:
declare function f( ) as int32
declare const function f( ) as int32
error 4: Duplicated definition
So a type cannot supply a const and a non-const GetIterator pair. Declare
the single GetIterator CONST -- it then serves const and non-const
collections alike, which is what a container wants anyway.- SINGLE PASS ONLY. There is no Reset, no MovePrev, no random access and no
way to ask a collection for its length. That is enough to walk a
collection and not enough for a generic algorithms library.- The names GetIterator, IsValid, Value and MoveNext are NOT reserved words
and are not in the keyword table. A type that has them compiles and
behaves as it would without the protocol; the only consequence is that it
may also be walkable.tests/generics/iterator-protocol.bas exercises every shape above: the hand-written linked-list iterator, the generic one, byref and by-value Value, a const GetIterator reached from const and non-const collections, an empty collection, two independent iterators over one collection, element types that are UDTs and generic instantiations, an iterator with a destructor (asserting constructed == destroyed), and one type carrying both protocols at once.
tiko-lang · Home · Feature Index · Keyword Index · License
FreeBASIC manual pages reproduced under the GNU FDL 1.3.
- Generics
- Interfaces
- For Each
- Iterators
- Tuples
- Array Results
- Defer
- Lambdas
- Block Strings
- Partial Types
- String Slices
- Embed
- Typed Variadics
- C Header Import
- Foreign Library Linking
- UString
- Fixed-Width Types
- Nullptr
- String Library
- Format
- Array
- Map
- Set
- LinkedList
- OrderedMap
- OrderedSet
- Sorting
- Optional
- Result
- Smart Pointers
- Date and Time (Chrono)
- Filesystem
- File Streams
- Config (INI and TOML)
- Codec
- Hash
- UUID
- OS
- Process
- Net
- HTTP and TLS
- Math Library
- Finance
- Logging
- Terminal TUI Library
- PsControls
- tkControls
- sys/win
- Goroutines and Channels
- LLVM Backend
- tiko build
- TIKO_COMPILER
- Variable Declarations
- Standard Data Types
- User Defined Types
- Procedures
- Operators
- Control Flow Statements
- Converting Data Types
- Array Functions
- String Functions
- Mathematical Functions
- Bit Manipulation
- Date and Time Functions
- File I/O Functions
- Console Functions
- User Input (Runtime Library)
- User Input Functions (Graphics Library)
- Screen Functions
- 2D Drawing Functions
- Graphics Library Reference
- Graphics Keyword List
- Memory Functions
- Threading Support Functions
- Error Handling Functions
- Operating System Functions
- Preprocessor
- Intrinsic Defines
- Miscellaneous Keywords
- Modularizing