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.
There are multiple ways to install liter:
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.
Same rules apply for installing to studio, just add the .rbxm
to your .project.json
and you should be good to go.
In your rotriever.toml
file under the [dependencies]
section, add:
liter = 'https://github.com/ok-nick/liter.git'
Numerous examples exist within the unit tests, although I specifically suggest reviewing the consumer unit tests.
liter.array({ 1, 2, 3 }):foreach(function(value)
doSomething(value)
end)
for value in liter.array({ 1, 2, 3 }) do
doSomething(value)
end
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
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.
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.
liter.array(array) -> Iterator
Iterates over values in an array. An array iterator will only return the value unless combined with the Enumerate adapter.
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.
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.
liter.utf8(string) -> Iterator
Iterates over utf8 characters in a string.
liter.keys(table) -> Iterator
Iterates over keys in a table.
liter.range(min, max) -> Iterator
Iterates numerically from the min to the max value.
This method replicates Rust's repeat source, although is renamed to adhere with Lua syntax.
This method replicates Rust's repeat_with source, although is renamed to adhere with Lua syntax.
liter.values(tbl) -> Iterator
Iterates over values in a table.
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.
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.
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
.
The best way to get in contact with me is through discord.
Liter is MIT licensed.