Skip to content
/ liter Public

A powerful Luau library for enhanced Rust-like iterators

License

Notifications You must be signed in to change notification settings

ok-nick/liter

Repository files navigation

liter

CI Status Latest Release Discord

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

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.

Sources

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.

Adapters

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.

Zip

Consumers

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.