Hello everyone,
from time to time I experiment a bit with things possible with modern language features. One of those experiments was an iterator library, which I found now to be in a publishable state.
It's a library for container independent iterators. It allows to iterate over data from different sources (e.g. Arrays, Lists, Streams, etc.) and modify the data on the fly (map, filter, fold, take, etc.). This allows to reduce complex functionality involving multiple nested loops to a small series of few iterator operations.
As a practical example on why this is useful, consider the following problem, you want to do a frequency analysis of some text file, an count the occurances of each character and print out the most used characters. You can either do this the classical way:
m := TDict.Create;
try
fs := TFileStream.Create(filename, fmOpenRead);
try
// Read file charwise
while True do
begin
if fs.Read(c, SizeOf(c)) < SizeOf(c) then
Break;
CountMap(m, c); // Will add count to m
end;
// Sort by occurance
arr := m.ToArray;
Sort(arr, Greate); // Sort ascending by value
// print out top 5
for i:=0 to 4 do
WriteLn(' ''', arr[i].Key,''': ', arr[i].Value);
finally
fs.Free;
end;
finally
m.Free;
end;
Which while being easily possible, is very bulky. Instead, using simple iterators it can be reduced to:
m := TDict.Create;
try
for p in Take<TDictPair>(5, // Show top 5 used chars
Sorted<TDictPair>(Greater, // Sort by most used
Iterate<Char, Integer>( //Iterate through resulting dict
FoldR<TDict, Char>(CountMap, m, // Count characters in a dict
Iterate<Char>(TFileStream.Create(filename, fmOpenRead) // Iterate over file contents
))))) do
WriteLn(' ''', p.Key,''': ', p.Value);
finally
m.Free;
end;
The code and package you can find in the
GitHub Repository together with additional information about the usage. Examples for all provided functions can be found in the
Examples.
Warning: This library uses a lot of generics and other modern compiler features, which FPC handles... well let's say roughly. This means it is very unstable, not because of the code itself is (though of course I only tested so much by myself), but because the fpc may at any point decide to throw internal exceptions, internalerrors or other errors. I tested it locally with 3.2.2 which works sometimes, and Trunk which seems to be a bit more stable. Also it may break Lazarus CodeTools, as it has trouble parsing generics correctly.