Module:fun: difference between revisions

From Wiktionary, the free dictionary
Jump to navigation Jump to search
Content deleted Content added
Even more efficient pattern.
Refactor memoize function.
Line 1: Line 1:
local export = {}
local export = {}


local libraryUtil = require "libraryUtil"
local libraryUtil = require("libraryUtil")
local m_table = require("Module:table")

local checkType = libraryUtil.checkType
local checkType = libraryUtil.checkType
local checkTypeMulti = libraryUtil.checkTypeMulti
local checkTypeMulti = libraryUtil.checkTypeMulti
local format = string.format
local get_nested = m_table.getNested
local ipairs = ipairs
local is_callable = require("Module:utilities").is_callable
local pairs = pairs
local setmetatable = setmetatable
local set_nested = m_table.setNested
local tostring = tostring
local type = type
local unpack = unpack


local iterableTypes = { "table", "string" }
local iterableTypes = { "table", "string" }
Line 244: Line 256:
-- Memoizes a function or callable table.
-- Memoizes a function or callable table.
-- Supports any number of arguments and return values.
-- Supports any number of arguments and return values.
do
local function is_callable(func)
local func_t = type(func)
local func_key = {}
local args_key = {}
if func_t == "function" then
return true
local Memo = {}
elseif func_t == "table" then
Memo.__index = Memo
local mt = getmetatable(func)
return type(mt) == "table" and is_callable(mt.__call)
function Memo:get_memo(...)
if ... == nil then
return self
end
local memo = get_nested(self, ...)
if not memo then
memo = {}
set_nested(self, memo, ...)
end
return memo
end
end
return false
function Memo:__call(...)
end
local memo = self:get_memo(...)

local output = memo[args_key]
local function cache_get(cache, func, args)
if output == nil then
local node = cache[1] and cache[1][func]
output = {self[func_key](...)}
if not node then
memo[args_key] = output
return nil
end
for i = 1, #args do
node = node[1] and node[1][args[i]]
if not node then
return nil
end
end
return unpack(output)
end
end
return node[2]
function export.memoize(func)
end

local function cache_put(cache, func, args, results)
cache[1] = cache[1] or {}
cache[1][func] = cache[1][func] or {}
local node, arg = cache[1][func]
for i = 1, #args do
arg = args[i]
node[1] = node[1] or {}
node[1][arg] = node[1][arg] or {}
node = node[1][arg]
end
node[2] = results
end

export.memoize = {
memo = function(cache, func)
cache = cache or {}
if not is_callable(func) then
if not is_callable(func) then
error(string.format(
error(format(
"Only functions and callable tables are memoizable. Received %s (a %s)",
"Only functions and callable tables are memoizable. Received %s (a %s)",
tostring(func), type(func)))
tostring(func), type(func)))
end
end
return function (...)
return setmetatable({[func_key] = func}, Memo)
local args = {...}
local results = cache_get(cache, func, args)
if not results then
results = {func(...)}
cache_put(cache, func, args, results)
end
return unpack(results)
end
end
end
end
}

setmetatable(export.memoize, {__call = function(...)
return export.memoize.memo(...) end
})


return export
return export

Revision as of 01:26, 15 April 2024

Lua error in package.lua at line 95: loop or previous error loading module 'Module:fun'

local export = {}

local libraryUtil = require("libraryUtil")
local m_table = require("Module:table")

local checkType = libraryUtil.checkType
local checkTypeMulti = libraryUtil.checkTypeMulti
local format = string.format
local get_nested = m_table.getNested
local ipairs = ipairs
local is_callable = require("Module:utilities").is_callable
local pairs = pairs
local setmetatable = setmetatable
local set_nested = m_table.setNested
local tostring = tostring
local type = type
local unpack = unpack

local iterableTypes = { "table", "string" }

local function _check(funcName, expectType)
	if type(expectType) == "string" then
		return function(argIndex, arg, nilOk)
			return checkType(funcName, argIndex, arg, expectType, nilOk)
		end
	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

-- Iterate over UTF-8-encoded codepoints in string.
local function iterString(str)
	local iter = string.gmatch(str, ".[\128-\191]*")
	local i = 0
	local function iterator()
		i = i + 1
		local char = iter()
		if char then
			return i, char
		end
	end
	
	return iterator
end

function export.chain(func1, func2, ...)
	return func1(func2(...))
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 check = _check 'map'
	check(1, func, "function")
	check(2, iterable, iterableTypes)
	
	local array = {}
	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
		array[i_or_k] = func(val, i_or_k, iterable)
	end
	return array
end

function export.mapIter(func, iter, iterable, initVal)
	local check = _check 'mapIter'
	check(1, func, "function")
	check(2, iter, "function")
	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
		array[i] = func(y, x, iterable)
	end
	return array
end

function export.forEach(func, iterable, isArray)
	local check = _check 'forEach'
	check(1, func, "function")
	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
	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)
	if isArray or t[1] ~= nil then -- array
		for i, v in ipairs(t) do
			if func(v, i, t) then
				return true
			end
		end
	else
		for k, v in pairs(t) do
			if func(v, k, t) then
				return true
			end
		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)
	if isArray or t[1] ~= nil then -- array
		for i, v in ipairs(t) do
			if not func(v, i, t) then
				return false
			end
		end
	else
		for k, v in pairs(t) do
			if not func(v, k, t) then
				return false
			end
		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

----- 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.
do
	local func_key = {}
	local args_key = {}
	
	local Memo = {}
	Memo.__index = Memo
	
	function Memo:get_memo(...)
		if ... == nil then
			return self
		end
		local memo = get_nested(self, ...)
		if not memo then
			memo = {}
			set_nested(self, memo, ...)
		end
		return memo
	end
	
	function Memo:__call(...)
		local memo = self:get_memo(...)
		local output = memo[args_key]
		if output == nil then
			output = {self[func_key](...)}
			memo[args_key] = output
		end
		return unpack(output)
	end
	
	function export.memoize(func)
		if not is_callable(func) then
			error(format(
	            "Only functions and callable tables are memoizable. Received %s (a %s)",
	             tostring(func), type(func)))
		end
		
		return setmetatable({[func_key] = func}, Memo)
	end
end

return export