Module:fun: Difference between revisions

From Linguifex
Jump to navigation Jump to search
No edit summary
m (1 revision imported)
 
(2 intermediate revisions by 2 users not shown)
Line 1: Line 1:
local export = {}
local export = {}


local libraryUtil = require("libraryUtil")
local debug_track_module = "Module:debug/track"
local table_get_unprotected_metatable = "Module:table/getUnprotectedMetatable"


local checkType = libraryUtil.checkType
local chain -- defined below
local checkTypeMulti = libraryUtil.checkTypeMulti
local chain_iter -- defined below
local format = string.format
local format = string.format
local getmetatable = getmetatable
local gmatch = string.gmatch
local ipairs = ipairs
local ipairs = ipairs
local is_callable -- defined as export.is_callable below
local is_callable -- defined below
local pairs = pairs
local pairs = pairs
local pcall = pcall
local rawget = rawget
local require = require
local select = select
local select = select
local tostring = tostring
local tostring = tostring
local type = type
local type = type
local unpack = unpack
local unpack = unpack or table.unpack -- Lua 5.2 compatibility
local unroll -- defined below
local xpcall = xpcall


local iterableTypes = { "table", "string" }
local function debug_track(...)
debug_track = require(debug_track_module)
return debug_track(...)
end
 
local function get_unprotected_metatable(...)
get_unprotected_metatable = require(table_get_unprotected_metatable)
return get_unprotected_metatable(...)
end


local function _check(funcName, expectType)
local function _iterString(iter, i)
if type(expectType) == "string" then
i = i + 1
return function(argIndex, arg, nilOk)
local char = iter()
return checkType(funcName, argIndex, arg, expectType, nilOk)
if char ~= nil then
end
return i, char
else
return function(argIndex, arg, expectType, nilOk)
if type(expectType) == "table" then
if not (nilOk and arg == nil) then
return checkTypeMulti(funcName, argIndex, arg, expectType)
end
else
return checkType(funcName, argIndex, arg, expectType, nilOk)
end
end
end
end
end
end
Line 37: Line 41:
-- Iterate over UTF-8-encoded codepoints in string.
-- Iterate over UTF-8-encoded codepoints in string.
local function iterString(str)
local function iterString(str)
local iter = string.gmatch(str, ".[\128-\191]*")
return _iterString, gmatch(str, ".[\128-\191]*"), 0
local i = 0
local function iterator()
i = i + 1
local char = iter()
if char then
return i, char
end
end
return iterator
end
end


--[==[
--[==[
Return {true} if the input is a function or functor (a table which can be called like a function, because it has a {__call} metamethod).
Return {true} if the input is a function or functor (an object which can be called like a function, because it has a {__call} metamethod).
]==]
 
function export.is_callable(f)
Note: if the input is an object with a {__call} metamethod, but this function is not able to find it because the object's metatable is protected with {__metatable}, then it will return {false} by default, or {nil} if the {allow_maybe} flag is set.]==]
local f_type = type(f)
function export.is_callable(f, allow_maybe)
if f_type == "function" then
if type(f) == "function" then
return true
return true
elseif f_type ~= "table" then
end
-- An object is a functor if it has a `__call` metamethod. The only way to truly confirm this is by trying to call it, but that could be expensive or have side effects, so look for a `__call` metamethod instead. If the metatable is protected with `__metatable`, this may not be possible.
local mt = get_unprotected_metatable(f)
if mt == nil then
return false
-- `get_unprotected_metatable` returns false if the metatable is protected.
elseif mt == false then
debug_track("fun/is_callable/protected metatable")
if allow_maybe then
return nil
end
return false
return false
end
end
local mt = getmetatable(f)
-- `__call` metamethods have to be functions, so don't recurse to check it.
-- __call metamethods have to be functions, not functors.
local __call = rawget(mt, "__call")
return mt and type(mt.__call) == "function" or false
return __call and type(__call) == "function" or false
end
end
is_callable = export.is_callable
is_callable = export.is_callable


--[==[
A version of {xpcall} which takes any arguments to be given to {f} as additional arguments after the error handler.
This fixes a deficiency in the standard version of {xpcall}, which is not able to handle arguments to be given to {f}, and brings it in line with {pcall}.]==]
function export.xpcall(f, err_handler, ...)
-- If there are no arguments, just call xpcall() with `f`.
if select("#", ...) == 0 then
return xpcall(f, err_handler)
end
-- Any arguments have to be smuggled in via a table, as ... can't be an
-- upvalue, and it's not possible to use pcall() to get aroud this, because
-- xpcall() calls the error handler before the stack unwinds.
local args = {...}
return xpcall(function()
return f(unpack(args))
end, err_handler)
end
do
local function catch_values(f, success, ...)
if success then
return success, ...
-- Error message will only take this exact form if `f` is not callable,
-- because it will contain a traceback if it was thrown further up the
-- stack.
elseif (...) == format("attempt to call a %s value", type(f)) then
return false
end
return error(...)
end
--[==[
A special form of {pcall()}, which returns {true} plus the result value(s) if {f} is callable, or {false} if it isn't. Errors that occur within the called function are not protected.]==]
function export.try_call(f, ...)
local callable = is_callable(f, true)
if callable then
return true, f(...)
elseif callable == false then
return false
end
-- If `callable` is nil, there's a protected metatable, so there's no way to check without doing a protected call.
return catch_values(f, pcall(f, ...))
end
end
--[==[
Takes two or more functions as arguments, and returns a new function which calls each of the input functions in turn. Any arguments given to the returned function are given to the first function, and all other functions receive the output value(s) from the previous function.]==]
function export.chain(func1, func2, ...)
function export.chain(func1, func2, ...)
return func1(func2(...))
local function chained_func(...)
return func2(func1(...))
end
if select("#", ...) == 0 then
return chained_func
end
return chain(chained_func, ...)
end
chain = export.chain
 
--[==[
Takes the usual for-loop parameters (an iterator, plus an optional state and initial index), and unrolls the iterator by returning every (first) value returned by the iterator.
 
For instance, {unroll(pairs(t))} will return every key in {t}, and {unroll(string.gmatch(s, "%w+"))} will return every word in {s}.]==]
function export.unroll(iter, state, k)
k = iter(state, k)
if k ~= nil then
return k, unroll(iter, state, k)
end
end
unroll = export.unroll
 
--[==[
Takes a generator function (i.e. a function that returns an iterator, such as {ipairs}) and one or more additional functions, and returns a new generator function. Any arguments given to the new generator (e.g. an input table) are given to the original generator, and the additional functions are called on each iteration. The first additional function takes the output from the original iterator (i.e. the function returned by the original generator), and any further functions receive the output value(s) from the previous function. This can be used to modify the values returned from an iterator.]==]
function export.chainIter(gen, new_iter, ...)
if select("#", ...) > 0 then
new_iter = chain(new_iter, ...)
end
return function(...)
local orig_iter, state, k = gen(...)
-- k has to be the first value returned by orig_iter on the last iteration, not whatever new_iter returned.
local function catch_values(...)
k = ...
if k ~= nil then
return new_iter(...)
end
end
return function()
return catch_values(orig_iter(state, k))
end, state, k
end
end
chain_iter = export.chainIter
 
do
local function catch_values(start, iter, state, k, ...)
if start == k or k == nil then
return k, ...
end
return catch_values(start, iter, state, iter(state, k))
end
function export.iterateFrom(start, iter, state, k)
local first = true
return function(state, k)
if first then
first = false
return catch_values(start, iter, state, iter(state, k))
end
return iter(state, k)
end, state, k
end
end
end


Line 75: Line 189:
-- "abc") --> { "A", "B", "C" }
-- "abc") --> { "A", "B", "C" }
function export.map(func, iterable, isArray)
function export.map(func, iterable, isArray)
local check = _check 'map'
check(1, func, "function")
check(2, iterable, iterableTypes)
local array = {}
local array = {}
local iterator = type(iterable) == "string" and iterString
for k, v in (type(iterable) == "string" and iterString or (isArray or iterable[1] ~= nil) and ipairs or pairs)(iterable) do
or (isArray or iterable[1] ~= nil) and ipairs or pairs
array[k] = func(v, k, iterable)
for i_or_k, val in iterator(iterable) do
array[i_or_k] = func(val, i_or_k, iterable)
end
end
return array
return array
end
end


function export.mapIter(func, iter, iterable, initVal)
function export.mapIter(func, iter, state, init)
local check = _check 'mapIter'
-- init could be anything
check(1, func, "function")
local array, i = {}, 0
check(2, iter, "function")
for x, y in iter, state, init do
check(3, iterable, iterableTypes, true)
-- initVal could be anything
local array = {}
local i = 0
for x, y in iter, iterable, initVal do
i = i + 1
i = i + 1
array[i] = func(y, x, iterable)
array[i] = func(y, x, state)
end
end
return array
return array
end
do
local function iter_tuples(tuples)
local i = tuples.i
if i > 1 then
i = i - 1
tuples.i = i
return unpack(tuples[i])
end
end
-- Takes an iterator function, and returns a new iterator that iterates in reverse, given the same arguments.
-- Note: changes to the state during iteration are not taken into account, since all the return values are calculated in advance.
function export.reverseIter(func)
return function(...)
-- Store all returned values as a list of tuples, then iterate in reverse over that list.
local tuples, i, iter, state, val1 = {}, 0, func(...)
while true do
i = i + 1
local vals = {iter(state, val1)}
-- Terminates if the first return value is nil, even if other values are non-nil.
val1 = vals[1]
if val1 == nil then
tuples.i = i
return iter_tuples, tuples
end
tuples[i] = vals
end
end
end
end
end


function export.forEach(func, iterable, isArray)
function export.forEach(func, iterable, isArray)
local check = _check 'forEach'
for k, v in (type(iterable) == "string" and iterString or (isArray or iterable[1] ~= nil) and ipairs or pairs)(iterable) do
check(1, func, "function")
func(v, k, iterable)
check(2, iterable, iterableTypes)
local iterator = type(iterable) == "string" and iterString
or (isArray or iterable[1] ~= nil) and ipairs or pairs
for i_or_k, val in iterator(iterable) do
func(val, i_or_k, iterable)
end
end
return nil
return nil
Line 126: Line 252:
-- reverse args by building a function to do it, similar to the unpack() example
-- reverse args by building a function to do it, similar to the unpack() example
local function reverseHelper(acc, v, ...)
local function reverseHelper(acc, v, ...)
if select('#', ...) == 0 then
if select("#", ...) == 0 then
return v, acc()
return v, acc()
else
else
Line 166: Line 292:
-- { 2, 3, 5, 7, 11 }) --> true
-- { 2, 3, 5, 7, 11 }) --> true
function export.some(func, t, isArray)
function export.some(func, t, isArray)
if isArray or t[1] ~= nil then -- array
for k, v in ((isArray or t[1] ~= nil) and ipairs or pairs)(t) do
for i, v in ipairs(t) do
if func(v, k, t) then
if func(v, i, t) then
return true
return true
end
end
else
for k, v in pairs(t) do
if func(v, k, t) then
return true
end
end
end
end
end
Line 185: Line 303:
-- { 2, 4, 8, 10, 12 }) --> true
-- { 2, 4, 8, 10, 12 }) --> true
function export.all(func, t, isArray)
function export.all(func, t, isArray)
if isArray or t[1] ~= nil then -- array
for k, v in ((isArray or t[1] ~= nil) and ipairs or pairs)(t) do
for i, v in ipairs(t) do
if not func(v, k, t) then
if not func(v, i, t) then
return false
return false
end
end
else
for k, v in pairs(t) do
if not func(v, k, t) then
return false
end
end
end
end
end
Line 232: Line 342:
-- Fancy stuff
-- Fancy stuff
local function capture(...)
local function capture(...)
local vals = { n = select('#', ...), ... }
local vals = {n = select("#", ...), ...}
return function()
return function()
return unpack(vals, 1, vals.n)
return unpack(vals, 1, vals.n)
Line 260: Line 370:
function export.logAll(t)
function export.logAll(t)
for k, v in pairs(t) do
for k, v in pairs(t) do
if type(v) == "function" then
if is_callable(v) then
t[k] = export.logReturnValues(v, tostring(k))
t[k] = export.logReturnValues(v, tostring(k))
end
end
end
end
return t
return t
end
----- M E M O I Z A T I O N-----
-- Memoizes a function or callable table.
-- Supports any number of arguments and return values.
-- If the optional parameter `simple` is set, then the memoizer will use a faster implementation, but this is only compatible with one argument and one return value. If `simple` is set, additional arguments will be accepted, but this should only be done if those arguments will always be the same.
do
-- Placeholders.
local args, nil_, pos_nan, neg_nan, neg_0
-- Certain potential argument values can't be used as table keys, so we use placeholders for them instead: e.g. f("foo", nil, "bar") would be memoized at f["foo"][nil_]["bar"][args]. These values are:
-- nil.
-- -0, which is equivalent to 0 in most situations, but becomes "-0" on conversion to string; it also behaves differently in some operations (e.g. 1/a evaluates to inf if a is 0, but -inf if a is -0).
-- NaN and -NaN, which are the only values for which n == n is false; they only seem to differ on conversion to string ("nan" and "-nan").
local function get_key(input)
-- nil
if input == nil then
if not nil_ then
nil_ = {}
end
return nil_
-- -0
elseif input == 0 and 1 / input < 0 then
if not neg_0 then
neg_0 = {}
end
return neg_0
-- Default
elseif input == input then
return input
-- NaN
elseif format("%f", input) == "nan" then
if not pos_nan then
pos_nan = {}
end
return pos_nan
-- -NaN
elseif not neg_nan then
neg_nan = {}
end
return neg_nan
end
-- Return values are memoized as tables of return values, which are looked up using each input argument as a key, followed by args. e.g. if the input arguments were (1, 2, 3), the memo would be located at t[1][2][3][args]. args is always used as the final lookup key so that (for example) the memo for f(1, 2, 3), f[1][2][3][args], doesn't interfere with the memo for f(1, 2), f[1][2][args].
local function get_memo(memo, n, nargs, key, ...)
key = get_key(key)
local next_memo = memo[key]
if next_memo == nil then
next_memo = {}
memo[key] = next_memo
end
memo = next_memo
return n == nargs and memo or get_memo(memo, n + 1, nargs, ...)
end
-- Catch the function output values, and return the hidden variable arg (which is {...}, and available when a function has ...). We do this instead of catching the output in a table directly, because arg also contains the key "n", which is equal to select("#", ...). i.e. it's the number of arguments in ..., including any nils returned after the last non-nil value (e.g. select("#", nil) == 1, select("#") == 0, select("#", nil, "foo", nil, nil) == 4 etc.). The distinction between nil and nothing affects some native functions (e.g. tostring() throws an error, but tostring(nil) returns "nil"), so it needs to be reconstructable from the memo.
local function catch_output(...)
return arg
end
function export.memoize(func, simple)
if not is_callable(func) then
local _type = type(func)
error(format(
"Only functions and callable tables are memoizable. Received %s.",
_type == "table" and "non-callable table" or _type
))
end
local memo = {}
return simple and function(...)
local key = get_key(...)
local output = memo[key]
if output ~= nil then
if output == nil_ then
return nil
end
return output
end
output = func(...)
if output ~= nil then
memo[key] = output
return output
elseif not nil_ then
nil_ = {}
end
memo[key] = nil_
return nil
end or function(...)
local nargs = select("#", ...)
local memo = nargs == 0 and memo or get_memo(memo, 1, nargs, ...)
if not args then
args = {}
end
local output = memo[args]
if output == nil then
output = catch_output(func(...))
memo[args] = output
end
-- Unpack from 1 to the original number of return values (memoized as output.n); unpack returns nil for any values not in output.
return unpack(output, 1, output.n)
end
end
end
end


return export
return export

Latest revision as of 17:47, 4 November 2025



local export = {}

local debug_track_module = "Module:debug/track"
local table_get_unprotected_metatable = "Module:table/getUnprotectedMetatable"

local chain -- defined below
local chain_iter -- defined below
local format = string.format
local gmatch = string.gmatch
local ipairs = ipairs
local is_callable -- defined below
local pairs = pairs
local pcall = pcall
local rawget = rawget
local require = require
local select = select
local tostring = tostring
local type = type
local unpack = unpack or table.unpack -- Lua 5.2 compatibility
local unroll -- defined below
local xpcall = xpcall

local function debug_track(...)
	debug_track = require(debug_track_module)
	return debug_track(...)
end

local function get_unprotected_metatable(...)
	get_unprotected_metatable = require(table_get_unprotected_metatable)
	return get_unprotected_metatable(...)
end

local function _iterString(iter, i)
	i = i + 1
	local char = iter()
	if char ~= nil then
		return i, char
	end
end

-- Iterate over UTF-8-encoded codepoints in string.
local function iterString(str)
	return _iterString, gmatch(str, ".[\128-\191]*"), 0
end

--[==[
Return {true} if the input is a function or functor (an object which can be called like a function, because it has a {__call} metamethod).

Note: if the input is an object with a {__call} metamethod, but this function is not able to find it because the object's metatable is protected with {__metatable}, then it will return {false} by default, or {nil} if the {allow_maybe} flag is set.]==]
function export.is_callable(f, allow_maybe)
	if type(f) == "function" then
		return true
	end
	-- An object is a functor if it has a `__call` metamethod. The only way to truly confirm this is by trying to call it, but that could be expensive or have side effects, so look for a `__call` metamethod instead. If the metatable is protected with `__metatable`, this may not be possible.
	local mt = get_unprotected_metatable(f)
	if mt == nil then
		return false
	-- `get_unprotected_metatable` returns false if the metatable is protected.
	elseif mt == false then
		debug_track("fun/is_callable/protected metatable")
		if allow_maybe then
			return nil
		end
		return false
	end
	-- `__call` metamethods have to be functions, so don't recurse to check it.
	local __call = rawget(mt, "__call")
	return __call and type(__call) == "function" or false
end
is_callable = export.is_callable

	--[==[
	A version of {xpcall} which takes any arguments to be given to {f} as additional arguments after the error handler.
	
	This fixes a deficiency in the standard version of {xpcall}, which is not able to handle arguments to be given to {f}, and brings it in line with {pcall}.]==]
function export.xpcall(f, err_handler, ...)
	-- If there are no arguments, just call xpcall() with `f`.
	if select("#", ...) == 0 then
		return xpcall(f, err_handler)
	end
	-- Any arguments have to be smuggled in via a table, as ... can't be an
	-- upvalue, and it's not possible to use pcall() to get aroud this, because
	-- xpcall() calls the error handler before the stack unwinds.
	local args = {...}
	return xpcall(function()
		return f(unpack(args))
	end, err_handler)
end

do
	local function catch_values(f, success, ...)
		if success then
			return success, ...
		-- Error message will only take this exact form if `f` is not callable,
		-- because it will contain a traceback if it was thrown further up the
		-- stack.
		elseif (...) == format("attempt to call a %s value", type(f)) then
			return false
		end
		return error(...)
	end

	--[==[
	A special form of {pcall()}, which returns {true} plus the result value(s) if {f} is callable, or {false} if it isn't. Errors that occur within the called function are not protected.]==]
	function export.try_call(f, ...)
		local callable = is_callable(f, true)
		if callable then
			return true, f(...)
		elseif callable == false then
			return false
		end
		-- If `callable` is nil, there's a protected metatable, so there's no way to check without doing a protected call.
		return catch_values(f, pcall(f, ...))
	end
end

--[==[
Takes two or more functions as arguments, and returns a new function which calls each of the input functions in turn. Any arguments given to the returned function are given to the first function, and all other functions receive the output value(s) from the previous function.]==]
function export.chain(func1, func2, ...)
	local function chained_func(...)
		return func2(func1(...))
	end
	if select("#", ...) == 0 then
		return chained_func
	end
	return chain(chained_func, ...)
end
chain = export.chain

--[==[
Takes the usual for-loop parameters (an iterator, plus an optional state and initial index), and unrolls the iterator by returning every (first) value returned by the iterator.

For instance, {unroll(pairs(t))} will return every key in {t}, and {unroll(string.gmatch(s, "%w+"))} will return every word in {s}.]==]
function export.unroll(iter, state, k)
	k = iter(state, k)
	if k ~= nil then
		return k, unroll(iter, state, k)
	end
end
unroll = export.unroll

--[==[
Takes a generator function (i.e. a function that returns an iterator, such as {ipairs}) and one or more additional functions, and returns a new generator function. Any arguments given to the new generator (e.g. an input table) are given to the original generator, and the additional functions are called on each iteration. The first additional function takes the output from the original iterator (i.e. the function returned by the original generator), and any further functions receive the output value(s) from the previous function. This can be used to modify the values returned from an iterator.]==]
function export.chainIter(gen, new_iter, ...)
	if select("#", ...) > 0 then
		new_iter = chain(new_iter, ...)
	end
	return function(...)
		local orig_iter, state, k = gen(...)
		
		-- k has to be the first value returned by orig_iter on the last iteration, not whatever new_iter returned.
		local function catch_values(...)
			k = ...
			if k ~= nil then
				return new_iter(...)
			end
		end
		
		return function()
			return catch_values(orig_iter(state, k))
		end, state, k
	end
end
chain_iter = export.chainIter

do
	local function catch_values(start, iter, state, k, ...)
		if start == k or k == nil then
			return k, ...
		end
		return catch_values(start, iter, state, iter(state, k))
	end
	
	function export.iterateFrom(start, iter, state, k)
		local first = true
		return function(state, k)
			if first then
				first = false
				return catch_values(start, iter, state, iter(state, k))
			end
			return iter(state, k)
		end, state, k
	end
end

--	map(function(number) return number ^ 2 end,
--		{ 1, 2, 3 })									--> { 1, 4, 9 }
--	map(function (char) return string.char(string.byte(char) - 0x20) end,
--		"abc")											--> { "A", "B", "C" }
function export.map(func, iterable, isArray)
	local array = {}
	for k, v in (type(iterable) == "string" and iterString or (isArray or iterable[1] ~= nil) and ipairs or pairs)(iterable) do
		array[k] = func(v, k, iterable)
	end
	return array
end

function export.mapIter(func, iter, state, init)
	-- init could be anything
	local array, i = {}, 0
	for x, y in iter, state, init do
		i = i + 1
		array[i] = func(y, x, state)
	end
	return array
end

do
	local function iter_tuples(tuples)
		local i = tuples.i
		if i > 1 then
			i = i - 1
			tuples.i = i
			return unpack(tuples[i])
		end
	end
	
	-- Takes an iterator function, and returns a new iterator that iterates in reverse, given the same arguments.
	-- Note: changes to the state during iteration are not taken into account, since all the return values are calculated in advance.
	function export.reverseIter(func)
		return function(...)
			-- Store all returned values as a list of tuples, then iterate in reverse over that list.
			local tuples, i, iter, state, val1 = {}, 0, func(...)
			while true do
				i = i + 1
				local vals = {iter(state, val1)}
				-- Terminates if the first return value is nil, even if other values are non-nil.
				val1 = vals[1]
				if val1 == nil then
					tuples.i = i
					return iter_tuples, tuples
				end
				tuples[i] = vals
			end
		end
	end
end

function export.forEach(func, iterable, isArray)
	for k, v in (type(iterable) == "string" and iterString or (isArray or iterable[1] ~= nil) and ipairs or pairs)(iterable) do
		func(v, k, iterable)
	end
	return nil
end

-------------------------------------------------
-- From http://lua-users.org/wiki/CurriedLua
-- reverse(...) : take some tuple and return a tuple of elements in reverse order
--
-- e.g. "reverse(1,2,3)" returns 3,2,1
local function reverse(...)
	-- reverse args by building a function to do it, similar to the unpack() example
	local function reverseHelper(acc, v, ...)
		if select("#", ...) == 0 then
			return v, acc()
		else
			return reverseHelper(function() return v, acc() end, ...)
		end
	end
	
	-- initial acc is the end of the list
	return reverseHelper(function() return end, ...)
end

function export.curry(func, numArgs)
	-- currying 2-argument functions seems to be the most popular application
	numArgs = numArgs or 2
	
	-- no sense currying for 1 arg or less
	if numArgs <= 1 then return func end
	
	-- helper takes an argTrace function, and number of arguments remaining to be applied
	local function curryHelper(argTrace, n)
		if n == 0 then
			-- kick off argTrace, reverse argument list, and call the original function
			return func(reverse(argTrace()))
		else
			-- "push" argument (by building a wrapper function) and decrement n
			return function(onearg)
				return curryHelper(function() return onearg, argTrace() end, n - 1)
			end
		end
	end
	
	-- push the terminal case of argTrace into the function first
	return curryHelper(function() return end, numArgs)
end

-------------------------------------------------

--	some(function(val) return val % 2 == 0 end,
--		{ 2, 3, 5, 7, 11 })						--> true
function export.some(func, t, isArray)
	for k, v in ((isArray or t[1] ~= nil) and ipairs or pairs)(t) do
		if func(v, k, t) then
			return true
		end
	end
	return false
end

--	all(function(val) return val % 2 == 0 end,
--		{ 2, 4, 8, 10, 12 })					--> true
function export.all(func, t, isArray)
	for k, v in ((isArray or t[1] ~= nil) and ipairs or pairs)(t) do
		if not func(v, k, t) then
			return false
		end
	end
	return true
end

function export.filter(func, t, isArray)
	local new_t = {}
	if isArray or t[1] ~= nil then -- array
		local new_i = 0
		for i, v in ipairs(t) do
			if func(v, i, t) then
				new_i = new_i + 1
				new_t[new_i] = v
			end
		end
	else
		for k, v in pairs(t) do
			if func(v, k, t) then
				new_t[k] = v -- or create array?
			end
		end
	end
	return new_t
end

function export.fold(func, t, accum)
	for i, v in ipairs(t) do
		accum = func(accum, v, i, t)
	end
	return accum
end


-------------------------------
-- Fancy stuff
local function capture(...)
	local vals = {n = select("#", ...), ...}
	return function()
		return unpack(vals, 1, vals.n)
	end
end

-- Log input and output of function.
-- Receives a function and returns a modified form of that function.
function export.logReturnValues(func, prefix)
	return function(...)
		local inputValues = capture(...)
		local returnValues = capture(func(...))
		if prefix then
			mw.log(prefix, inputValues())
			mw.log(returnValues())
		else
			mw.log(inputValues())
			mw.log(returnValues())
		end
		return returnValues()
	end
end

export.log = export.logReturnValues

-- Convenience function to make all functions in a table log their input and output.
function export.logAll(t)
	for k, v in pairs(t) do
		if is_callable(v) then
			t[k] = export.logReturnValues(v, tostring(k))
		end
	end
	return t
end

return export