Module functional
A module for functional programming utils.
About the module
This module seeks to provide some utility functions and structures which are too verbose in vanilla lua, in particular with regards to iteration and inline function definition.
The module is writen completely in vanilla lua, with no dependencies on external packages. This was a decision made for portability, and has drawbacks. Since none of this was written as a C binding, it is not as performant as it could be.
For example, luafun is "high-performance functional programming library for Lua designed with LuaJIT's trace compiler in mind" . If your environment allows you to use LuaJIT and performance is a concern, perhaps luafun will be more suited for your needs.
The motivation behind this module is, again, portability. If you want to embed this code on a webpage, or use it in some weird system for which a C binding wouldn't work, this project is aimed at you.
Definitions
Array
As lua doesn't have a dedicated array
type, the word "array" in this document referes to a table with contiguous
non-nil
values starting at index 1
.
Iterable
An iterable
refers to either of:
- An array (see above); or
- An instance of
Iterator
.
Info:
- Copyright: 2021
- Release: 1.6.0
- License: MIT
- Author: William Quelho Ferreira
Functions
iterate (iterable) | Create an Iterator for the iterable . |
counter () | Iterate over the naturals starting at 1. |
range ([start=1], stop[, step=1]) | Create an integer iterator that goes from start to stop , step -wise. |
filter (iterable, predicate) | Select only values which match the predicate. |
map (iterable, mapping) | Map values into new values. |
reduce (iterable, reducer, initial_value) | Collapse values into a single value. |
foreach (iterable, func) | Apply a function to all values. |
last (iterable) | Return the last value of the given iterable. |
take (iterable, n) | Iterate over the n first values and stop. |
skip (iterable, n) | Iterate over the values, starting at the (n+1) th one. |
every (iterable, n) | Take 1 value every n . |
any (iterable, predicate) | Checks if any values evaluate to true . |
all (iterable, predicate) | Checks if all values evaluate to true . |
zip () | Iterate over two iterables simultaneously. |
packed_zip () | Iterate over two iterables simultaneously. |
enumerate () | Iterate with an added index. |
concat () | Concatenate two iterables into an Iterator. |
nop () | Does nothing. |
identity (...) | Returns its arguments in the same order. |
lambda (lambda_def[, env={}]) | Create a lambda function from a given definition string. |
clambda (expr, extra_env) | Create a context-aware lambda function. |
to_array (iterable) | Return an array version of the iterable . |
to_coroutine (iterable) | Create a coroutine that yields the values. |
negate (predicate) | Create a negated function of predicate . |
compose (f1, f2, ...) | Create a function composition from the given functions. |
bind (func, ...) | Create a function with bound arguments. |
curry (func, levels) | Curries a function by the given amount of arguments. |
dict (t) | Create a function that accesses t . |
indexer (k) | Create a function that accesses the key k for any table. |
bind_self (t, k, ...) | Create a bound function whose first argument is t . |
constant (value) | Create a function that always returns the same value. |
import ([env=_G]) | Import Iterator , lambda , and commonly used functions into a given scope. |
Fields
_VERSION | Module version. |
Class Iterator
Iterator.over (iterable) | Iterate over the given iterable . |
Iterator:next () | Retrieve the next element from the iterator, if any. |
Iterator.counter () | Iterate over the naturals starting at 1. |
Iterator.range ([start=1], stop[, step=1]) | Create an integer iterator that goes from start to stop , step -wise. |
Iterator.from (func, is, var) | Iterate over the function's returned values upon repeated calls. |
Iterator.packed_from (func, is, var) | Iterate over the function's returned values (packed into a table) upon repeated calls. |
Iterator.from_coroutine (co) | Iterate over the coroutine 's yielded values. |
Iterator.clone (iterable) | Nondestructively return an independent iterable from the given one. |
Iterator:filter (predicate) | Select only values which match the predicate. |
Iterator:map (mapping) | Map values into new values. |
Iterator:reduce (reducer, initial_value) | Collapse values into a single value. |
Iterator:foreach (func) | Apply a function to all values. |
Iterator:last () | Consume the iterator and retrieve the last value it produces. |
Iterator:take (n) | Iterate over the n first values and stop. |
Iterator:take_while (predicate) | Iterate while predicate is true and stop. |
Iterator:take_last (n) | Consume the iterator and produce its n last values in order. |
Iterator:take_until (predicate) | Iterate while predicate is false and stop. |
Iterator:skip (n) | Iterate over the values, starting at the (n+1) th one. |
Iterator:skip_while (predicate) | Iterate over the values, starting whenever predicate becomes false for the first time. |
Iterator:skip_until (predicate) | Iterate over the values, starting whenever predicate becomes true for the first time. |
Iterator:every (n) | Take 1 value every n . |
Iterator:any (predicate) | Checks if any values evaluate to true . |
Iterator:all (predicate) | Checks if all values evaluate to true . |
Iterator:count (predicate) | Counts how many values evaluate to true . |
Iterator:zip (other) | Iterate over two iterables simultaneously. |
Iterator:packed_zip (other) | Iterate over two iterables simultaneously, giving their values as an array. |
Iterator:enumerate () | Include an index while iterating. |
Iterator:concat (other) | Append elements from other after this iterator has been exhausted. |
Iterator:to_array () | Create an array out of the Iterator 's values. |
Iterator:to_coroutine () | Create a coroutine that yields the values. |
Iterator:is_complete () | Check whether or not the iterator is done. |
Functions
- iterate (iterable)
-
Create an
Iterator
for theiterable
.Equivalent to
Iterator.over(iterable)
.Parameters:
- iterable iterable the values to be iterated over
Returns:
- counter ()
-
Iterate over the naturals starting at 1.
Returns:
-
Iterator
the counter
See also:
- range ([start=1], stop[, step=1])
-
Create an integer iterator that goes from
start
tostop
,step
-wise.Parameters:
- start integer the start of the integer range (default 1)
- stop integer the end of the integer range (inclusive)
- step integer the difference between consecutive elements (default 1)
Returns:
See also:
- filter (iterable, predicate)
-
Select only values which match the predicate.
Equivalent to
iterate(iterable):filter(predicate)
.Parameters:
- iterable iterable the values to be filtered
- predicate predicate the function to evaluate for each value
Returns:
See also:
- map (iterable, mapping)
-
Map values into new values.
Equivalent to
iterate(iterable):map(mapping)
.Please note that at no point during iteration may the
mapping
function returnnil
as its first value.Parameters:
- iterable iterable the values to be mapped
- mapping function the function to evaluate for each value
Returns:
See also:
- reduce (iterable, reducer, initial_value)
-
Collapse values into a single value.
Equivalent to
iterate(iterable):reduce(reducer, initial_value)
.A reducer is a function of the form
function(accumulated_value, new_value)
which returns the reducing or "accumulation" ofaccumulated_value
andnew_value
The definition of "reducing" is flexible, and a few common examples include sum and concatenation.
Parameters:
- iterable iterable the values to be collapsed
- reducer reducer the collapsing function
- initial_value
the initial value passed to the
reducer
Returns:
-
the accumulation of all values
See also:
- foreach (iterable, func)
-
Apply a function to all values.
Equivalent to
iterate(iterable):foreach(func)
.The main difference between
foreach
andmap
is thatforeach
ignores the return value(s) of its function, while map uses them and has restrictions on what it can return.Another important difference is that
map
is a lazy evaluator, whileforeach
iterates over its values immediately.Parameters:
- iterable iterable the values to be iterated over
- func function the function to apply for each value
See also:
- last (iterable)
-
Return the last value of the given iterable.
If
iterable
is an iterator, this call is equivalent toiterable:last()
. Otherwise, this call accesses the last element in the array.Parameters:
- iterable iterable the sequence whose last element is to be accessed
Returns:
-
the last element in the sequence
See also:
- take (iterable, n)
-
Iterate over the
n
first values and stop.Equivalent to
iterate(iterable):take(n)
.Parameters:
- iterable iterable the values to be iterated over
- n integer amount of values to take
Returns:
See also:
- skip (iterable, n)
-
Iterate over the values, starting at the
(n+1)
th one.Equivalent to
iterate(iterable):skip(n)
.Parameters:
- iterable iterable the values to be iterated over
- n integer amount of values to skip
Returns:
See also:
- every (iterable, n)
-
Take 1 value every
n
.Equivalent to
iterate(iterable):every(n)
.The first value is always taken.
Parameters:
- iterable iterable the values to be iterated over
- n integer one more than the number of skipped values
Returns:
See also:
- any (iterable, predicate)
-
Checks if any values evaluate to
true
.Equivalent to
iterate(iterable):any(predicate)
.Parameters:
- iterable iterable the values to be iterated over
- predicate
predicate
the function to evaluate for each value,
defaults to
not (value == nil or value == false)
Returns:
-
boolean
true
if and only if at least one of the values evaluate totrue
See also:
- all (iterable, predicate)
-
Checks if all values evaluate to
true
.Equivalent to
iterate(iterable):all(predicate)
.Parameters:
- iterable iterable the values to be iterated over
- predicate
predicate
the function to evaluate for each value,
defaults to
not (value == nil or value == false)
Returns:
-
boolean
true
if and only if all of the values evaluate totrue
See also:
- zip ()
-
Iterate over two iterables simultaneously.
See also:
- packed_zip ()
-
Iterate over two iterables simultaneously.
See also:
- enumerate ()
-
Iterate with an added index.
See also:
- concat ()
-
Concatenate two iterables into an Iterator.
See also:
- nop ()
- Does nothing.
- identity (...)
-
Returns its arguments in the same order.
Parameters:
- ... the values to be returned
Returns:
-
the given values
- lambda (lambda_def[, env={}])
-
Create a lambda function from a given definition string.
DO NOT USE THIS WITH UNTRUSTED AND/OR UNKNOWN STRINGS!
This and clambda are meant to facilitate the creation of inline functions, given the vanilla Lua syntax is quite verbose (especially with higher order functions).
To attempt limit user error and naïve attacks, some restrictions are put in place with respect to the definition:
- It must be of the form
(arglist) => body
, wherebody
is either another lambda definition or an expression that could normally be put inside parenthesis in Lua. - The
()=>
chain must not be longer than 10 steps. - The environment
env
must not contain more than 150 string keys. - The definition string must not be longer than 100 bytes.
- The definition string must not contain a newline ("
\n
") character. - The definition string must not contain the substring "
--
". - The definition string must not contain the word "
end
".
Parameters:
- lambda_def string or table the definition to be made into a function
- env table the function environment (default {})
Returns:
-
function
the generated function
See also:
Usage:
local add = f.lambda "(x, y) => x + y" print(add(2, 3)) --> 5 local emphasis = f.lambda("(x)=>x..suffix", {suffix = "!!"}) -- f.lambda can even create higher order functions easily local linear_combinator = f.lambda "(m) => (x, y) => m*x + y" local comb2 = linear_combinator(2) print(comb2(3, 4)) --> 10 -- lambdas can "see" variables given in their environment local emphasis = f.lambda("(x)=>x..suffix", {suffix = "!!"}) emphasis = f.lambda{"(x)=>x..suffix", suffix = "!!"} -- alternative way to define environment print(emphasis("Hello")) --> Hello!!
- It must be of the form
- clambda (expr, extra_env)
-
Create a context-aware lambda function.
DO NOT USE THIS WITH UNTRUSTED AND/OR UNKNOWN STRINGS!
This works similarly to lambda, except it automatically adds any non-
false
and non-nil
locals and globals into its environment. As a flip-side, its environment is now read-only.See lambda for more information on restrictions and usage.
Parameters:
- expr string or table the expression to be made into a function
- extra_env table additional values to insert into the environment
See also:
Usage:
local angle = math.pi / 3 local offset_sin = f.clambda "(x) => math.sin(x + angle)" print(offset_sin(math.pi)) --> -0.866 print(offset_sin(2 * math.pi / 3)) --> 0 local alternative = f.clambda("(x)=>sin(x + angle)", {sin=math.sin}) local or_even = f.clambda{"(x)=>sin(x+angle)", sin=math.sin}
- to_array (iterable)
-
Return an array version of the
iterable
.If
iterable
is an array, return itself.If
iterable
is anIterator
, returniterable:to_array()
Parameters:
- iterable iterable the values to make an array out of
Returns:
-
array
the array
See also:
- to_coroutine (iterable)
-
Create a
coroutine
that yields the values. of theiterable
.Equivalent to
iterate(iterable):to_coroutine()
.Parameters:
- iterable iterable the values to be iterated over
Returns:
-
thread
The new
coroutine
See also:
- negate (predicate)
-
Create a negated function of
predicate
.Parameters:
- predicate predicate the function to be negated
Returns:
-
predicate
the inverted predicate
- compose (f1, f2, ...)
-
Create a function composition from the given functions.
Parameters:
- f1 function the outermost function of the composition
- f2 function the second outermost function of the composition
- ... function... any further functions to add to the composition, in order
Returns:
-
function
the composite function
- bind (func, ...)
-
Create a function with bound arguments.
The bound function returned will call
func
with the arguments passed on to its creation.If more arguments are given during its call, they are appended to the original ones.
Parameters:
- func function the function to create a binding of
- ... the arguments to bind to the function.
Returns:
-
function
the bound function
- curry (func, levels)
-
Curries a function by the given amount of arguments.
Currying is, in simple terms, transforming a function
f
which is used as:res = f(x, y, z)
Into a function
g
which is used as:h = g(x); i = h(y); res = i(z)
Or, chaining all the calls into a single statement:
res = g(x)(y)(z)
You can read more about currying in the Wikipedia page on currying
In all levels except the deepest, any arguments past the first are ignored. In the deepest level, though, they are passed along to the function as normal. For an example, see Usage.
Parameters:
- func function the function to be curried
- levels integer the number of levels to curry for
Returns:
-
function
the curried function
Usage:
local function func(a, b, c, d) return a * b * c * d end local cfunc = f.curry(func, 3) -- 3 levels deep curry local c1 = cfunc(1) -- binds a = 1; 2 levels left after the call local c2 = c1(2, 3) -- binds b = 2, drops the 3; 1 level left after the call local res = c2(4, 5) -- calls w/ c = 4 & d = 5; this was the last level</pre>
- dict (t)
-
Create a function that accesses
t
.Creates a function that reads from
t
. The new function's argument is used as the key to indext
.Parameters:
- t table the table to be indexed
Returns:
-
function
the dictionary function
- indexer (k)
-
Create a function that accesses the key
k
for any table.The argument passed to the returned function is used as the table
t
to be accessed. The value oft[k]
is returned.Parameters:
- k the key to access
Returns:
-
function
the table indexer
- bind_self (t, k, ...)
-
Create a bound function whose first argument is
t
.Particularly useful to pass a method as a function.
Equivalent to
bind(t[k], t, ...)
.Parameters:
- t table the table to be accessed
- k the key to be accessed
- ... further arguments to bind to the function
Returns:
-
function
the binding for
t[k]
- constant (value)
-
Create a function that always returns the same value.
Parameters:
- value the constant to be returned
Returns:
-
function
the constant function
- import ([env=_G])
-
Import
Iterator
,lambda
, and commonly used functions into a given scope.Upon calling this, the following module entries will be added to the given environment. If
env
isnil
,_G
(global scope) is assumed.- Iterator
- iterate
- filter
- map
- reduce
- foreach
- take
- skip
- every
- any
- all
- zip
- packed_zip
- enumerate
- concat
- nop
- identity
- lambda
- clambda
They can still be accessed as usual through the module after the call.
Parameters:
- env table the environment to import into (default _G)
Fields
Class Iterator
- Iterator.over (iterable)
-
Iterate over the given
iterable
.If
iterable
is an array, create an Iterator instance that returns its values one by one. If it is an iterator, return itself.Parameters:
- iterable iterable the values to be iterated over
Returns:
-
Iterator
the new Iterator
- Iterator:next ()
-
Retrieve the next element from the iterator, if any.
Returns:
-
the next value in the sequence, or
nil
if there is none - Iterator.counter ()
-
Iterate over the naturals starting at 1.
Returns:
-
Iterator
the counter
See also:
- Iterator.range ([start=1], stop[, step=1])
-
Create an integer iterator that goes from
start
tostop
,step
-wise.Parameters:
- start integer the start of the integer range (default 1)
- stop integer the end of the integer range (inclusive)
- step integer the difference between consecutive elements (default 1)
Returns:
See also:
- Iterator.from (func, is, var)
-
Iterate over the function's returned values upon repeated calls.
This can effectively convert a vanilla-Lua iterator into a capital I Iterator. For example,
Iterator.from(io.lines "my_file.txt")
gives you a string iterator over the lines in a file.In general, any expression you can use in a for loop, you can wrap into an
Iterator.from
to get the same sequence of values in an Iterator form. For more information on iterators, read chapter 7 of Programming in Lua.Since any repeated function call without arguments can be used as a vanilla Lua iterator, they can also be used with
Iterator.from
. For an example, see Usage.Parameters:
- func function the function to call
- is
invariant state passed to
func
- var
initial variable passed to
func
Returns:
See also:
Usage:
local i = 0 local function my_counter() i = i + 1 if i > 10 then return nil end return i end f.from(my_counter):foreach(print) -- prints 1 through 10 and stops
- Iterator.packed_from (func, is, var)
-
Iterate over the function's returned values (packed into a table) upon repeated calls.
This is similar to Iterator.from, but instead of the created Iterator
generating multiple return values per call, it returns them all
packed into an array.
Parameters:
- func function the function to call
- is
invariant state passed to
func
- var
initial variable passed to
func
Returns:
See also:
- Iterator.from_coroutine (co)
-
Iterate over the
coroutine
's yielded values.For example, assume you have a coroutine that produces messages from clients. One easy way to consume them in a for loop would be as in Usage.
Parameters:
- co
thread
the
coroutine
to iterate
Returns:
Usage:
local function my_totally_real_message_queue() coroutine.yield("this is a message") coroutine.yield("hello!") coroutine.yield("almost done") coroutine.yield("bye bye") end local co = coroutine.create(my_totally_real_message_queue) for count, message in f.Iterator.from_coroutine(co):enumerate() do io.write(count, ": ", message, "\n") end io.write("end")
- co
thread
the
- Iterator.clone (iterable)
-
Nondestructively return an independent iterable from the given one.
If
iterable
is an Iterator, clone it according to its subtype. Ifiterable
is an array, then return itself.Please note that coroutine and iterated function call iterators cannot be cloned.
Parameters:
- iterable iterable the iterable to be cloned
Returns:
-
iterable
the clone
- Iterator:filter (predicate)
-
Select only values which match the predicate.
Parameters:
- predicate predicate the function to evaluate for each value
Returns:
- Iterator:map (mapping)
-
Map values into new values.
Please note that at no point during iteration may the
mapping
function returnnil
as its first value.Parameters:
- mapping function the function to evaluate for each value
Returns:
- Iterator:reduce (reducer, initial_value)
-
Collapse values into a single value.
A reducer is a function of the form
function(accumulated_value, new_value)
which returns the reducing or "accumulation" ofaccumulated_value
andnew_value
The definition of "reducing" is flexible, and a few common examples include sum and concatenation.
Parameters:
- reducer reducer the collapsing function
- initial_value
the initial value passed to the
reducer
Returns:
-
the accumulation of all values
- Iterator:foreach (func)
-
Apply a function to all values.
The main difference between
Iterator:foreach
andIterator:map
is thatforeach
ignores the return value(s) of its function, while map uses them and has restrictions on what it can return.Another important difference is that
Iterator:map
is a lazy evaluator, whileIterator:foreach
iterates over its values immediately.Parameters:
- func function the function to apply for each value
- Iterator:last ()
-
Consume the iterator and retrieve the last value it produces.
Returns:
-
the last value produced by the iterator, or
nil
if there was none. - Iterator:take (n)
-
Iterate over the
n
first values and stop.Parameters:
- n integer amount of values to take
Returns:
- Iterator:take_while (predicate)
-
Iterate while
predicate
istrue
and stop.Parameters:
- predicate predicate the predicate to check against
Returns:
- Iterator:take_last (n)
-
Consume the iterator and produce its
n
last values in order.Parameters:
- n integer the number of elements to capture
Returns:
- Iterator:take_until (predicate)
-
Iterate while
predicate
isfalse
and stop.Parameters:
- predicate predicate the predicate to check against
Returns:
- Iterator:skip (n)
-
Iterate over the values, starting at the
(n+1)
th one.Parameters:
- n integer amount of values to skip
Returns:
- Iterator:skip_while (predicate)
-
Iterate over the values, starting whenever
predicate
becomesfalse
for the first time.Parameters:
- predicate predicate the predicate to check against
Returns:
- Iterator:skip_until (predicate)
-
Iterate over the values, starting whenever
predicate
becomestrue
for the first time.Parameters:
- predicate predicate the predicate to check against
Returns:
- Iterator:every (n)
-
Take 1 value every
n
.The first value is always taken.
Parameters:
- n integer one more than the number of skipped values
Returns:
See also:
- Iterator:any (predicate)
-
Checks if any values evaluate to
true
.Parameters:
- predicate
predicate
the function to evaluate for each value,
defaults to
not (value == nil or value == false)
Returns:
-
boolean
true
if and only if at least one of the values evaluate totrue
- predicate
predicate
the function to evaluate for each value,
defaults to
- Iterator:all (predicate)
-
Checks if all values evaluate to
true
.Parameters:
- predicate
predicate
the function to evaluate for each value,
defaults to
not (value == nil or value == false)
Returns:
-
boolean
true
if and only if all of the values evaluate totrue
- predicate
predicate
the function to evaluate for each value,
defaults to
- Iterator:count (predicate)
-
Counts how many values evaluate to
true
.Parameters:
- predicate
predicate
function to evaluate for each value; if
nil
, then counts all values.
Returns:
-
integer
the number of values that match the
predicate
- predicate
predicate
function to evaluate for each value; if
- Iterator:zip (other)
-
Iterate over two iterables simultaneously.
This results in an Iterator with multiple values per :next() call.
The new Iterator will be considered complete as soon as the one the method was called on (
self
) is completed, regardless of the status ofother
.Parameters:
- other iterable the other iterable to zip with this one
Returns:
-
Iterator
the resulting zipped Iterator
- Iterator:packed_zip (other)
-
Iterate over two iterables simultaneously, giving their values as an array.
This results in an Iterator with a single value per :next() call.
Parameters:
- other iterable the other iterable to zip with this one
Returns:
-
Iterator
the resulting zipped Iterator
- Iterator:enumerate ()
-
Include an index while iterating.
The index is given as a single value to the left of all return values. Otherwise, the iterator is unchanged. This is similar to how
ipairs
iterates over an array, except it can be used with any iterator.Returns:
-
Iterator
the resulting indexed Iterator
See also:
Usage:
letters = f.every({"a", "b", "c", "d", "e"}, 2) for idx, letter in letters:enumerate() do print(idx, letter) end
- Iterator:concat (other)
-
Append elements from
other
after this iterator has been exhausted.Parameters:
- other iterable the iterator whose elements will be appended
Returns:
-
Iterator
the concatenation
- Iterator:to_array ()
-
Create an array out of the
Iterator
's values.Returns:
-
array
the array of values
- Iterator:to_coroutine ()
-
Create a
coroutine
that yields the values. of theIterator
.Returns:
-
thread
The new
coroutine
- Iterator:is_complete ()
-
Check whether or not the iterator is done.
Please note that even if the iterator has reached its actual last value, it has no way of knowing it was the last. Therefore, this function will only return true once the iterator returns
nil
for the first time.Returns:
-
boolean
true
if theIterator
has iterated over all its values.