Module:fun: Difference between revisions
Jump to navigation
Jump to search
No edit summary |
No edit summary |
||
| Line 1: | Line 1: | ||
local export = {} | local export = {} | ||
local getmetatable = getmetatable | local getmetatable = getmetatable | ||
local gmatch = string.gmatch | |||
local ipairs = ipairs | local ipairs = ipairs | ||
local pairs = pairs | local pairs = pairs | ||
local pcall = pcall | |||
local rawequal = rawequal | |||
local rawget = rawget | |||
local select = select | local select = select | ||
local setmetatable = setmetatable | |||
local tostring = tostring | local tostring = tostring | ||
local type = type | local type = type | ||
local unpack = unpack | local unpack = unpack | ||
--[==[ | |||
Loaders for functions in other modules, which overwrite themselves with the target function when called. This ensures modules are only loaded when needed, retains the speed/convenience of locally-declared pre-loaded functions, and has no overhead after the first call, since the target functions are called directly in any subsequent calls.]= | |||
local function | local function _iterString(iter, i) | ||
i = i + 1 | |||
local char = iter() | |||
if char ~= nil then | |||
return i, char | |||
end | end | ||
end | end | ||
| Line 37: | Line 27: | ||
-- Iterate over UTF-8-encoded codepoints in string. | -- Iterate over UTF-8-encoded codepoints in string. | ||
local function iterString(str) | local function iterString(str) | ||
return _iterString, gmatch(str, ".[\128-\191]*"), 0 | |||
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 (a table which can be called like a function, because it has a {__call} metamethod). | ||
]==] | |||
Note: if the input is a table with a protected metatable (i.e. one hidden using the `__metatable` metamethod), then this function will treat the value of `__metatable` as though it is the metatable, as that is what gets returned by `getmetatable` in such cases. If you are making use of the `__metatable` metamethod, make sure that `__metatable` is a table with a function at the `__call` key to ensure that this function returns the correct result; it does not matter if this function is the true `__call` metamethod.]==] | |||
function export.is_callable(f) | function export.is_callable(f) | ||
local f_type = type(f) | local f_type = type(f) | ||
| Line 60: | Line 41: | ||
return false | return false | ||
end | end | ||
-- A table is a functor if it has a `__call` metamethod. The only way to truly confirm this is by trying to call the table, but that could modify the table or other variables out of scope, so look for a `__call` metamethod instead. If the metatable is protected with `__metatable`, this may not be possible. | |||
local mt = getmetatable(f) | local mt = getmetatable(f) | ||
-- __call metamethods have to be functions, | if mt == nil then | ||
return | return false | ||
end | |||
-- Check if the metatable is protected: `setmetatable` will throw an error if so. | |||
local success = pcall(setmetatable, f, mt) | |||
-- If it's protected, then `mt` could be anything, but use the heuristic that if a `__call` key exists then that's probably intentional. | |||
-- This also builds in ways to ensure that this function always returns the correct result when implementing protected metatables. | |||
if not success then | |||
if type(mt) ~= "table" then | |||
return false | |||
end | |||
local __metatable = rawget(mt, "__metatable") | |||
-- If the value of `__metatable` is also `mt`, then `mt` must be the true metatable anyway (e.g. mw.loadData does this). | |||
end | |||
local __call = rawget(mt, "__call") | |||
-- `__call` metamethods have to be functions, so don't recurse when checking it. | |||
return __call ~= nil and type(__call) == "function" | |||
end | end | ||
function export.chain(func1, func2, ...) | function export.chain(func1, func2, ...) | ||
return func1(func2(...)) | return func1(func2(...)) | ||
end | |||
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 91: | ||
-- "abc") --> { "A", "B", "C" } | -- "abc") --> { "A", "B", "C" } | ||
function export.map(func, iterable, isArray) | function export.map(func, iterable, isArray) | ||
local array = {} | 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) | |||
array[ | |||
end | end | ||
return array | return array | ||
end | end | ||
function export.mapIter(func, iter, | function export.mapIter(func, iter, state, init) | ||
-- init could be anything | |||
local array, i = {}, 0 | |||
for x, y in iter, state, init do | |||
-- | |||
local array = {} | |||
for x, y in iter, | |||
i = i + 1 | i = i + 1 | ||
array[i] = func(y, x, | 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) | ||
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) | |||
func( | |||
end | end | ||
return nil | return nil | ||
| Line 126: | Line 154: | ||
-- 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( | if select("#", ...) == 0 then | ||
return v, acc() | return v, acc() | ||
else | else | ||
| Line 166: | Line 194: | ||
-- { 2, 3, 5, 7, 11 }) --> true | -- { 2, 3, 5, 7, 11 }) --> true | ||
function export.some(func, t, isArray) | 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 | ||
end | end | ||
| Line 185: | Line 205: | ||
-- { 2, 4, 8, 10, 12 }) --> true | -- { 2, 4, 8, 10, 12 }) --> true | ||
function export.all(func, t, isArray) | 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 | ||
end | end | ||
| Line 232: | Line 244: | ||
-- 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 265: | Line 277: | ||
end | end | ||
return t | return t | ||
end | end | ||
return export | return export | ||
Revision as of 21:19, 8 January 2025
- The following documentation is located at Module:fun/doc.[edit]
- Useful links: subpage list • links • transclusions • testcases • sandbox
local export = {}
local getmetatable = getmetatable
local gmatch = string.gmatch
local ipairs = ipairs
local pairs = pairs
local pcall = pcall
local rawequal = rawequal
local rawget = rawget
local select = select
local setmetatable = setmetatable
local tostring = tostring
local type = type
local unpack = unpack
--[==[
Loaders for functions in other modules, which overwrite themselves with the target function when called. This ensures modules are only loaded when needed, retains the speed/convenience of locally-declared pre-loaded functions, and has no overhead after the first call, since the target functions are called directly in any subsequent calls.]=
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 (a table which can be called like a function, because it has a {__call} metamethod).
Note: if the input is a table with a protected metatable (i.e. one hidden using the `__metatable` metamethod), then this function will treat the value of `__metatable` as though it is the metatable, as that is what gets returned by `getmetatable` in such cases. If you are making use of the `__metatable` metamethod, make sure that `__metatable` is a table with a function at the `__call` key to ensure that this function returns the correct result; it does not matter if this function is the true `__call` metamethod.]==]
function export.is_callable(f)
local f_type = type(f)
if f_type == "function" then
return true
elseif f_type ~= "table" then
return false
end
-- A table is a functor if it has a `__call` metamethod. The only way to truly confirm this is by trying to call the table, but that could modify the table or other variables out of scope, so look for a `__call` metamethod instead. If the metatable is protected with `__metatable`, this may not be possible.
local mt = getmetatable(f)
if mt == nil then
return false
end
-- Check if the metatable is protected: `setmetatable` will throw an error if so.
local success = pcall(setmetatable, f, mt)
-- If it's protected, then `mt` could be anything, but use the heuristic that if a `__call` key exists then that's probably intentional.
-- This also builds in ways to ensure that this function always returns the correct result when implementing protected metatables.
if not success then
if type(mt) ~= "table" then
return false
end
local __metatable = rawget(mt, "__metatable")
-- If the value of `__metatable` is also `mt`, then `mt` must be the true metatable anyway (e.g. mw.loadData does this).
end
local __call = rawget(mt, "__call")
-- `__call` metamethods have to be functions, so don't recurse when checking it.
return __call ~= nil and type(__call) == "function"
end
function export.chain(func1, func2, ...)
return func1(func2(...))
end
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 type(v) == "function" then
t[k] = export.logReturnValues(v, tostring(k))
end
end
return t
end
return export