Module:fun
- The followingdocumentationis located atModule:fun/documentation.[edit]
- Useful links:subpage list•links•transclusions•testcases•sandbox(diff)
fun
stands for "functional", but also functional programming can be fun. This library contains some typical metafunctions for functional programming, such asmap
,some
,all
,curry
,as well as others.
Functions that take an array as their second argument are available as methods in the arrays created byModule:array,with the arguments reversed so that they can be called as methods.
It was started in a user sandbox (Module:User:Erutuon/functional). It is not to be confused withLua Fun(of which there is a version atModule:User:Erutuon/luafun), even though some functions are similar.
The functions that take a table as their second argument will treat the table as an array ift[1]
is notnil
,and useipairs
.Otherwise, they will treat it as a hashmap, usingpairs
.
functionmap(func,iterable)
- Perform a function
func
on every element initerable
and return the resulting table.iterable
may be a table or a string. If a table, the function operates on every element in the array portion of the table; if a string, the function operates on every UTF-8 character in the string. The functionfunc
has the following signature:func(member,i,iterable)
.That is, the table element or UTF-8 character is first, then the index of this element, and then the iterable value (table or string). functionmapIter(func,iterator,iterable,initial_value)
- Create a new array from the results of performing a function on the values returned by an iterator. Can be used in combination with
sortedPairs
inModule:table.func
has the same signature described above. Not very useful withgmatch
;func
would have anil
first argument, because the single value returned from the iterator would be supplied as the second argument tofunc
. mapIter( function(parameter_value,parameter_name) return{parameter_name,parameter_value} end, sortedPairs(frame.args)) --> returns a sorted array of arrays containing parameter names and values
localexport={}
locallibraryUtil=require("libraryUtil")
localcheckType=libraryUtil.checkType
localcheckTypeMulti=libraryUtil.checkTypeMulti
localformat=string.format
localgetmetatable=getmetatable
localipairs=ipairs
localis_callable-- defined as export.is_callable below
localpairs=pairs
localselect=select
localtostring=tostring
localtype=type
localunpack=unpack
localiterableTypes={"table","string"}
localfunction_check(funcName,expectType)
iftype(expectType)=="string"then
returnfunction(argIndex,arg,nilOk)
returncheckType(funcName,argIndex,arg,expectType,nilOk)
end
else
returnfunction(argIndex,arg,expectType,nilOk)
iftype(expectType)=="table"then
ifnot(nilOkandarg==nil)then
returncheckTypeMulti(funcName,argIndex,arg,expectType)
end
else
returncheckType(funcName,argIndex,arg,expectType,nilOk)
end
end
end
end
-- Iterate over UTF-8-encoded codepoints in string.
localfunctioniterString(str)
localiter=string.gmatch(str,".[\128-\191]* ")
locali=0
localfunctioniterator()
i=i+1
localchar=iter()
ifcharthen
returni,char
end
end
returniterator
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).
]==]
functionexport.is_callable(f)
localf_type=type(f)
iff_type=="function"then
returntrue
elseiff_type~="table"then
returnfalse
end
localmt=getmetatable(f)
-- __call metamethods have to be functions, not functors.
returnmtandtype(mt.__call)=="function"orfalse
end
is_callable=export.is_callable
functionexport.chain(func1,func2,...)
returnfunc1(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" }
functionexport.map(func,iterable,isArray)
localcheck=_check'map'
check(1,func,"function")
check(2,iterable,iterableTypes)
localarray={}
localiterator=type(iterable)=="string"anditerString
or(isArrayoriterable[1]~=nil)andipairsorpairs
fori_or_k,valiniterator(iterable)do
array[i_or_k]=func(val,i_or_k,iterable)
end
returnarray
end
functionexport.mapIter(func,iter,iterable,initVal)
localcheck=_check'mapIter'
check(1,func,"function")
check(2,iter,"function")
check(3,iterable,iterableTypes,true)
-- initVal could be anything
localarray={}
locali=0
forx,yiniter,iterable,initValdo
i=i+1
array[i]=func(y,x,iterable)
end
returnarray
end
functionexport.forEach(func,iterable,isArray)
localcheck=_check'forEach'
check(1,func,"function")
check(2,iterable,iterableTypes)
localiterator=type(iterable)=="string"anditerString
or(isArrayoriterable[1]~=nil)andipairsorpairs
fori_or_k,valiniterator(iterable)do
func(val,i_or_k,iterable)
end
returnnil
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
localfunctionreverse(...)
-- reverse args by building a function to do it, similar to the unpack() example
localfunctionreverseHelper(acc,v,...)
ifselect('#',...)==0then
returnv,acc()
else
returnreverseHelper(function()returnv,acc()end,...)
end
end
-- initial acc is the end of the list
returnreverseHelper(function()returnend,...)
end
functionexport.curry(func,numArgs)
-- currying 2-argument functions seems to be the most popular application
numArgs=numArgsor2
-- no sense currying for 1 arg or less
ifnumArgs<=1thenreturnfuncend
-- helper takes an argTrace function, and number of arguments remaining to be applied
localfunctioncurryHelper(argTrace,n)
ifn==0then
-- kick off argTrace, reverse argument list, and call the original function
returnfunc(reverse(argTrace()))
else
-- "push" argument (by building a wrapper function) and decrement n
returnfunction(onearg)
returncurryHelper(function()returnonearg,argTrace()end,n-1)
end
end
end
-- push the terminal case of argTrace into the function first
returncurryHelper(function()returnend,numArgs)
end
-------------------------------------------------
-- some(function(val) return val % 2 == 0 end,
-- { 2, 3, 5, 7, 11 }) --> true
functionexport.some(func,t,isArray)
ifisArrayort[1]~=nilthen-- array
fori,vinipairs(t)do
iffunc(v,i,t)then
returntrue
end
end
else
fork,vinpairs(t)do
iffunc(v,k,t)then
returntrue
end
end
end
returnfalse
end
-- all(function(val) return val % 2 == 0 end,
-- { 2, 4, 8, 10, 12 }) --> true
functionexport.all(func,t,isArray)
ifisArrayort[1]~=nilthen-- array
fori,vinipairs(t)do
ifnotfunc(v,i,t)then
returnfalse
end
end
else
fork,vinpairs(t)do
ifnotfunc(v,k,t)then
returnfalse
end
end
end
returntrue
end
functionexport.filter(func,t,isArray)
localnew_t={}
ifisArrayort[1]~=nilthen-- array
localnew_i=0
fori,vinipairs(t)do
iffunc(v,i,t)then
new_i=new_i+1
new_t[new_i]=v
end
end
else
fork,vinpairs(t)do
iffunc(v,k,t)then
new_t[k]=v-- or create array?
end
end
end
returnnew_t
end
functionexport.fold(func,t,accum)
fori,vinipairs(t)do
accum=func(accum,v,i,t)
end
returnaccum
end
-------------------------------
-- Fancy stuff
localfunctioncapture(...)
localvals={n=select('#',...),...}
returnfunction()
returnunpack(vals,1,vals.n)
end
end
-- Log input and output of function.
-- Receives a function and returns a modified form of that function.
functionexport.logReturnValues(func,prefix)
returnfunction(...)
localinputValues=capture(...)
localreturnValues=capture(func(...))
ifprefixthen
mw.log(prefix,inputValues())
mw.log(returnValues())
else
mw.log(inputValues())
mw.log(returnValues())
end
returnreturnValues()
end
end
export.log=export.logReturnValues
-- Convenience function to make all functions in a table log their input and output.
functionexport.logAll(t)
fork,vinpairs(t)do
iftype(v)=="function"then
t[k]=export.logReturnValues(v,tostring(k))
end
end
returnt
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
-- Sentinels.
localargs,nil_,neg_0,pos_nan,neg_nan
-- Since all possible inputs need to be memoized (including true, false and nil), the table of arguments is stored with the sentinel key `args`. In addition, certain values can't be used as table keys, so they require sentinels as well: 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" ).
localfunctionget_key(input)
-- nil
ifinput==nilthen
ifnotnil_then
nil_={}
end
returnnil_
-- -0
elseifinput==0and1/input<0then
ifnotneg_0then
neg_0={}
end
returnneg_0
-- Default
elseifinput==inputthen
returninput
-- NaN
elseifformat("%f",input)=="nan"then
ifnotpos_nanthen
pos_nan={}
end
returnpos_nan
-- -NaN
elseifnotneg_nanthen
neg_nan={}
end
returnneg_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].
localfunctionget_memo(memo,n,nargs,key,...)
key=get_key(key)
localnext_memo=memo[key]
ifnext_memo==nilthen
next_memo={}
memo[key]=next_memo
end
memo=next_memo
returnn==nargsandmemoorget_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.
localfunctioncatch_output(...)
returnarg
end
functionexport.memoize(func,simple)
ifnotis_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
localmemo={}
returnsimpleandfunction(...)
localkey=get_key(...)
localoutput=memo[key]
ifoutput~=nilthen
ifoutput==nil_then
returnnil
end
returnoutput
end
output=func(...)
ifoutput~=nilthen
memo[key]=output
returnoutput
elseifnotnil_then
nil_={}
end
memo[key]=nil_
returnnil
endorfunction(...)
localnargs=select("#",...)
localmemo=nargs==0andmemoorget_memo(memo,1,nargs,...)
ifnotargsthen
args={}
end
localoutput=memo[args]
ifoutput==nilthen
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.
returnunpack(output,1,output.n)
end
end
end
returnexport