Module:User:Erutuon/functional

local export = {}

local ustring = mw.ustring local libraryUtil = require "libraryUtil" local checkType = libraryUtil.checkType local checkTypeMulti = libraryUtil.checkTypeMulti

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 return checkTypeMulti(funcName, argIndex, arg, expectType, nilOk) else return checkType(funcName, argIndex, arg, expectType, nilOk) end end end end

local function iterString(str) local indices = {} local find = string.find local function iterator(str, pos) i = indices[pos] or 0 i = i + 1 pos = pos + 1 local _, endpos, char = find(str, "([%z\1-\127\194-\244][\128-\191]*)", pos) if char then indices[endpos] = i			return endpos, char, i		end end return iterator, str, 0 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) local check = _check 'map' check(1, func, "function") check(2, iterable, iterableTypes) local array = {} local iterator = type(iterable) == "string" and iterString or ipairs for i, val in iterator(iterable) do		array[i] = func(val, i, 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.mapIter2(func, iter, iterable, initVal) local check = _check 'mapIter2' check(1, func, "function") check(2, iter, "function") check(3, iterable, iterableTypes, true) -- initVal could be anything local array = {} local i = 0 while true do		local vals = { iter(iterable, initVal) } if not vals[1] then break end initVal = vals[1] i = i + 1 array[i] = func(unpack(vals)) end return array end

function export.forEach(func, iterable) local check = _check 'forEach' check(1, func, "function") check(2, iterable, iterableTypes) local iterator = type(iterable) == "string" and iterString or ipairs for i, val in iterator(iterable) do		func(val, i, iterable) end return nil end

- -- From []. -- 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 -

local function capture(...) local vals = {...} return function return unpack(vals) end end

-- Find out what functions are returning by making a new version that still -- returns the same values, but also logs them. 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

-- Make all functions in a table (for instance, a module's export table) log -- their return values, but otherwise function normally. 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- -- metamethod that does the work -- Currently supports one argument and one return value. local function callMethod(self, x)	local output = self[x] if not output then output = self.func(x) self[x] = output end return output end

-- shared metatable local mt = { __call = callMethod }

-- Create callable table. function export.memoize(func) return setmetatable({ func = func }, mt) end ---

--	some(function(val) return val % 2 == 0 end, --		{ 2, 3, 5, 7, 11 })						--> true function export.some(func, t)	if t[i] 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)	if t[i] 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

local function toHex(number) return ("0x%X"):format(number) end

local function printCodepoint(char) return toHex(ustring.codepoint(char)) end

function export.test(frame) local str = "ΒΓΔΖΘΚΛΜΝΞΠΡΣΣΤΦΧΨ" return table.concat(		export.mapIter2( function(pos, char, i) return char .. " (" .. pos .. ", " .. i .. ") &rarr; " .. mw.ustring.char(mw.ustring.codepoint(char) + 0x20) end, iterString(str)),		", "	) end

return export