liter
A powerful Luau library for enhanced Rust-like iterators.
- Extensibility: Liter makes it easy to define custom behavior while preserving functionality.
- Productivity: Improve your workflow as you write complex yet legible code.
- Parallel?: Due to liter's extensible nature, parallel iterators could be implemented for significant performance boosts.
Installation
There are multiple ways to install liter:
Using Studio:
Pre-built binaries are available on the GitHub release page. Simply install the .rbxm
file and import directly into studio.
Liter is also available in the catalog.
Using Rojo:
Same rules apply for installing to studio, just add the .rbxm
to your .project.json
and you should be good to go.
Using Kayak:
In your rotriever.toml
file under the [dependencies]
section, add:
liter = 'https://github.com/ok-nick/liter.git'
Examples
Numerous examples exist within the unit tests, although I specifically suggest reviewing the consumer unit tests.
Iterating over an array's values:
liter.array({ 1, 2, 3 }):foreach(function(value)
doSomething(value)
end)
for value in liter.array({ 1, 2, 3 }) do
doSomething(value)
end
Iterating over an array's index/value:
Unbox is used here to unpack the index/value from its array. This behavior is also seen while using the Hash composite.
liter.array({ 1, 2, 3 }):enumerate():unbox():foreach(function(index, value)
doSomething(index, value)
end)
for index, value in liter.array({ 1, 2, 3 }):enumerate():unbox() do
doSomething(value)
end
Performance
Rust iterators are lazily evaluated which means in order to replicate its behavior, I cannot use native iterators. Although this degrades performance, it offers some unique functionality! It allows you to pass iterators and evaluate them when its actually necessary. This means you don't have to iterate over a table, do some calculations, pass the table somewhere else, iterate and evaluate, and so on... In a situation like this, liter could outperform native iterators!
A Lua-oriented functional approach is in the making for on-demand performant iterators.
Run the benchmarks using boatbomber's benchmark plugin.
Documentation
All behavior is mimicked from Rust iterators. I suggest searching their for clear and concise documentation, although there are a few gotchas and added features documented below.
Composites
Array
liter.array(array) -> Iterator
Iterates over values in an array. An array iterator will only return the value unless combined with the Enumerate adapter.
Ascii
liter.ascii(string) -> Iterator
Iterates over ascii characters in a string.
If you're not sure whether to use Ascii or Utf8, then your most likely just going to need Ascii.
Hash
liter.hash(hash) -> Iterator
Iterates over key/values in a HashMap/Dictionary.
A hash iterator will return the key/value pair packed into an array. Use the Unbox consumer to unpack the array as shown in the example.
Utf8
liter.utf8(string) -> Iterator
Iterates over utf8 characters in a string.
Sources
Empty
FromFunc
Keys
liter.keys(table) -> Iterator
Iterates over keys in a table.
Once
OnceWith
Range
liter.range(min, max) -> Iterator
Iterates numerically from the min to the max value.
Recur
This method replicates Rust's repeat source, although is renamed to adhere with Lua syntax.
RecurWith
This method replicates Rust's repeat_with source, although is renamed to adhere with Lua syntax.
Successors
Values
liter.values(tbl) -> Iterator
Iterates over values in a table.
Adapters
Chain
Cycle
This adapter adopts the same syntax of a Rust Cycle, although it differs in behavior. The first iteration will cache the resulting values for future iterations. This behavior is guaranteed to change as development progresses.
Enumerate
Filter
FlatMap
Inspect
Intersperse
IntersperseWith
Map
Peekable
Scan
Skip
SkipWhile
StepBy
Take
TakeWhile
Unbox
Iterator:unbox()
Unpacks a value into a "tuple." This is particularly useful when dealing with the Hash composite.
Without Unbox
:
liter.hash({ 1, 2, 3 }):foreach(function(pair)
doSomething(pair[1], pair[2])
end)
With Unbox
:
liter.hash({ 1, 2, 3 }):unbox():foreach(function(key, value)
doSomething(key, value)
end)
NOTE: Unbox will ONLY work with packed array returns.
Consumers
foreach
fold
reduce
sum
product
count
last
advanceBy
nth
partition
partitionInPlace
isPartitioned
all
any
find
position
max
min
eq
eqBy
ne
FAQ
Want more control over how you iterate?
Liter makes this easy by providing an Iterator
table which is intended to be set as your iterator's metatable. This process will extend the functionality of built-in iterators.
Example of the built-in array iterator:
local Array = setmetatable({}, liter.Iterator)
Array.__index = Array
Array.__call = liter.Iterator.__call
function Array.new(array)
return setmetatable({
array = array,
index = 1,
}, Array)
end
function Array:after()
local index = self.index
self.index += 1
return self.array[index]
end
Assigning __call
is necessary to preserve the for loop syntax as shown in the example.
Creating custom adapters is exactly the same, as seen by the Skip adapter. It is important to recognize that you could still call internal consumers from self
.
Any more questions?
The best way to get in contact with me is through discord.
License
Liter is MIT licensed.